Repository navigation
authority(0.7.1g1P2-3c2): T3/T2/T8 mapped, T9 pinned to the chain it actually reaches - #478
Conversation
…actually reaches Step 3c2 proof closure for T2, T3, T8 and T9. Audit applied the rule that a rule-id hit is not coverage: a row closes only when a test reproduces the canonical scenario with the correct initial authority state, the specific perturbation, the intended rule and the intended outcome. ALREADY CLOSED, mapped rather than duplicated: - T3: MutationPopulationAdmissionCeremonyTest "authorized identity with a different raw status fails M32" is §7's scenario field for field - the authorized row, its own raw status, M32, with the M32 message asserted verbatim. - T2: "T2 an outcome change changes the authority projection" (digest sensitivity) plus "authorized row with a different population digest fails M34" (M34 on a mismatch) close both halves of the row. - T8: "authorized row measured under different analyzer semantics fails M33" (refusal, digest minted against the same candidate so no earlier predicate refuses) plus "T8 analyzer semantics change the authority projection" close both halves. Two T-numbered tests I wrote first turned out to duplicate that pre-existing coverage, so they were REMOVED rather than shipped as T-labelled duplicates. Net diff is one file, one test. Method correction worth recording: my first audit pass searched test bodies for rule ids and could not see "T2 an outcome change..." or "T8 analyzer semantics change...", because neither contains one. Naming-based T-row matching is required alongside rule-id matching. T9 - the finding. Section 7 maps T9 to "M32 fail". M32 cannot fire for this attack and the implementation is not at fault: MutationPopulationAdmission.admitsRow compares identity, status, outcome, family and module, but a mutant's identity is a hash over the row's coordinates, so re-homing an authorized row to another family produces a DIFFERENT identity. The same-identity/different-row case that is M32's only precondition is unachievable, so the premise does not hold. Measured with a temporary probe, the transition actually reaches: M06: the moved row is a NEW NON_KILLED identity absent from the base authority M37: the authorization that admitted the old identity disappears unconsumed The re-homing is therefore refused - what the row's OUTCOME requires - with a different named rule. The new test pins that chain and its KDoc states the divergence, so the row is never counted from a rule-id hit. No production change is proposed: the transition is refused, and the family/module terms in admitsRow remain correct defence in depth. Verification: focused suites 180 tests / 0 failures (T9 new; T2/T8 pre-existing); spotless clean; Detekt baseline 4792 -> 4792 with 0 added; verifyChangePolicy PASSED against base 514f61a, class build-logic; exactly 1 file modified. Authority invariants: 67 historical admissions untouched, P1M certificate untouched, no new AdmissionAuthority production site, no new Valid construction path, M36/M37 unchanged, M34 unchanged, T7 unchanged, T10 not started, no production change.
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Although the single test is mechanically verified to match production behavior, it deliberately reinterprets the §7 authority matrix (M32 → M06+M37), a governance-semantics judgment that warrants human ratification.
Review effort: Balanced
Findings: None
What changed in this PR
This PR adds a single test to MutationPopulationAdmissionCeremonyTest to close row T9 of the §7 authority matrix (family/module re-homing of an authorized mutation identity). The §7 spec maps T9 to an M32 failure, but the PR argues—and the test pins—that M32 is structurally unreachable: because a mutant's identity is a hash over its coordinates (family is encoded in the class name, plus module), re-homing produces a different identity, so the same-identity precondition admitsRow requires never holds. The test instead asserts the chain the transition actually reaches: the moved row surfaces as a new survivor (M06/MUTATION_RATCHET_NEW_SURVIVOR) while the old authorization is orphaned without consumption (M37/MUTATION_RATCHET_ADMISSION_INVALID). The outcome (refusal) still matches §7; only the named rule differs, and the KDoc documents this divergence so the row is never counted from a rule-id hit. There is no production, workflow, baseline, or suppression change.
Changes:
- Added test
T9 re-homing an authorized identity produces the moved identity and orphaned custodyasserting theM06+M37refusal chain. - Added a KDoc block explaining why the §7
M32mapping is structurally unachievable for this attack.
| File | Description |
|---|---|
| build-logic/src/test/kotlin/dev/tramai/build/quality/MutationPopulationAdmissionCeremonyTest.kt | Adds the T9 re-homing test (and documenting KDoc) that pins the real M06/M37 refusal chain instead of the spec's nominal M32. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Review hold on #478: T9 is not yet proven, because the new test changes more than the canonical T9 premise. Production identity schema v2 hashes: module, className, method, methodDescription, mutator, description, block, index. It does not hash But
changes both the row's A family-only re-homing is structurally possible under identity schema v2: keep the original identity/module/className/method/etc. and change only Please correct T9 by constructing the candidate row from the authorized row and changing only the canonical T9 field(s) while preserving every identity field. For a family-only case, conceptually:
with coherent candidate family topology/counters. Then assert whether M32 fires as §7 specifies. If the canonical row explicitly requires module re-homing as well, split the two dimensions rather than treating them as equivalent: module is identity-bearing, family is not. Do not change production code unless the corrected canonical discriminator proves a real defect. Separately, the 10 |
…ly-only re-homing fails M32
The first T9 test was testing the wrong transition. MutationRatchetTestSupport.row() derives BOTH
identity = identityOf(marker, family, module) and className = "dev.tramai.$family.Policy" from the
family argument, so row("target", family = retryFamily) changed an identity-bearing field and minted
a new mutant. It tested a className change wearing a T9 label, and the M06/M37 chain it reported was
evidence about that transition, not the canonical one. The "M32 cannot fire" conclusion was a fixture
artifact and is withdrawn.
MutationIdentity.stableKey() hashes module, className, method, descriptor, mutator, description,
block and index: family is NOT identity-bearing, module IS. Section 7 fuses the two, so the corrected
test separates them.
family - non-identity-bearing. The re-homing is applied to an existing row via
row("target").copy(family = retryFamily), so className and every other stableKey input survive. The
candidate topology is kept coherent with baseFamilies + (retryFamily to retryTarget); the earlier
"families [policy, retry] disagree with the governing targetFamilies [policy]" diagnostic was exactly
that gap. Identity preservation is asserted as an explicit precondition, so this test can never again
silently drift into a className discriminator. Result: M32, with the M32 message - section 7 as
written. No production defect.
module - identity-bearing, therefore a different attack. It cannot reach M32, because the mutant that
arrives is a new identity: refused as a new survivor (M06) while the authorization for the old
identity is orphaned (M37). Pinned separately so neither case can be read as evidence for the other.
Verification: MutationPopulationAdmissionCeremonyTest 25 tests / 0 failures at this head. Production
untouched.
|
T9 corrected. You were right, and the diagnosis was exact.
What the corrected test does now:
Local result at head: On the 10 |
|
Base reproduction recorded — same signature, so the 10 are pre-existing. Correction first: my initial attempt proved nothing. I filtered Corrected run — clean clone, pristine base, serial, nothing else running: The candidate run at the task base failed the same 10 tests in the same class, and every name captured from that log ( Caveat I am not hiding: I captured the candidate-side names from the run log, not from its XML, because that run's results were later overwritten by a focused This closes the last open evidence question on my side. The other five local failures already had stronger explanations ( |
|
The corrected T9 family-only discriminator is now structurally sound and resolves the review blocker: it preserves the exact identity-bearing fields, changes only One small test-precision follow-up before merge: the module-move half's KDoc/comment says it pins
No production change needed. The canonical T9 closure itself (family-only re-homing -> same identity -> M32) is now correct. The 10 ReleaseVerificationPluginTest failures are also now properly base-reproduced at pristine |
…move assertion The module half of the T9 test asserted only MUTATION_RATCHET_ADMISSION_INVALID, which is broader than the M37 the KDoc claims. Pin the named rule: the orphaned authorization must fail M37, with the M37 message, so the assertion matches its own documentation and M37 cannot silently degrade into a sibling rule under the same code. The canonical family-only half is unchanged: identity preserved, family changed, M32 reached. MutationPopulationAdmissionCeremonyTest: 25 tests / 0 failures. spotless, static analysis and verifyChangePolicy (base 514f61a) all pass. Production untouched.
|
M37 pin done at head Supplemental module assertion now pins M37 exactly ( Local at this head: Exact-head CI, confirmed per workflow by headSha rather than by tally: PR check summary: 19 pass, no non-pass. Ready for your review and merge — not merged by me. |
a1ce28c
into
epic/0.7.1-control-plane-authority
Base
514f61a9a19f5778b58f65ddcf151e4878e5b433— the post-#476 epic tip (#476 squash-merged; branch cut from this tip, tree clean).Scope
One test file, one test added.
MutationPopulationAdmissionCeremonyTest+47 lines. No production change, no workflow change, no baseline, no suppression.Audit — what already closed each row, and what did not
Coverage rule applied: a rule-id hit is not coverage. A row closes only when a test reproduces the canonical scenario, with the correct initial authority state, the specific perturbation, the intended rule and the intended outcome.
NON_KILLED→KILLEDMutationPopulationEvolutionProofTest"T2 an outcome change changes the authority projection" (digest sensitivity) +MutationPopulationAdmissionCeremonyTest"authorized row with a different population digest fails M34" (M34 on a digest mismatch)MutationPopulationAdmissionCeremonyTest"authorized identity with a different raw status fails M32" — authorized row, own raw status, M32, with the M32 message asserted verbatimTwo rows I initially tested turned out to be duplicates of pre-existing coverage, so I removed those tests rather than keep T-numbered duplicates, per the task's mapping rule. The net diff is smaller than my first attempt, and the mapping above is the evidence.
Audit method correction worth recording: my first pass searched test bodies for
Mxxstrings and could not seeT2 an outcome change…/T8 analyzer semantics change…, because those tests contain no rule id. Naming-based T-row matching is required alongside rule-id matching.T9 — the finding (CORRECTED)
My first T9 test was testing the wrong transition, and the reviewer caught it. The fixture called
row("target", family = retryFamily), andMutationRatchetTestSupport.row()derives bothidentity = identityOf(marker, family, module)andclassName = "dev.tramai.$family.Policy"from thefamily. So that perturbation changed an identity-bearing field (className) and simply minted a new
mutant — it never re-homed an existing identity, and it could not have reached M32 by construction. The
M06/M37 chain I reported was real but it was evidence about a className change, not about T9.
MutationIdentity.stableKey()hashes module, className, method, descriptor, mutator, description, blockand index. family is not identity-bearing; module is. §7 fuses the two, so the corrected test
separates them:
row("target").copy(family = retryFamily), with the candidate topologykept coherent (
baseFamilies + (retryFamily to retryTarget)) and the identity-preservation asserted asan explicit precondition. The identity genuinely survives, so the only rule that can refuse the row is
the persisted-row binding, and it does: M32, with the M32 message, exactly as §7 expects.
arrives is a new identity: refused as a new survivor (M06) while the authorization for the old
identity is orphaned (M37). Pinned separately so neither case can be read as evidence for the other.
No production defect, and no production change. M32 is reachable for the canonical attack; my earlier
"M32 cannot fire" claim was a fixture artifact and is withdrawn. The identity-preservation assertion is
deliberate: it makes the fixture self-checking, so this test can never again silently drift into a
className discriminator.
Verification
Authority invariants
67 historical admissions modified: 0 · P1M certificate modified: false · new
AdmissionAuthorityproduction sites: 0 · newValidconstruction paths: 0 · M36/M37 semantics changed: false · M34 weakened: false · T7 modified: false · T10 started: false · production changes: none.