open-standards/jouledesk/jouledesk-rs
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science b77b9d70f6
All checks were successful
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 11m52s
jcp-orchestrate: steps wait for steps, and a retry needs a name for the work
The loop was a flat queue of independent tasks routed by kind. A flat queue is
a work pool, not automation: nothing could express "transform after fetch, and
never publish because fetch failed". `Task::after` names the steps a task waits
on, and only an item whose every dependency is `Done` is dispatched.

An edge is one task id naming another in this run, resolved against the run's
own journal — not an endpoint in someone else's system. That is the whole point
of putting the graph here rather than in an integration fabric: the fabrics put
their edges across trust boundaries they do not own, which is where their
failures live, and an edge that does not cross a boundary cannot fail at one.

A step whose dependency did not complete is settled `WorkState::Blocked`, naming
what it waited on and why that did not happen. Blocked is deliberately not
Denied: a denial is a decision about a grant and carries a sealed receipt, while
a block is a missing precondition — no harness was offered the task, no grant was
consulted, nothing was spent, so there is nothing to seal. The blocking pass
runs to a fixed point, because settling one step is what determines why the next
can never run; a single pass would have to call a long chain behind one failure a
cycle, which it is not. A real cycle lands there too, so the loop still
terminates on any graph a caller can submit.

`Status::Retry` was taken at its word: the harness said transient, so the loop
ran the task again. But transient describes why the attempt failed, not whether
it left anything behind — a harness can fail after its effect has landed, and the
second run then pays for and performs that effect twice. The crate already
refuses that bet for `Interrupted`; it was taking it here. A retry is now
honoured only for a task carrying `Task::idempotency_key`, and refused otherwise
with that reason recorded. An attempt bound was never a substitute: it limits how
many times an effect may be duplicated, it does not make duplicating it safe.

SPEC §4.11 gains the rule. It already governed a decision recorded with no
outcome; this is the neighbouring case its own reasoning implies — an outcome
recorded as "run it again" — and it requires the key to be part of the durable
record, so a restarted enforcement point can tell a replay-safe action from one
that merely has attempts left.

Both the graph edges and the replay identity are journalled with the task, so a
resumed run rebuilds the graph and not a flat queue. The example is now a
five-step pipeline, submitted in reverse so the ordering cannot be an artefact of
the queue: the ship is denied for want of a capability and the announcement
behind it is never started. jouledesk gains the matching `TagOutcome::Blocked`,
which seals no efficiency receipt — there were no joules to account for.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-12 07:43:11 -05:00
..
crates jcp-orchestrate: steps wait for steps, and a retry needs a name for the work 2026-11-12 07:43:11 -05:00
vendor/libwebrtc jouledesk-livekit: prove the room binding live — real LiveKit run 2026-07-06 04:13:40 -04:00
Cargo.lock jouledesk: Telegram and WhatsApp bindings, believed only when authentic 2026-11-12 03:50:10 -05:00
Cargo.toml Cargo: no minimum-supported-version floor, because the policy is latest stable 2026-11-03 17:42:20 -05:00