Migrating to Flow 4.0.0#
Three defects were fixed that let a run assert things that had not happened. Each fix is correct, and each changes behaviour a consumer may be relying on — which is why this is a major rather than the patch the commit types implied.
Breaking: evaluate --gate <gate> refuses to carry a run past an unevaluated gate#
Before: naming a gate at a step other than the run's current step silently synthesised a cursor at that gate, so the jump check compared the gate against itself and could never fire. The run advanced, and the persisted record asserted a from_step the run had never occupied — marked allowed.
After: the request is appraised against the run's real state.
- Walking forward through gateless steps still works, and each transition is recorded on its own. This is the case the previous behaviour existed to serve, and the repo's own
examples/adversarial-pass-flow.jsondepends on it. - Re-appraising a step the run already occupies still works.
- A request that would carry the run past a gate that has not been evaluated now throws where it previously exited 0.
If you relied on the old behaviour, the sanctioned route past an unsatisfiable gate is flow except, which records a reason and an authority. That has always been the authenticated path; the synthesised cursor was an unauthenticated second one.
The refusal is enforced at the write point, so a reintroduced shortcut still cannot fabricate a from_step.
Breaking: the run-output TrustBundle no longer asserts a run-level verdict#
Before: the all-required claim group contained only the stages that had passed, so "Run verified — all flow stages passed" was vacuously true. A run whose first required gate was block and whose middle gate was never evaluated rolled up to verified. Exception-passed stages emitted verified with an empty evidence list and no mention of the exception.
After: every stage is a member claim and Flow emits no producer-asserted status. The verdict is derived by the consumer.
- An accepted exception is now a waiver in
claim.metadata, per the Hachurewaivers.mdcontract, with eventassumed. - An unappraised stage carries no event and derives
unknown. - A blocked-and-waived run now rolls up
rejected. An all-passed run still rolls upverified.
If you consumed the old rollup, you were reading a verdict Flow computed by choosing the membership. Derive it from the members instead — buildTrustReport from @kontourai/surface does this.
Non-breaking: run artifacts publish atomically#
state.json, the evidence manifest, and the report are now staged and renamed rather than truncated in place, with the state file published last. Under concurrent load the previous form produced torn reads — 17 in ~32,000 trials before, 0 in ~36,000 after.
Also#
Never-evaluated gates now report unknown rather than wait. Previously "never checked" and "checked and waiting" were indistinguishable in the report.
Known issues, not fixed here#
- #211 — a completed run reports its terminal step as
currentindefinitely. Worked around inside the bundle builder; the reporting bug is live. - #212 —
acceptExceptionaccepts an exception for a gate the run has not reached and flips the whole run toaccepted_by_exceptionwith no diagnostic.