Skip to main content

ff_script/functions/
scheduling.rs

1//! Typed FCALL wrappers for scheduling functions (lua/scheduling.lua).
2//!
3//! See `execution.rs` module-level rustdoc for the Partial-type pattern
4//! rationale (RFC-011 §2.4).
5
6use ff_core::contracts::*;
7use crate::error::ScriptError;
8use ff_core::keys::{ExecKeyContext, IndexKeys};
9use ff_core::types::*;
10
11use crate::result::{FcallResult, FromFcallResult};
12
13/// Partial form of [`ChangePriorityResult`].
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub enum ChangePriorityResultPartial {
16    Changed,
17}
18
19impl ChangePriorityResultPartial {
20    pub fn complete(self, execution_id: ExecutionId) -> ChangePriorityResult {
21        match self {
22            Self::Changed => ChangePriorityResult::Changed { execution_id },
23        }
24    }
25}
26
27/// Key context for scheduling operations that need exec_core + index keys.
28pub struct SchedOpKeys<'a> {
29    pub ctx: &'a ExecKeyContext,
30    pub idx: &'a IndexKeys,
31    pub lane_id: &'a LaneId,
32}
33
34// ─── ff_issue_claim_grant ──────────────────────────────────────────────
35//
36// Lua KEYS (3): exec_core, claim_grant_key, eligible_zset
37// Lua ARGV (9): execution_id, worker_id, worker_instance_id,
38//               lane_id, capability_hash, grant_ttl_ms,
39//               route_snapshot_json, admission_summary,
40//               worker_capabilities_csv
41
42ff_function! {
43    pub ff_issue_claim_grant(args: IssueClaimGrantArgs) -> IssueClaimGrantResult {
44        keys(k: &SchedOpKeys<'_>) {
45            k.ctx.core(),
46            k.ctx.claim_grant(),
47            k.idx.lane_eligible(k.lane_id),
48        }
49        argv {
50            args.execution_id.to_string(),
51            args.worker_id.to_string(),
52            args.worker_instance_id.to_string(),
53            args.lane_id.to_string(),
54            args.capability_hash.clone().unwrap_or_default(),
55            args.grant_ttl_ms.to_string(),
56            args.route_snapshot_json.clone().unwrap_or_default(),
57            args.admission_summary.clone().unwrap_or_default(),
58            // BTreeSet iterates in sorted order → stable CSV for Lua match
59            args.worker_capabilities.iter().cloned().collect::<Vec<_>>().join(","),
60        }
61    }
62}
63
64impl FromFcallResult for IssueClaimGrantResult {
65    fn from_fcall_result(raw: &ferriskey::Value) -> Result<Self, ScriptError> {
66        let r = FcallResult::parse(raw)?.into_success()?;
67        // ok(execution_id)
68        let eid_str = r.field_str(0);
69        let eid = ExecutionId::parse(&eid_str)
70            .map_err(|e| ScriptError::Parse {
71                fcall: "ff_issue_claim_grant".into(),
72                execution_id: None,
73                message: format!("bad execution_id: {e}"),
74            })?;
75        Ok(IssueClaimGrantResult::Granted { execution_id: eid })
76    }
77}
78
79// ─── ff_change_priority ────────────────────────────────────────────────
80//
81// Lua KEYS (2): exec_core, eligible_zset
82// Lua ARGV (2): execution_id, new_priority
83
84ff_function! {
85    pub ff_change_priority(args: ChangePriorityArgs) -> ChangePriorityResultPartial {
86        keys(k: &SchedOpKeys<'_>) {
87            k.ctx.core(),
88            k.idx.lane_eligible(k.lane_id),
89        }
90        argv {
91            args.execution_id.to_string(),
92            args.new_priority.to_string(),
93        }
94    }
95}
96
97impl FromFcallResult for ChangePriorityResultPartial {
98    fn from_fcall_result(raw: &ferriskey::Value) -> Result<Self, ScriptError> {
99        let _r = FcallResult::parse(raw)?.into_success()?;
100        // ok(old_priority, new_priority)
101        Ok(Self::Changed)
102    }
103}
104
105// ─── ff_update_progress ────────────────────────────────────────────────
106//
107// Lua KEYS (1): exec_core
108// Lua ARGV (5): execution_id, lease_id, lease_epoch, progress_pct, progress_message
109
110ff_function! {
111    pub ff_update_progress(args: UpdateProgressArgs) -> UpdateProgressResult {
112        keys(k: &SchedOpKeys<'_>) {
113            k.ctx.core(),
114        }
115        argv {
116            args.execution_id.to_string(),
117            args.lease_id.to_string(),
118            args.lease_epoch.to_string(),
119            args.progress_pct.map(|p| p.to_string()).unwrap_or_default(),
120            args.progress_message.clone().unwrap_or_default(),
121        }
122    }
123}
124
125impl FromFcallResult for UpdateProgressResult {
126    fn from_fcall_result(raw: &ferriskey::Value) -> Result<Self, ScriptError> {
127        let _r = FcallResult::parse(raw)?.into_success()?;
128        // ok()
129        Ok(UpdateProgressResult::Updated)
130    }
131}
132
133// ─── Partial-type tests (RFC-011 §2.4 acceptance) ──────────────────────
134#[cfg(test)]
135mod partial_tests {
136    use super::*;
137    use ff_core::partition::PartitionConfig;
138
139    #[test]
140    fn change_priority_partial_complete_attaches_execution_id() {
141        let partial = ChangePriorityResultPartial::Changed;
142        let eid = ExecutionId::for_flow(&FlowId::new(), &PartitionConfig::default());
143        let full = partial.complete(eid.clone());
144        match full {
145            ChangePriorityResult::Changed { execution_id } => assert_eq!(execution_id, eid),
146        }
147    }
148}