Skip to content

Decision order: guard the position a checkpoint decides in - #471

Merged
m2ux merged 4 commits into
mainfrom
fix/469-step-order
Aug 18, 2026
Merged

Decision order: guard the position a checkpoint decides in#471
m2ux merged 4 commits into
mainfrom
fix/469-step-order

Conversation

@m2ux

@m2ux m2ux commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

A step gated on a variable no earlier step could have bound reads nothing, so the step is skipped. The checkpoint that decides that variable runs later, and its answer arrives with nothing left to apply it to. The run completes, having asked a question that changed nothing.

Nothing reported this class, so it accumulated. This adds a guard that does, and adopts the corpus commit that clears the two instances it finds. Closes the server half of #469; the definition half is #470.

What the guard checks

For each checkpoint, the variables its options bind, against every earlier step in the same activity that is gated on one of them. A hit is a step that runs first and reads nothing.

Five positions are exempt because the earlier read has an answer, or loses nothing by not firing:

Exempt Why
The variable declares a defaultValue Seeding puts it in the bag at session creation, so the earlier gate reads the default rather than nothing
The earlier gate reads by exists / notExists A presence test answers on a missing variable; absence is one of its two answers
The earlier step only messages or logs An announcement that stays quiet costs nothing
The deciding option carries transitionTo Re-entry sends the run back through the earlier step, which then reads what the option wrote
The two gates demand incompatible values of one variable No single run reaches both steps, so the earlier one was never waiting on this decision

The last exemption is provable rather than conventional, and it is what makes the corpus's standard way of settling a value legible: a technique derives it, an announcement reports it when the derivation was confident, a checkpoint decides it when the derivation was ambiguous, and the announcement and the checkpoint carry opposite gates on the ambiguity flag.

Gates are read through the repository's own when parser rather than by splitting the string, so precedence and parentheses are handled the way the server handles them. Only conjuncts are taken as requirements; an or proves nothing about which branch a run took, so a gate built from one contributes no exclusion — the guard reports rather than assumes.

Why the exemptions are the guard

Measured on the corpus this adopts: the rule with its exemptions removed — a checkpoint decides what an earlier step read — finds 14 pairs, and 12 are working patterns. A guard with that ratio does not get run. With the exemptions it finds two, both real, and both fixed in #470: the issue-platform gate in work-package and the structural-inventory persist in workflow-design.

Every exemption is load-bearing. Removing any single one puts a working pattern back on the report — the defaults and announcement carve-outs by two each, the presence, re-entry and exclusivity carve-outs by one each.

The guard reports two findings against the parent corpus commit and zero against the adopted one, so a green corpus is evidence the guard can still fire rather than evidence it never could. The fixtures pin both directions and one case per exemption.

Why the smoke suite blessed the defect

Worth stating, because the answer shaped a change here. A step absent from a walk's executed list is silent about why — correctly gated out and skipped-for-want-of-a-decision look identical — and the snapshot excluded the variable bag by design. So the twelve platform-gated steps were recorded as absent and read as correct.

Measured on the six-policy matrix: 103 of 266 steps (39%) execute in zero walks, and 83 of 99 checkpoint options are never exercised. issue-verification declares no defaultOption, so every policy falls back to its first option — "Provide existing issue" — and the create path where the platform gate lives is never walked at all.

The snapshot now records, per activity, the gates it evaluated with nothing in the bag to read. Two filters keep it to the shape worth seeing: only positive forms (absence answers a negative comparison or a presence test, which is how this corpus spells "not in that mode"), and only variables the activity's own checkpoints or set actions bind (a technique output a no-agent walker was never going to produce stays out).

That is 20 entries on start-work-package and none on the other eleven activities — twelve naming the issue platform, eight the review-mode flag. It makes the class visible in review and fails the snapshot when it changes. It is deliberately not an assertion: a walk cannot reach zero while technique outputs stay unbound.

Scope of change

One new guard script and its test, one registry entry, one npm script, and a documentation section on where a checkpoint belongs. One new exported helper on the gate-liveness module with six tests, and the walker and snapshot changes that use it. The corpus bump brings seven definition fixes, with the walk snapshots, the corpus stamp and the delivery-cost baseline re-recorded in the same commit as each pointer move.

Acceptance criteria

  • 28 guards pass, up from 27.
  • 1035 tests pass, including nine that pin the new guard's rule and each exemption, and six on the unbound-read helper.
  • The walk snapshots reflect the three path changes visible in a walk — the signing check preceding the index, one fewer checkpoint round trip, and the reference detector's second binding firing on the path this walk takes — and nothing else.
  • The delivery-cost gate passes against a baseline re-recorded on the adopted corpus commit, at index 100.

What the walk shows

