Skip to content

fix(preflight): scope seeded-fault checks by the request each leg issues - #85

Merged
muratkeremozcan merged 6 commits into
mainfrom
fix/seeded-faults-scoped-identical-legs
Sep 9, 2026
Merged

fix(preflight): scope seeded-fault checks by the request each leg issues#85
muratkeremozcan merged 6 commits into
mainfrom
fix/seeded-faults-scoped-identical-legs

Conversation

@muratkeremozcan

Copy link
Copy Markdown
Collaborator

seeded-faults-scoped built its clean-leg set by excluding the fault leg's id, so a leg that carried a request identical to the fault leg's stayed in the set. The environment answers the two the same way, the manifestation witness fires on the clean leg, and the check reported a scoping violation for one observation counted twice.

The clean-leg filter now compares built requests. requestSignature in src/core/preflight/plan.ts serialises the whole ProbeRequest in RFC 8785 form with probeId neutralised, and every planned leg's signature is recorded as addLeg builds it, so a field added to ProbeRequest later joins the comparison with no edit at the call site and key order never reads as a difference.

Narrowing: the exclusion applies only to an operation AD-19 marks as changing no state. There one request has one answer for the length of the run, which is the property state-reset asserts over the same fixture. A mutating operation can answer the same request differently at two points in the sequence, so both legs are two events and both stay in the clean set. Targets need no narrowing: a ProbeRequest names a logical interfaceId (AD-35) and the clean-leg group is already scoped to one interface and operation, so an identical request reaches an identical target.

Tests: fixture 126 (plan) pins that list-a, which carries the fault leg's request byte for byte, is dropped while list-b stays; fixture 127 (reduce) pins that a witness firing on list-a leaves the check satisfied; fixture 128 pins the mutating bound. Fixture 59 now patches list-b and still fails, which is the genuinely different clean leg. All three new fixtures fail against main and pass here.

Also in this PR: tests/preflight/reduce.test.ts carried two fixtures numbered 124, which makes the story's "Fixture 124" ambiguous. The later one is renumbered 125.

npm run validate green: 116 files, 3757 tests.

The clean-leg set of a `seeded-faults-scoped` check excluded the fault leg
by identity alone. Another leg of the same operation, most often a
sensitivity leg, can carry a request equal to the fault leg's. The
environment answers both the same way, the manifestation witness fires on
the clean leg, and the check reported a scoping violation for one
observation counted twice.

A leg whose built request equals the fault leg's is now dropped from the
set. The comparison runs over the whole `ProbeRequest` in RFC 8785 form
with `probeId` neutralised, so a field added to the request later joins the
comparison with no edit at the call site, and key order never reads as a
difference.

Bounded to an operation AD-19 marks as changing no state, where one request
has one answer for the length of the run. A mutating operation can answer
the same request differently at two points in the sequence, so both legs
stay in the set there.

Fixture 59 now patches `list-b`, whose request differs from the fault
leg's, and still fails. Fixtures 126, 127 and 128 cover the
identical-request case at plan and reduce level and the mutating bound. The
second fixture numbered 124 in `tests/preflight/reduce.test.ts` is
renumbered 125.

Claude-Session: https://claude.ai/code/session_01P4Adb9SkVtVbKCWPiV1Uwc
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 5 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 2ca18b78-6eea-40cd-8b76-536be2c04e46

📥 Commits

Reviewing files that changed from the base of the PR and between fe0bae8 and 549c5db.

