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(format!("bad execution_id: {e}")))?;
71        Ok(IssueClaimGrantResult::Granted { execution_id: eid })
72    }
73}
74
75// ─── ff_change_priority ────────────────────────────────────────────────
76//
77// Lua KEYS (2): exec_core, eligible_zset
78// Lua ARGV (2): execution_id, new_priority
79
80ff_function! {
81    pub ff_change_priority(args: ChangePriorityArgs) -> ChangePriorityResultPartial {
82        keys(k: &SchedOpKeys<'_>) {
83            k.ctx.core(),
84            k.idx.lane_eligible(k.lane_id),
85        }
86        argv {
87            args.execution_id.to_string(),
88            args.new_priority.to_string(),
89        }
90    }
91}
92
93impl FromFcallResult for ChangePriorityResultPartial {
94    fn from_fcall_result(raw: &ferriskey::Value) -> Result<Self, ScriptError> {
95        let _r = FcallResult::parse(raw)?.into_success()?;
96        // ok(old_priority, new_priority)
97        Ok(Self::Changed)
98    }
99}
100
101// ─── ff_update_progress ────────────────────────────────────────────────
102//
103// Lua KEYS (1): exec_core
104// Lua ARGV (5): execution_id, lease_id, lease_epoch, progress_pct, progress_message
105
106ff_function! {
107    pub ff_update_progress(args: UpdateProgressArgs) -> UpdateProgressResult {
108        keys(k: &SchedOpKeys<'_>) {
109            k.ctx.core(),
110        }
111        argv {
112            args.execution_id.to_string(),
113            args.lease_id.to_string(),
114            args.lease_epoch.to_string(),
115            args.progress_pct.map(|p| p.to_string()).unwrap_or_default(),
116            args.progress_message.clone().unwrap_or_default(),
117        }
118    }
119}
120
121impl FromFcallResult for UpdateProgressResult {
122    fn from_fcall_result(raw: &ferriskey::Value) -> Result<Self, ScriptError> {
123        let _r = FcallResult::parse(raw)?.into_success()?;
124        // ok()
125        Ok(UpdateProgressResult::Updated)
126    }
127}
128
129// ─── Partial-type tests (RFC-011 §2.4 acceptance) ──────────────────────
130#[cfg(test)]
131mod partial_tests {
132    use super::*;
133    use ff_core::partition::PartitionConfig;
134
135    #[test]
136    fn change_priority_partial_complete_attaches_execution_id() {
137        let partial = ChangePriorityResultPartial::Changed;
138        let eid = ExecutionId::for_flow(&FlowId::new(), &PartitionConfig::default());
139        let full = partial.complete(eid.clone());
140        match full {
141            ChangePriorityResult::Changed { execution_id } => assert_eq!(execution_id, eid),
142        }
143    }
144}