Delivery is 1,302,319 characters, 5,559 more than the recording before the adopted fixes. The walk also drops from 11 checkpoint round trips to 10, because a project with no build-dependent artifacts is no longer asked about them.

Most of the increase is one extra get_technique: the issue-reference detector's second binding delivers a full 4,054 characters rather than a ledger hit, because a technique bundled into get_activity does not satisfy a later standalone get_technique for the same content. The bundled delivery and the standalone answer are recorded under different ledger key shapes, so the content hash never matches. That is a gap in the reference-delivery machinery rather than a definition problem, and it is left open here — the fixture description records the measurement so it does not have to be re-derived.

Non-goals

  • The guard proves order, not liveness. It cannot tell whether a producer that exists actually binds the variable on the path a given run takes — a technique that declares an output and leaves it unset on one branch still counts as a producer, which is exactly why the issue-platform defect needed a definition fix the guard could not have found. The runtime already counts gates it has no answer for, on the cost line, with real bag state; that is the measurement that sees paths.
  • The bundled-delivery ledger gap is not closed. Measured at 4,054 characters on this walk. It belongs with the delivery layer.
  • No change to the entry rule. check:checkpoint-entry keeps its single sentence — a checkpoint may not be an activity's first step. The two rules share a subject but not an id, so a sweep reports which one fired.
  • The 34 remaining findings from the evaluation are untouched.

Investigation detail

Evaluation report and per-dimension analysis

🤖 Generated with Claude Code

Bumps the corpus pin to the five step-position fixes, and re-records everything
coupled to a walk: the six snapshots, the corpus stamp they were taken against,
and the delivery-cost baseline the gate reads.

Two of the fixes are visible in the snapshots. The signing precondition now
precedes the repository index, so a repository without signing configured fails
before paying for a full index. The build-artifact gate applies to
rust-substrate projects, so this walk takes 10 checkpoint round trips instead of
11 — one fewer yield, respond and resume for every project that has no
build-dependent artifacts to regenerate.

Delivery is 1,297,394 characters, 634 above the previous recording. Those
characters are the step that derives the regeneration commands and the
project-type clause on the two gates that present them. The fixture description
carries the reason, and the gate reads 100.
A step gated on a variable no earlier step could have bound reads nothing, so it
is skipped, and the checkpoint that decides that variable runs later with nothing
left to apply its answer to. check:decision-order reports the pair.

Five exemptions, each because the earlier read has an answer or loses nothing by
not firing: a declared default is in the bag from session creation; exists and
notExists answer on a missing variable; an announcement that stays quiet costs
nothing; an option carrying transitionTo sends the run back through the reader;
and gates demanding incompatible values of one variable are never both reached,
which is the shape of deriving a value, announcing it when the derivation was
confident, and gating it when it was not.

Measured on the corpus this lands against: the plain rule finds 14 pairs, 12 of
them patterns working as designed, and 2 real ones, both fixed in the corpus
commit this adopts. Every exemption is load-bearing — removing any single one
puts a pattern back on the report.
m2ux added 2 commits August 17, 2026 16:07
Bumps the pin to the two adjacent fixes and re-records the five walk snapshots,
the corpus stamp and the delivery baseline against it.

The snapshots gain one step: the issue-reference detector's second binding, which
fires on this walk because the walker never binds a platform. That is the defect
the step closes, visible from the runtime side — the platform-gated verification
steps were reached with nothing to read.

Delivery is 1,302,319 characters, 4,925 above the previous recording. Most of it
is one extra get_technique: the detector's second delivery is a full 4,054-character
fetch rather than a ledger hit, because a technique bundled into get_activity does
not satisfy a later standalone get_technique for the same content. The gate reads
100 against the re-recorded fixture, whose description carries the reason.
A step absent from a walk's executed list is silent about why. Correctly gated
out and skipped-for-want-of-a-decision look identical, and the snapshot excluded
the variable bag by design, so nothing in the committed artifact distinguished
them. That is how the issue-platform ordering defect sat in the baseline: twelve
steps gated on a platform no walk binds, recorded as absent and read as correct.

The snapshot now carries, per activity, the gates it evaluated with nothing in
the bag to read. Two filters keep it to the shape worth seeing. Only positive
forms count, because absence answers a negative comparison and a presence test —
which is how this corpus spells "not in that mode". And only variables the
activity's own checkpoints or set actions bind count, so a technique output a
no-agent walker was never going to produce stays out.

On the work-package matrix that is 20 entries on start-work-package and none on
the other eleven activities. Twelve name the issue platform, which is the defect
this branch fixes; eight are the review-mode flag, a boolean with no declared
default whose absence its readers take as false.

This makes the class visible in review and fails the snapshot when it changes.
It is not yet an assertion: a walk cannot reach zero here while technique
outputs stay unbound.
@m2ux
m2ux merged commit 1297e65 into main Aug 18, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant