PR #928 shipped the executor client with side_channel and lease. It does not
advertise steps, and this is the other half.
steps is the one capability that fails rather than degrades: a durable step
that silently did not commit is a step that will re-run a charge. So the client
does not advertise it today, which is the contract's own rule rather than a gap
left open by accident — a step call simply is not available.
What it needs, none of which the wire hands over:
- A local
StepSequence. crates/flexiq-core/src/step/sequence.rs is the
reference and is core-internal; a Go executor reimplements it. seq is
gapless, 0-based, and equals the number of rows already stored — not the
positional cursor, because a keyed hit can land out of order and leave the
cursor behind.
- Snapshot decoding.
JobStepsFrame.snapshot is a JSON metadata array, one
\n, then every step's blob concatenated in seq order, each blob's length
taken from that step's result_len. The decoder must be strict: a snapshot
that silently came back short is a memo that silently went missing. No frame
means an empty snapshot, never an unknown one.
step_key derivation. name#occurrence unkeyed, name:key keyed. The
occurrence counter is per name and per attempt, and a refused request does not
spend one. A keyed call never touches the counter, so adding one cannot shift a
later unkeyed call's number.
- Ack correlation on
(job_id, seq). Register the waiter before sending
the commit — a fast scheduler can answer before anyone is listening. The wait
is bounded by min(step_ack_timeout, the job's remaining timeout). already
is a success: it is a retransmission after a lost ack.
- The two-phase sleep.
step_commit with kind: Sleep and a candidate
deadline, then — only after the ack, which echoes the deadline storage actually
settled on — a slept frame that ends the attempt. A deadline already elapsed
is an ordinary memo hit and sends no frame at all.
- Refusal classification from the enum, never the message.
StepFailure is
retryable, permanent or superseded, and a superseded commit means another
attempt owns this job now and this one must stop without writing.
Every step frame carries the dispatch's lease, like every other frame that
advances an attempt.
Prerequisite reading: crates/flexiq-core/BINDING_CONTRACT.md, the durable-step
sections; contracts/REMOTE_SDK_CONTRACT.md.
PR #928 shipped the executor client with
side_channelandlease. It does notadvertise
steps, and this is the other half.stepsis the one capability that fails rather than degrades: a durable stepthat silently did not commit is a step that will re-run a charge. So the client
does not advertise it today, which is the contract's own rule rather than a gap
left open by accident — a step call simply is not available.
What it needs, none of which the wire hands over:
StepSequence.crates/flexiq-core/src/step/sequence.rsis thereference and is core-internal; a Go executor reimplements it.
seqisgapless, 0-based, and equals the number of rows already stored — not the
positional cursor, because a keyed hit can land out of order and leave the
cursor behind.
JobStepsFrame.snapshotis a JSON metadata array, one\n, then every step's blob concatenated inseqorder, each blob's lengthtaken from that step's
result_len. The decoder must be strict: a snapshotthat silently came back short is a memo that silently went missing. No frame
means an empty snapshot, never an unknown one.
step_keyderivation.name#occurrenceunkeyed,name:keykeyed. Theoccurrence counter is per name and per attempt, and a refused request does not
spend one. A keyed call never touches the counter, so adding one cannot shift a
later unkeyed call's number.
(job_id, seq). Register the waiter before sendingthe commit — a fast scheduler can answer before anyone is listening. The wait
is bounded by
min(step_ack_timeout, the job's remaining timeout).alreadyis a success: it is a retransmission after a lost ack.
step_commitwithkind: Sleepand a candidatedeadline, then — only after the ack, which echoes the deadline storage actually
settled on — a
sleptframe that ends the attempt. A deadline already elapsedis an ordinary memo hit and sends no frame at all.
StepFailureisretryable,permanentorsuperseded, and a superseded commit means anotherattempt owns this job now and this one must stop without writing.
Every step frame carries the dispatch's lease, like every other frame that
advances an attempt.
Prerequisite reading:
crates/flexiq-core/BINDING_CONTRACT.md, the durable-stepsections;
contracts/REMOTE_SDK_CONTRACT.md.