fix(preflight): scope seeded-fault checks by the request each leg issues - #85
Merged
Merged
Conversation
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
|
Warning Review limit reachedNext included review available in 5 minutes. View limit detailsLimit 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. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (4)
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. Comment |
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
force-pushed
the
fix/seeded-faults-scoped-identical-legs
branch
from
September 9, 2026 10:13
4d0afe6 to
d37aef5
Compare
…ped-identical-legs # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
seeded-faults-scopedbuilt 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.
requestSignatureinsrc/core/preflight/plan.tsserialises the wholeProbeRequestin RFC 8785 form withprobeIdneutralised, and every planned leg's signature is recorded asaddLegbuilds it, so a field added toProbeRequestlater 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-resetasserts 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: aProbeRequestnames a logicalinterfaceId(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 whilelist-bstays; fixture 127 (reduce) pins that a witness firing onlist-aleaves the check satisfied; fixture 128 pins the mutating bound. Fixture 59 now patcheslist-band still fails, which is the genuinely different clean leg. All three new fixtures fail againstmainand pass here.Also in this PR:
tests/preflight/reduce.test.tscarried two fixtures numbered 124, which makes the story's "Fixture 124" ambiguous. The later one is renumbered 125.npm run validategreen: 116 files, 3757 tests.