From f83f0f243bf97c133bcba07aba4b87179e48a0b5 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 1 Aug 2026 10:20:41 +0000 Subject: [PATCH 1/8] WIP: CI admission safety: exact-head evidence coverage, then heal revalidatio --- dag/gunbc/merge_admission.dag | 15 +++ .../merge_admission_attempt_witness_test.dag | 52 ++++++++ dag/tools/merge_admission_current_context.dag | 124 ++++++++++++++++++ dag/tools/merge_admission_gate.dag | 14 +- 4 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 dag/tools/merge_admission_current_context.dag diff --git a/dag/gunbc/merge_admission.dag b/dag/gunbc/merge_admission.dag index 55f0acc82aa..6874c12a17e 100644 --- a/dag/gunbc/merge_admission.dag +++ b/dag/gunbc/merge_admission.dag @@ -496,6 +496,21 @@ fn classify_merge_admission_verdict_v2( } } +fn merge_admission_verdict_label(v: MergeAdmissionVerdict) -> String { + match v { + MergeAdmitted => "admitted" + MergeDeniedStaleBase => "stale base tree — re-run CI against current main" + MergeDeniedStaleRoster => "stale gate roster — re-run CI after criteria change" + MergeDeniedNotSuccess => "receipt conclusion is not success" + MergeDeniedWrongAttempt => "receipt belongs to a different walk attempt — not the subject; re-run CI" + MergeDeniedSubjectMismatch => "receipt describes a different head or base tree than this attempt captured — not the subject; re-run CI" + } +} + +data merge_admission_current_context_consumer_note: String = "LIVE CONSUMER, NOT YET A MERGE ACTUATOR (incident 2026-08-01). tools.merge_admission_current_context fetches the current merge target, reads the attempt-scoped TestedSubject and MergeAdmissionReceiptV2 wires, and consumes classify_merge_admission_verdict_v2 against current_gate_roster_hash. This closes the modeled-without-a-wet-consumer gap: a receipt evaluated before PublicationPlacementGate joined commit_gate_roster classifies MergeDeniedStaleRoster against the post-cutover roster, while a receipt minted under the current roster classifies MergeAdmitted. The operator currently merges manually, so this entry is not described as enforcement: a correctly displayed verdict that a human may ignore is a warning, not a wall." + +data merge_admission_required_check_binding_dissolution_trigger: String = "DISSOLVE-ON operator selects the merge-time required-check binding for tools.merge_admission_current_context.main. Until that binding exists, the entry produces the typed current-context verdict but does not claim to prevent a manual merge. The later policy-content increment is already declared by gate_content_hash_variant_tag_scaffold: replacing gate_content_hash's variant-tag placeholder fires that SingleAuthority dissolution; it is not part of this consumer increment." + fn merge_admission_verdict_would_block(v: MergeAdmissionVerdict) -> Bool { match v { MergeAdmitted => false diff --git a/dag/test/claim/merge_admission_attempt_witness_test.dag b/dag/test/claim/merge_admission_attempt_witness_test.dag index 198d813483a..949c8129ca4 100644 --- a/dag/test/claim/merge_admission_attempt_witness_test.dag +++ b/dag/test/claim/merge_admission_attempt_witness_test.dag @@ -17,7 +17,10 @@ import gunbc.merge_admission { MergeDeniedSubjectMismatch, classify_merge_admission_verdict_v2, merge_admission_verdict_would_block, + gate_roster_content_hash, + current_gate_roster_hash, } +import gunbc.commit_workflow { commit_gate_roster } import gunbc.merge_admission_produce { merge_admission_receipt_schema_v2, merge_admission_tested_subject_schema, @@ -96,6 +99,55 @@ test fn matching_attempt_and_subject_with_fresh_base_admits() -> Bool { ) { MergeAdmitted => true _ => false } } +fn publication_pre_activation_roster_hash() -> ContentHash { + gate_roster_content_hash( + roster: concat(commit_gate_roster.take(n: 5), commit_gate_roster.skip(n: 6)) + ) +} + +test fn pre_publication_wall_receipt_refuses_against_live_roster() -> Bool { + let receipt = MergeAdmissionReceiptV2 { + attempt_id: attempt_a(), + pr_number: Present { value: 7544 }, + tested_head_sha: head_sha_fx, + tested_base_tree: base_tree_fx(), + gate_roster_hash: publication_pre_activation_roster_hash(), + conclusion: Success, + } + (publication_pre_activation_roster_hash() != current_gate_roster_hash) + && match classify_merge_admission_verdict_v2( + current_attempt_id: attempt_a(), + tested_subject: subject_fx(attempt: attempt_a(), tree: base_tree_fx(), head: head_sha_fx), + receipt: receipt, + merge_target_tree: base_tree_fx(), + current_roster_hash: current_gate_roster_hash, + ) { + MergeDeniedStaleRoster => true + _ => false + } +} + +test fn current_publication_wall_receipt_admits_against_live_roster() -> Bool { + let receipt = MergeAdmissionReceiptV2 { + attempt_id: attempt_a(), + pr_number: Present { value: 7544 }, + tested_head_sha: head_sha_fx, + tested_base_tree: base_tree_fx(), + gate_roster_hash: current_gate_roster_hash, + conclusion: Success, + } + match classify_merge_admission_verdict_v2( + current_attempt_id: attempt_a(), + tested_subject: subject_fx(attempt: attempt_a(), tree: base_tree_fx(), head: head_sha_fx), + receipt: receipt, + merge_target_tree: base_tree_fx(), + current_roster_hash: current_gate_roster_hash, + ) { + MergeAdmitted => true + _ => false + } +} + test fn foreign_attempt_refuses_as_wrong_attempt() -> Bool { let v = classify_merge_admission_verdict_v2( current_attempt_id: attempt_a(), diff --git a/dag/tools/merge_admission_current_context.dag b/dag/tools/merge_admission_current_context.dag new file mode 100644 index 00000000000..ce58d3a4491 --- /dev/null +++ b/dag/tools/merge_admission_current_context.dag @@ -0,0 +1,124 @@ +module tools.merge_admission_current_context + +import std.process { ProcessExit, ExitSuccess, exit_failure } +import std.types { NonEmptyStr, GitRef } +import extdeps.git +import extdeps.git.inspect +import extdeps.git.object_store { + GitObjectIdTextDecoded, + GitObjectIdTextRefused, + git_decode_object_id_text, + git_object_id_format, +} +import extdeps.filesystem.filesystem_io { Filesystem } +import extdeps.shell +import gunbc.merge_admission { + WalkAttemptId, + MergeAdmitted, + classify_merge_admission_verdict_v2, + current_gate_roster_hash, + merge_admission_verdict_label, +} +import gunbc.merge_admission_produce { + compose_walk_attempt_id, + compose_explicit_walk_attempt_id, + merge_admission_tested_subject_path, + merge_admission_floor_receipt_path, + parse_tested_subject_wire, + parse_receipt_wire_v2, + merge_admission_merge_base_ref, + merge_admission_gate_fetch_parts, + shell_stdout_line, +} + +fn optional_env(name: NonEmptyStr) -> String? { + let observed = shell.Env.Get(name: name) + match observed.value { + Present { value: raw } => + let value = trim(raw) + if value == "" { none } else { Present { value: value } } + Absent => none + } +} + +fn current_walk_attempt_id() -> WalkAttemptId? { + match optional_env(name: "GUNBC_WALK_ATTEMPT_ID") { + Present { value: explicit } => compose_explicit_walk_attempt_id(explicit: explicit) + Absent => + compose_walk_attempt_id( + run_id: match optional_env(name: "GITHUB_RUN_ID") { + Present { value: value } => value + Absent => "" + }, + run_attempt: match optional_env(name: "GITHUB_RUN_ATTEMPT") { + Present { value: value } => value + Absent => "" + }, + job: match optional_env(name: "GITHUB_JOB") { + Present { value: value } => value + Absent => "" + }, + ) + } +} + +func main() -> ProcessExit { + let attempt = match current_walk_attempt_id() { + Present { value: value } => value + Absent => return exit_failure(reason: "merge_admission_current_context: walk attempt identity absent or invalid") + } + let root = git.Inspect.Toplevel().path as String + let subject_read = Filesystem.Read( + path: merge_admission_tested_subject_path(root: root, attempt_id: attempt) + ) + if !subject_read.success { + return exit_failure(reason: "merge_admission_current_context: tested-subject wire missing") + } + let subject = match parse_tested_subject_wire(text: subject_read.content) { + Present { value: value } => value + Absent => return exit_failure(reason: "merge_admission_current_context: tested-subject wire invalid") + } + let receipt_read = Filesystem.Read( + path: merge_admission_floor_receipt_path(root: root, attempt_id: attempt) + ) + if !receipt_read.success { + return exit_failure(reason: "merge_admission_current_context: floor-receipt wire missing") + } + let receipt = match parse_receipt_wire_v2(text: receipt_read.content) { + Present { value: value } => value + Absent => return exit_failure(reason: "merge_admission_current_context: floor-receipt wire invalid") + } + let fetch_parts = merge_admission_gate_fetch_parts() + let fetched = git.Core.FetchNoTags(remote: fetch_parts.remote, ref: fetch_parts.ref_name) + if !fetched.success { + return exit_failure(reason: "merge_admission_current_context: current merge target fetch refused") + } + let observed = git.Inspect.ShowTree(ref: merge_admission_merge_base_ref as GitRef) + if !observed.success { + return exit_failure(reason: "merge_admission_current_context: current merge target tree observation refused") + } + let target = match git_decode_object_id_text( + format: git_object_id_format(oid: receipt.tested_base_tree), + text: shell_stdout_line(raw: observed.tree), + ) { + GitObjectIdTextDecoded { oid: oid } => oid + GitObjectIdTextRefused { cause: _ } => + return exit_failure(reason: "merge_admission_current_context: current merge target object id refused") + } + let verdict = classify_merge_admission_verdict_v2( + current_attempt_id: attempt, + tested_subject: subject, + receipt: receipt, + merge_target_tree: target, + current_roster_hash: current_gate_roster_hash, + ) + match verdict { + MergeAdmitted => ExitSuccess + _ => exit_failure( + reason: concat( + "merge_admission_current_context: blocked (", + concat(merge_admission_verdict_label(v: verdict), ")") + ) + ) + } +} diff --git a/dag/tools/merge_admission_gate.dag b/dag/tools/merge_admission_gate.dag index 237800ae9b9..ba1f227e560 100644 --- a/dag/tools/merge_admission_gate.dag +++ b/dag/tools/merge_admission_gate.dag @@ -8,6 +8,7 @@ import extdeps.filesystem.filesystem_io { Filesystem } import gunbc.merge_admission { current_gate_roster_hash, classify_merge_admission_verdict, + merge_admission_verdict_label, MergeAdmissionVerdict, MergeDeniedStaleBase, MergeDeniedStaleRoster, @@ -27,17 +28,6 @@ import gunbc.merge_admission_produce { } import gunbc.ci_failure_class { merge_admission_blocks_merge } -fn verdict_reason(v: MergeAdmissionVerdict) -> String { - match v { - MergeAdmitted => "admitted" - MergeDeniedStaleBase => "stale base tree — re-run CI against current main" - MergeDeniedStaleRoster => "stale gate roster — re-run CI after criteria change" - MergeDeniedNotSuccess => "receipt conclusion is not success" - MergeDeniedWrongAttempt => "receipt belongs to a different walk attempt — not the subject; re-run CI" - MergeDeniedSubjectMismatch => "receipt describes a different head or base tree than this attempt captured — not the subject; re-run CI" - } -} - func main() -> ProcessExit { let root = git.Inspect.Toplevel().path as String let path = merge_admission_receipt_path(root: root) @@ -77,7 +67,7 @@ func main() -> ProcessExit { exit_failure( reason: concat( "merge_admission_gate: blocked (", - concat(verdict_reason(v: verdict), ")") + concat(merge_admission_verdict_label(v: verdict), ")") ) ) } else { From fc51be60d06bec6f5e63eded03020b034bdbbab4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 1 Aug 2026 10:34:18 +0000 Subject: [PATCH 2/8] Make admission context classification exhaustive --- .../merge_admission_attempt_witness_test.dag | 34 ++++++++++++++++--- dag/tools/merge_admission_current_context.dag | 26 ++++++++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/dag/test/claim/merge_admission_attempt_witness_test.dag b/dag/test/claim/merge_admission_attempt_witness_test.dag index 949c8129ca4..8c6cf680086 100644 --- a/dag/test/claim/merge_admission_attempt_witness_test.dag +++ b/dag/test/claim/merge_admission_attempt_witness_test.dag @@ -20,7 +20,14 @@ import gunbc.merge_admission { gate_roster_content_hash, current_gate_roster_hash, } -import gunbc.commit_workflow { commit_gate_roster } +import gunbc.commit_workflow { + commit_gate_roster, + CommitSpecGate, + CommitWitnessClaim, + CommitCargoFmtCheck, + gate_eq, +} +import gunbc.ci_gate { PublicationPlacementGate } import gunbc.merge_admission_produce { merge_admission_receipt_schema_v2, merge_admission_tested_subject_schema, @@ -101,7 +108,18 @@ test fn matching_attempt_and_subject_with_fresh_base_admits() -> Bool { fn publication_pre_activation_roster_hash() -> ContentHash { gate_roster_content_hash( - roster: concat(commit_gate_roster.take(n: 5), commit_gate_roster.skip(n: 6)) + roster: fold(commit_gate_roster, init: [], f: fn(acc, row) { + match row.check { + CommitSpecGate { gate: gate } => + if gate_eq(a: gate, b: PublicationPlacementGate) { + acc + } else { + concat(acc, [row]) + } + CommitWitnessClaim { entry: _, check_fns: _, kind: _, span: _ } => concat(acc, [row]) + CommitCargoFmtCheck => concat(acc, [row]) + } + }) ) } @@ -123,7 +141,11 @@ test fn pre_publication_wall_receipt_refuses_against_live_roster() -> Bool { current_roster_hash: current_gate_roster_hash, ) { MergeDeniedStaleRoster => true - _ => false + MergeAdmitted => false + MergeDeniedStaleBase => false + MergeDeniedNotSuccess => false + MergeDeniedWrongAttempt => false + MergeDeniedSubjectMismatch => false } } @@ -144,7 +166,11 @@ test fn current_publication_wall_receipt_admits_against_live_roster() -> Bool { current_roster_hash: current_gate_roster_hash, ) { MergeAdmitted => true - _ => false + MergeDeniedStaleBase => false + MergeDeniedStaleRoster => false + MergeDeniedNotSuccess => false + MergeDeniedWrongAttempt => false + MergeDeniedSubjectMismatch => false } } diff --git a/dag/tools/merge_admission_current_context.dag b/dag/tools/merge_admission_current_context.dag index ce58d3a4491..e885a8957a3 100644 --- a/dag/tools/merge_admission_current_context.dag +++ b/dag/tools/merge_admission_current_context.dag @@ -14,7 +14,13 @@ import extdeps.filesystem.filesystem_io { Filesystem } import extdeps.shell import gunbc.merge_admission { WalkAttemptId, + MergeAdmissionVerdict, MergeAdmitted, + MergeDeniedStaleBase, + MergeDeniedStaleRoster, + MergeDeniedNotSuccess, + MergeDeniedWrongAttempt, + MergeDeniedSubjectMismatch, classify_merge_admission_verdict_v2, current_gate_roster_hash, merge_admission_verdict_label, @@ -62,6 +68,15 @@ fn current_walk_attempt_id() -> WalkAttemptId? { } } +fn denied_exit(verdict: MergeAdmissionVerdict) -> ProcessExit { + exit_failure( + reason: concat( + "merge_admission_current_context: blocked (", + concat(merge_admission_verdict_label(v: verdict), ")") + ) + ) +} + func main() -> ProcessExit { let attempt = match current_walk_attempt_id() { Present { value: value } => value @@ -114,11 +129,10 @@ func main() -> ProcessExit { ) match verdict { MergeAdmitted => ExitSuccess - _ => exit_failure( - reason: concat( - "merge_admission_current_context: blocked (", - concat(merge_admission_verdict_label(v: verdict), ")") - ) - ) + MergeDeniedStaleBase => denied_exit(verdict: verdict) + MergeDeniedStaleRoster => denied_exit(verdict: verdict) + MergeDeniedNotSuccess => denied_exit(verdict: verdict) + MergeDeniedWrongAttempt => denied_exit(verdict: verdict) + MergeDeniedSubjectMismatch => denied_exit(verdict: verdict) } } From 14650f64f1942d5c2930d21be42e4da462bb427a Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 1 Aug 2026 10:47:19 +0000 Subject: [PATCH 3/8] Recut current-context admission as staged --- dag/gunbc/merge_admission.dag | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dag/gunbc/merge_admission.dag b/dag/gunbc/merge_admission.dag index 6874c12a17e..fe5ff8f2eb4 100644 --- a/dag/gunbc/merge_admission.dag +++ b/dag/gunbc/merge_admission.dag @@ -507,9 +507,11 @@ fn merge_admission_verdict_label(v: MergeAdmissionVerdict) -> String { } } -data merge_admission_current_context_consumer_note: String = "LIVE CONSUMER, NOT YET A MERGE ACTUATOR (incident 2026-08-01). tools.merge_admission_current_context fetches the current merge target, reads the attempt-scoped TestedSubject and MergeAdmissionReceiptV2 wires, and consumes classify_merge_admission_verdict_v2 against current_gate_roster_hash. This closes the modeled-without-a-wet-consumer gap: a receipt evaluated before PublicationPlacementGate joined commit_gate_roster classifies MergeDeniedStaleRoster against the post-cutover roster, while a receipt minted under the current roster classifies MergeAdmitted. The operator currently merges manually, so this entry is not described as enforcement: a correctly displayed verdict that a human may ignore is a warning, not a wall." +data merge_admission_current_context_staging_note: String = "STAGED INVOCABLE ENTRY, NOT A LIVE CONSUMER (review 46238, incident 2026-08-01). tools.merge_admission_current_context can fetch the current merge target, read attempt-scoped TestedSubject and MergeAdmissionReceiptV2 wires, and classify them against current_gate_roster_hash, but no production path invokes the entry and no production path mints either V2 wire. It therefore closes no runtime admission gap today. What IS delivered and executing is the discovery-corpus control pair: classify_merge_admission_verdict_v2 refuses a receipt evaluated before PublicationPlacementGate joined commit_gate_roster against the post-cutover roster, and admits a receipt minted under the current roster. A correctly computed but uninvoked verdict is staging, not a warning and not a wall." -data merge_admission_required_check_binding_dissolution_trigger: String = "DISSOLVE-ON operator selects the merge-time required-check binding for tools.merge_admission_current_context.main. Until that binding exists, the entry produces the typed current-context verdict but does not claim to prevent a manual merge. The later policy-content increment is already declared by gate_content_hash_variant_tag_scaffold: replacing gate_content_hash's variant-tag placeholder fires that SingleAuthority dissolution; it is not part of this consumer increment." +data merge_admission_v2_production_wires_dissolution_trigger: String = "FIRST DISSOLVE-ON: production captures TestedSubject before the floor, mints MergeAdmissionReceiptV2 after the floor, and persists both through merge_admission_tested_subject_path and merge_admission_floor_receipt_path for the same WalkAttemptId. Until both producers execute, tools.merge_admission_current_context has no production subject or evidence to consume and cannot be called live." + +data merge_admission_required_check_binding_dissolution_trigger: String = "SECOND DISSOLVE-ON, after merge_admission_v2_production_wires_dissolution_trigger: gunbc.ci_failure_class.merge_freshness_gating_status changes from GatingComputedDeferred to the existing GatingEnforced arm after operator sign-off and the ci_failure_class prerequisites named by gunbc.plans.merge_admission_gate_shape_proposal. That transition binds the V2 current-context verdict into the approved merge-time consumption path so a denial refuses rather than merely being invocable. The later policy-content increment is already declared by gate_content_hash_variant_tag_scaffold: replacing gate_content_hash's variant-tag placeholder fires that SingleAuthority dissolution; it is not part of this staged increment." fn merge_admission_verdict_would_block(v: MergeAdmissionVerdict) -> Bool { match v { From 85d0b06d93fa0e18f578a286f66471adb78fcb5f Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 1 Aug 2026 13:59:36 +0000 Subject: [PATCH 4/8] Grant publication for staged admission tool --- dag/gunbc/publication_grant.dag | 1 + 1 file changed, 1 insertion(+) diff --git a/dag/gunbc/publication_grant.dag b/dag/gunbc/publication_grant.dag index 0617a3559e0..ef13b35a7d1 100644 --- a/dag/gunbc/publication_grant.dag +++ b/dag/gunbc/publication_grant.dag @@ -87,6 +87,7 @@ data public_file_publish_grants: List = [ public_file_publish_grant(path: "dag/test/claim/tailscale_serve_status_witness_test.dag"), public_file_publish_grant(path: "dag/test/claim/workflow_dispatch_input_witness_test.dag"), public_file_publish_grant(path: "dag/tools/ci_heal_dispatch.dag"), + public_file_publish_grant(path: "dag/tools/merge_admission_current_context.dag"), public_file_publish_grant(path: "dag/tools/publication_placement_gate.dag"), public_file_publish_grant(path: "docs/plans/roadmap-presentation-seam-design.md"), public_file_publish_grant(path: "src/v2/workflow/ci_heal_revalidation_preflight_emit.dag"), From 23370a062ddf89410d4e9d0deb50c7e773b23c79 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 1 Aug 2026 17:36:15 +0000 Subject: [PATCH 5/8] Recut roster staleness control over synthetic delta --- .../merge_admission_attempt_witness_test.dag | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/dag/test/claim/merge_admission_attempt_witness_test.dag b/dag/test/claim/merge_admission_attempt_witness_test.dag index 8c6cf680086..6513328332f 100644 --- a/dag/test/claim/merge_admission_attempt_witness_test.dag +++ b/dag/test/claim/merge_admission_attempt_witness_test.dag @@ -22,12 +22,9 @@ import gunbc.merge_admission { } import gunbc.commit_workflow { commit_gate_roster, - CommitSpecGate, - CommitWitnessClaim, + CommitCheckEnrollment, CommitCargoFmtCheck, - gate_eq, } -import gunbc.ci_gate { PublicationPlacementGate } import gunbc.merge_admission_produce { merge_admission_receipt_schema_v2, merge_admission_tested_subject_schema, @@ -106,33 +103,27 @@ test fn matching_attempt_and_subject_with_fresh_base_admits() -> Bool { ) { MergeAdmitted => true _ => false } } -fn publication_pre_activation_roster_hash() -> ContentHash { +fn synthetic_changed_roster_hash() -> ContentHash { gate_roster_content_hash( - roster: fold(commit_gate_roster, init: [], f: fn(acc, row) { - match row.check { - CommitSpecGate { gate: gate } => - if gate_eq(a: gate, b: PublicationPlacementGate) { - acc - } else { - concat(acc, [row]) - } - CommitWitnessClaim { entry: _, check_fns: _, kind: _, span: _ } => concat(acc, [row]) - CommitCargoFmtCheck => concat(acc, [row]) - } - }) + roster: concat(commit_gate_roster, [CommitCheckEnrollment { + check: CommitCargoFmtCheck, + surfaces: [], + }]) ) } -test fn pre_publication_wall_receipt_refuses_against_live_roster() -> Bool { +data roster_staleness_control_recut_note: String = "RECUT AFTER #7591 INVALIDATED #7583. The 2026-08-01 incident motivates the roster-identity law, but this control does not reconstruct that incident by execution. Its stale roster is the live roster plus one synthetic, surface-less check enrollment, so the two hashes differ without naming any production gate or asserting any historical roster content. The prior control named PublicationPlacementGate; that coupled the regression control to one volatile roster member's continued existence, and the member's operator-ordered deletion invalidated the entire PR from a direction its original evidence did not watch. A control for a roster-identity law must exercise the SHAPE of a roster change, never a particular member, or its lifetime is bounded by the least stable thing it mentions. The stale arm must classify MergeDeniedStaleRoster and the live arm must classify MergeAdmitted, so an always-admit or always-refuse implementation fails one control." + +test fn synthetic_changed_roster_receipt_refuses_against_live_roster() -> Bool { let receipt = MergeAdmissionReceiptV2 { attempt_id: attempt_a(), pr_number: Present { value: 7544 }, tested_head_sha: head_sha_fx, tested_base_tree: base_tree_fx(), - gate_roster_hash: publication_pre_activation_roster_hash(), + gate_roster_hash: synthetic_changed_roster_hash(), conclusion: Success, } - (publication_pre_activation_roster_hash() != current_gate_roster_hash) + (synthetic_changed_roster_hash() != current_gate_roster_hash) && match classify_merge_admission_verdict_v2( current_attempt_id: attempt_a(), tested_subject: subject_fx(attempt: attempt_a(), tree: base_tree_fx(), head: head_sha_fx), @@ -149,7 +140,7 @@ test fn pre_publication_wall_receipt_refuses_against_live_roster() -> Bool { } } -test fn current_publication_wall_receipt_admits_against_live_roster() -> Bool { +test fn current_roster_receipt_admits_against_live_roster() -> Bool { let receipt = MergeAdmissionReceiptV2 { attempt_id: attempt_a(), pr_number: Present { value: 7544 }, From 43984a4e3ce188e6f0dd0554fd7bed1e2c4e9873 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 1 Aug 2026 18:13:58 +0000 Subject: [PATCH 6/8] Align staging note with synthetic roster control --- dag/gunbc/merge_admission.dag | 2 +- dag/test/claim/merge_admission_attempt_witness_test.dag | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dag/gunbc/merge_admission.dag b/dag/gunbc/merge_admission.dag index 4fda75a1062..27c37bc5160 100644 --- a/dag/gunbc/merge_admission.dag +++ b/dag/gunbc/merge_admission.dag @@ -506,7 +506,7 @@ fn merge_admission_verdict_label(v: MergeAdmissionVerdict) -> String { } } -data merge_admission_current_context_staging_note: String = "STAGED INVOCABLE ENTRY, NOT A LIVE CONSUMER (review 46238, incident 2026-08-01). tools.merge_admission_current_context can fetch the current merge target, read attempt-scoped TestedSubject and MergeAdmissionReceiptV2 wires, and classify them against current_gate_roster_hash, but no production path invokes the entry and no production path mints either V2 wire. It therefore closes no runtime admission gap today. What IS delivered and executing is the discovery-corpus control pair: classify_merge_admission_verdict_v2 refuses a receipt evaluated before PublicationPlacementGate joined commit_gate_roster against the post-cutover roster, and admits a receipt minted under the current roster. A correctly computed but uninvoked verdict is staging, not a warning and not a wall." +data merge_admission_current_context_staging_note: String = "STAGED INVOCABLE ENTRY, NOT A LIVE CONSUMER (review 46238, incident 2026-08-01). tools.merge_admission_current_context can fetch the current merge target, read attempt-scoped TestedSubject and MergeAdmissionReceiptV2 wires, and classify them against current_gate_roster_hash, but no production path invokes the entry and no production path mints either V2 wire. It therefore closes no runtime admission gap today. What IS delivered and executing is the discovery-corpus control pair: classify_merge_admission_verdict_v2 refuses a receipt carrying a synthetic changed-roster hash against the current roster, and admits a receipt minted under the current roster. A correctly computed but uninvoked verdict is staging, not a warning and not a wall." data merge_admission_v2_production_wires_dissolution_trigger: String = "FIRST DISSOLVE-ON: production captures TestedSubject before the floor, mints MergeAdmissionReceiptV2 after the floor, and persists both through merge_admission_tested_subject_path and merge_admission_floor_receipt_path for the same WalkAttemptId. Until both producers execute, tools.merge_admission_current_context has no production subject or evidence to consume and cannot be called live." diff --git a/dag/test/claim/merge_admission_attempt_witness_test.dag b/dag/test/claim/merge_admission_attempt_witness_test.dag index 6513328332f..789d86671b0 100644 --- a/dag/test/claim/merge_admission_attempt_witness_test.dag +++ b/dag/test/claim/merge_admission_attempt_witness_test.dag @@ -112,7 +112,7 @@ fn synthetic_changed_roster_hash() -> ContentHash { ) } -data roster_staleness_control_recut_note: String = "RECUT AFTER #7591 INVALIDATED #7583. The 2026-08-01 incident motivates the roster-identity law, but this control does not reconstruct that incident by execution. Its stale roster is the live roster plus one synthetic, surface-less check enrollment, so the two hashes differ without naming any production gate or asserting any historical roster content. The prior control named PublicationPlacementGate; that coupled the regression control to one volatile roster member's continued existence, and the member's operator-ordered deletion invalidated the entire PR from a direction its original evidence did not watch. A control for a roster-identity law must exercise the SHAPE of a roster change, never a particular member, or its lifetime is bounded by the least stable thing it mentions. The stale arm must classify MergeDeniedStaleRoster and the live arm must classify MergeAdmitted, so an always-admit or always-refuse implementation fails one control." +data roster_staleness_control_recut_note: String = "RECUT AFTER #7591 INVALIDATED #7583. The 2026-08-01 incident motivates the roster-identity law, but this control does not reconstruct that incident by execution. Its stale roster is the live roster plus one synthetic, surface-less check enrollment, so the two hashes differ without naming any production gate or asserting any historical roster content. The prior control named PublicationPlacementGate; that coupled the regression control to one volatile roster member's continued existence, and the member's operator-ordered deletion invalidated the entire PR from a direction its original evidence did not watch. A control for a roster-identity law must exercise the SHAPE of a roster change, never a particular member, or its lifetime is bounded by the least stable thing it mentions. A description of a control's shape must live with that control or derive from it, so an implementation change cannot leave a remote carrier describing evidence that no longer executes. The stale arm must classify MergeDeniedStaleRoster and the live arm must classify MergeAdmitted, so an always-admit or always-refuse implementation fails one control." test fn synthetic_changed_roster_receipt_refuses_against_live_roster() -> Bool { let receipt = MergeAdmissionReceiptV2 { From b9dc0d140dc8acf2af1372d630dd65eab8b68f6d Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 1 Aug 2026 20:34:55 +0000 Subject: [PATCH 7/8] Prove staleness with valid roster revision --- .../merge_admission_attempt_witness_test.dag | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/dag/test/claim/merge_admission_attempt_witness_test.dag b/dag/test/claim/merge_admission_attempt_witness_test.dag index 789d86671b0..033ea6bdfa2 100644 --- a/dag/test/claim/merge_admission_attempt_witness_test.dag +++ b/dag/test/claim/merge_admission_attempt_witness_test.dag @@ -22,8 +22,6 @@ import gunbc.merge_admission { } import gunbc.commit_workflow { commit_gate_roster, - CommitCheckEnrollment, - CommitCargoFmtCheck, } import gunbc.merge_admission_produce { merge_admission_receipt_schema_v2, @@ -104,18 +102,13 @@ test fn matching_attempt_and_subject_with_fresh_base_admits() -> Bool { } fn synthetic_changed_roster_hash() -> ContentHash { - gate_roster_content_hash( - roster: concat(commit_gate_roster, [CommitCheckEnrollment { - check: CommitCargoFmtCheck, - surfaces: [], - }]) - ) + gate_roster_content_hash(roster: reverse(commit_gate_roster)) } -data roster_staleness_control_recut_note: String = "RECUT AFTER #7591 INVALIDATED #7583. The 2026-08-01 incident motivates the roster-identity law, but this control does not reconstruct that incident by execution. Its stale roster is the live roster plus one synthetic, surface-less check enrollment, so the two hashes differ without naming any production gate or asserting any historical roster content. The prior control named PublicationPlacementGate; that coupled the regression control to one volatile roster member's continued existence, and the member's operator-ordered deletion invalidated the entire PR from a direction its original evidence did not watch. A control for a roster-identity law must exercise the SHAPE of a roster change, never a particular member, or its lifetime is bounded by the least stable thing it mentions. A description of a control's shape must live with that control or derive from it, so an implementation change cannot leave a remote carrier describing evidence that no longer executes. The stale arm must classify MergeDeniedStaleRoster and the live arm must classify MergeAdmitted, so an always-admit or always-refuse implementation fails one control." +data roster_staleness_control_recut_note: String = "RECUT AFTER #7591 INVALIDATED #7583. The 2026-08-01 incident motivates the roster-identity law, but this control does not reconstruct that incident by execution. Its alternative revision reverses the exact live enrollments: identical members and surfaces, still unique and otherwise valid, but with a different order-sensitive roster hash. It names no particular production gate and asserts no historical roster content. The prior control named PublicationPlacementGate; that coupled the regression control to one volatile roster member's continued existence, and the member's operator-ordered deletion invalidated the entire PR from a direction its original evidence did not watch. A control for a roster-identity law must exercise the SHAPE of a roster change, never a particular member, or its lifetime is bounded by the least stable thing it mentions. A description of a control's shape must live with that control or derive from it, so an implementation change cannot leave a remote carrier describing evidence that no longer executes. The discriminator proves staleness rather than malformedness: the alternative-revision receipt refuses against the live revision and the identical receipt admits when that alternative revision is supplied as current. The live-revision receipt also admits, so an always-admit or always-refuse implementation fails a control." -test fn synthetic_changed_roster_receipt_refuses_against_live_roster() -> Bool { - let receipt = MergeAdmissionReceiptV2 { +fn synthetic_changed_roster_receipt() -> MergeAdmissionReceiptV2 { + MergeAdmissionReceiptV2 { attempt_id: attempt_a(), pr_number: Present { value: 7544 }, tested_head_sha: head_sha_fx, @@ -123,11 +116,14 @@ test fn synthetic_changed_roster_receipt_refuses_against_live_roster() -> Bool { gate_roster_hash: synthetic_changed_roster_hash(), conclusion: Success, } +} + +test fn synthetic_changed_roster_receipt_refuses_against_live_roster() -> Bool { (synthetic_changed_roster_hash() != current_gate_roster_hash) && match classify_merge_admission_verdict_v2( current_attempt_id: attempt_a(), tested_subject: subject_fx(attempt: attempt_a(), tree: base_tree_fx(), head: head_sha_fx), - receipt: receipt, + receipt: synthetic_changed_roster_receipt(), merge_target_tree: base_tree_fx(), current_roster_hash: current_gate_roster_hash, ) { @@ -140,6 +136,23 @@ test fn synthetic_changed_roster_receipt_refuses_against_live_roster() -> Bool { } } +test fn synthetic_changed_roster_receipt_admits_when_revision_is_current() -> Bool { + match classify_merge_admission_verdict_v2( + current_attempt_id: attempt_a(), + tested_subject: subject_fx(attempt: attempt_a(), tree: base_tree_fx(), head: head_sha_fx), + receipt: synthetic_changed_roster_receipt(), + merge_target_tree: base_tree_fx(), + current_roster_hash: synthetic_changed_roster_hash(), + ) { + MergeAdmitted => true + MergeDeniedStaleBase => false + MergeDeniedStaleRoster => false + MergeDeniedNotSuccess => false + MergeDeniedWrongAttempt => false + MergeDeniedSubjectMismatch => false + } +} + test fn current_roster_receipt_admits_against_live_roster() -> Bool { let receipt = MergeAdmissionReceiptV2 { attempt_id: attempt_a(), From d8f43eb46c811bafe25837cebb73f85ebcbb78f8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 1 Aug 2026 20:46:37 +0000 Subject: [PATCH 8/8] Document roster order identity --- dag/test/claim/merge_admission_attempt_witness_test.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dag/test/claim/merge_admission_attempt_witness_test.dag b/dag/test/claim/merge_admission_attempt_witness_test.dag index 033ea6bdfa2..c4e63a26def 100644 --- a/dag/test/claim/merge_admission_attempt_witness_test.dag +++ b/dag/test/claim/merge_admission_attempt_witness_test.dag @@ -105,7 +105,7 @@ fn synthetic_changed_roster_hash() -> ContentHash { gate_roster_content_hash(roster: reverse(commit_gate_roster)) } -data roster_staleness_control_recut_note: String = "RECUT AFTER #7591 INVALIDATED #7583. The 2026-08-01 incident motivates the roster-identity law, but this control does not reconstruct that incident by execution. Its alternative revision reverses the exact live enrollments: identical members and surfaces, still unique and otherwise valid, but with a different order-sensitive roster hash. It names no particular production gate and asserts no historical roster content. The prior control named PublicationPlacementGate; that coupled the regression control to one volatile roster member's continued existence, and the member's operator-ordered deletion invalidated the entire PR from a direction its original evidence did not watch. A control for a roster-identity law must exercise the SHAPE of a roster change, never a particular member, or its lifetime is bounded by the least stable thing it mentions. A description of a control's shape must live with that control or derive from it, so an implementation change cannot leave a remote carrier describing evidence that no longer executes. The discriminator proves staleness rather than malformedness: the alternative-revision receipt refuses against the live revision and the identical receipt admits when that alternative revision is supplied as current. The live-revision receipt also admits, so an always-admit or always-refuse implementation fails a control." +data roster_staleness_control_recut_note: String = "RECUT AFTER #7591 INVALIDATED #7583. The 2026-08-01 incident motivates the roster-identity law, but this control does not reconstruct that incident by execution. Its alternative revision reverses the exact live enrollments: identical members and surfaces, still unique and otherwise valid, but with a different order-sensitive roster hash. Order participates in roster identity even though it does not affect roster validity, so a source-only reordering is a real roster revision that refuses every in-flight admission minted under the prior order. It names no particular production gate and asserts no historical roster content. The prior control named PublicationPlacementGate; that coupled the regression control to one volatile roster member's continued existence, and the member's operator-ordered deletion invalidated the entire PR from a direction its original evidence did not watch. A control for a roster-identity law must exercise the SHAPE of a roster change, never a particular member, or its lifetime is bounded by the least stable thing it mentions. A description of a control's shape must live with that control or derive from it, so an implementation change cannot leave a remote carrier describing evidence that no longer executes. The discriminator proves staleness rather than malformedness: the alternative-revision receipt refuses against the live revision and the identical receipt admits when that alternative revision is supplied as current. The live-revision receipt also admits, so an always-admit or always-refuse implementation fails a control." fn synthetic_changed_roster_receipt() -> MergeAdmissionReceiptV2 { MergeAdmissionReceiptV2 {