⛔ Files ignored due to path filters (3)
  • CHANGELOG.md is excluded by none and included by none
  • _bmad-output/implementation-artifacts/6-2-pre-flight-as-plan-observation-and-pure-verdict.md is excluded by !_bmad-output/** and included by none
  • _bmad-output/project-knowledge/learning-path-step-by-step.md is excluded by !_bmad-output/** and included by none
📒 Files selected for processing (4)
  • docs/reference/glossary.md
  • src/core/preflight/reduce.ts
  • tests/preflight/fixtures/observations.ts
  • tests/preflight/reduce.test.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Step 20's rules list said what the two seeded-fault checks read and left
"clean leg" as every other leg of the operation. A reader who assumes the
set is built from leg identity alone misreads both the shipped behaviour
and a mutating operation's result, so the rule and its `stateChangeMarker`
bound are stated there.

AC 11's `seeded-faults-scoped` row in story 6.2 carries an amendment note
in the ADR-003 shape: the original row stays readable and the note says
what superseded it.

Step 27 is untouched. It covers AD-40's signature, qualification, and the
witness match, and states no rule about pre-flight's clean-leg set.

Claude-Session: https://claude.ai/code/session_01P4Adb9SkVtVbKCWPiV1Uwc
…ean leg

An empty `cleanLegIds` resolved `satisfied`, so a defect whose operation
carried no other leg certified its own scoping from no observation. That is
the vacuity the file rejects everywhere else: `interface-present` is emitted
only for an operation with a leg, and a sensitivity relation resolving
`insufficient-evidence` fails. The check now fails on an empty set and the
note names the cause, since an operation with no other leg and an operation
whose every leg carries the fault leg's request are different authoring
mistakes. The check carries `droppedLegIds` so the reducer can tell them
apart. `docs/reference/glossary.md` documented the old answer and is updated.

The request-equality filter widened the reach of that hole. AD-10's
exemption case reaches it exactly: one keyless safe read, whose two
control-observe legs both send the empty inputs the operation admits, and a
defect seeded there matches every leg its operation has.

`serialize` is not total over `JsonValue`. An integer outside the safe
range, a lone surrogate, or nesting past AD-36's depth raises
`non-canonicalizable-value`, and a 64-bit id in a query parameter is
ordinary. Signing every leg eagerly turned that into a thrown fault from a
stage that had only ever raised `StructuralFailure`. Signatures are now
taken only for the legs of an operation carrying a seeded defect, and a
request that cannot be canonicalised has no signature, matches nothing, and
keeps its leg in the clean-leg set.

The justification for the `stateChangeMarker` bound claimed `state-reset`
established that one request has one answer for the length of the run. It
does not: it compares the two control-observe legs alone, on one operation,
and `runPreflight` issues legs in plan order, which puts mutating legs
between reads of a different operation. The bound is caution, its cost is
live, and the comment, the changelog, the story, and the learning path all
say so now.

Fixtures 130 and 131 cover the plan side, 132 and 133 the reducer. The
second fixture numbered 123 in `tests/preflight/reduce.test.ts` is
renumbered 129.

Claude-Session: https://claude.ai/code/session_01P4Adb9SkVtVbKCWPiV1Uwc
…ped-identical-legs

# Conflicts:
#	CHANGELOG.md
The clean-leg drop moves from `planPreflight` to `reducePreflight`, where
both legs' answers are in hand. A clean leg is dropped when it issued the
fault leg's request and received the fault leg's answer, which makes it the
fault leg's own probe under a second label.

Both halves are required. Answers alone would drop AD-10's own worked
example, two distinct nonexistent identifiers both returning 404, which are
exactly the legs this check exists to read. Requests alone are what the plan
could see, and identical requests can still be answered differently.

The answer half compares the leg's evidence, which is everything a relation
can address. It carries AD-11's projected body, so a field the operation
declares volatile is already out of it and a server-minted identifier stops
being a difference. That is what makes the same request to a mutating
operation comparable, and it closes the case the plan-side version had to
leave open: a defect seeded on `create-thing` whose witness posts a
sensitivity leg's body now passes when the two legs were answered alike, and
fails when they were not.

Emptiness is tested on the set that survives the drop, since the drop is
what can empty it. The note names the cause: the operation has no leg
besides the fault leg, or every leg the plan named ran the fault leg's probe.

`digestArtifact` raises `non-canonicalizable-value` on an integer outside
the safe range or a lone surrogate, both of which `JsonValue` admits. It is
caught and read as no digest, which matches nothing, so a pair that cannot
be compared stays a pair the check reads and the stage still returns a
verdict.

`plan.ts` and its tests are back to what main carries. Fixtures 126, 127,
128, 130, 131, 132, and 133 all sit at reduce level now.

Claude-Session: https://claude.ai/code/session_01P4Adb9SkVtVbKCWPiV1Uwc
@muratkeremozcan
muratkeremozcan force-pushed the fix/seeded-faults-scoped-identical-legs branch from 4d0afe6 to d37aef5 Compare September 9, 2026 10:13
…ped-identical-legs

# Conflicts:
#	CHANGELOG.md
@muratkeremozcan
muratkeremozcan merged commit 8483334 into main Sep 9, 2026
22 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