From a6a931f55986ffe70eb2e9d59e14d48ace0086a7 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 6 Sep 2026 22:48:18 +0000 Subject: [PATCH 01/22] Give docs/*.md ledger projections a ProcessExit actuator that can finish. Required-regen never owned those files, and main_wet dies inside the whole-registry emit graph, so rostered failure-mode rows could sit stale with every completing instrument green. A docs-only gate plus a required docs-projections population refuse that split. Co-authored-by: Cursor --- ...enerated_artifact_boundary_seed_growth.dag | 20 +++++ .../instruments/docs_projection_agreement.dag | 26 ++++++ .../instruments/docs_projection_gate.dag | 79 +++++++++++++++++++ ...rojections_have_no_completing_actuator.dag | 31 ++++++++ dag/gunbc/recurring_failure_mode/roster.dag | 2 + dag/test/claim/docs_projection_gate_test.dag | 31 ++++++++ ...rated_artifact_assessment_witness_test.dag | 9 +++ src/v1/stage0/src/bin/claim_executor.rs | 20 +++++ src/v1/stage0/src/cli_run.rs | 7 +- .../src/generated_artifact_boundary_host.rs | 53 +++++++++++++ src/v2/workflow/required_floor.dag | 6 ++ 11 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 dag/gunbc/instruments/docs_projection_agreement.dag create mode 100644 dag/gunbc/instruments/docs_projection_gate.dag create mode 100644 dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag create mode 100644 dag/test/claim/docs_projection_gate_test.dag diff --git a/dag/gunbc/generated_artifact_boundary_seed_growth.dag b/dag/gunbc/generated_artifact_boundary_seed_growth.dag index bc10c151beb..b3426e0a9ff 100644 --- a/dag/gunbc/generated_artifact_boundary_seed_growth.dag +++ b/dag/gunbc/generated_artifact_boundary_seed_growth.dag @@ -185,6 +185,26 @@ data generated_artifact_boundary_seed_growth_justification: SeedGrowthJustificat decl_name: "run_generated_artifact_boundary", field: WholeDeclaration }, + DeclarationRef { + module_path: "v1_compiler.generated_artifact_boundary_host", + decl_name: "DOCS_PROJECTION_REGEN_COMMAND", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.generated_artifact_boundary_host", + decl_name: "DOCS_PROJECTION_GATE_ENTRY", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.generated_artifact_boundary_host", + decl_name: "DocsProjectionAgreement", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.generated_artifact_boundary_host", + decl_name: "run_docs_projection_agreement", + field: WholeDeclaration + }, DeclarationRef { module_path: "v1_compiler.generated_artifact_boundary_host", decl_name: "disposition_separates_absent_from_drifted", diff --git a/dag/gunbc/instruments/docs_projection_agreement.dag b/dag/gunbc/instruments/docs_projection_agreement.dag new file mode 100644 index 00000000000..886313c3ce4 --- /dev/null +++ b/dag/gunbc/instruments/docs_projection_agreement.dag @@ -0,0 +1,26 @@ +module tools.docs_projection_agreement + +import std.process { ProcessExit, ExitSuccess, exit_failure } + +// Same literals as gunbc.generated_artifact artifact_location for the two ledger variants. +// The join is test.claim.docs_projection_path_join_test / generated_artifact_assessment: +// artifact_path(DesignFailureModesArtifact) == docs_design_failure_modes_path. +data docs_design_failure_modes_path: String = "docs/design-failure-modes.md" +data docs_design_rung_drops_path: String = "docs/design-rung-drops.md" + +// ONE PURE VERDICT OVER ONE PATH'S EXPECTED AND COMMITTED BYTES. Production regen and the +// required generated-artifact phase both reach a ProcessExit through this function, so a fixture +// can present drifted ledger bytes without resolving gunbc.generated_artifact_emit or writing +// the live tree. The path is an argument because the diagnostic must name the file; the bytes +// are arguments because a live-tree read is a different question (the wet gate). + +fn docs_projection_bytes_agree(path: String, expected: String, committed: String) -> ProcessExit { + if expected == committed { + ExitSuccess + } else { + exit_failure(reason: concat( + "generated-artifact drift: ", + concat(path, " is not derived from its .dag authority; regenerate with gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/instruments/docs_projection_gate.dag --function regen") + )) + } +} diff --git a/dag/gunbc/instruments/docs_projection_gate.dag b/dag/gunbc/instruments/docs_projection_gate.dag new file mode 100644 index 00000000000..184fd196d68 --- /dev/null +++ b/dag/gunbc/instruments/docs_projection_gate.dag @@ -0,0 +1,79 @@ +module tools.docs_projection_gate + +import std.process { ProcessExit, ExitSuccess, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem, filesystem_read_outcome, FilesystemReadSucceeded, FilesystemReadRefused } +import gunbc.design_ledgers { expected_design_failure_modes_md, expected_design_rung_drops_md } +import tools.docs_projection_agreement { + docs_projection_bytes_agree, + docs_design_failure_modes_path, + docs_design_rung_drops_path +} + +// THE CLEANLY-EXITING ACTUATOR FOR docs/*.md LEDGER PROJECTIONS. +// +// gunbc.generated_artifact_gate main_wet folds the WHOLE committed registry, and resolving +// gunbc.generated_artifact_emit pulls every generator (plans, workflows, CAD, stage0 mirrors). +// Authors filing a recurring-failure-mode row then ran claim_executor --required-regen (which +// never owns these files) or main_wet (which does not return a ProcessExit on the runners that +// SIGKILL it), so three rostered rows sat in the authority while docs/design-failure-modes.md +// stayed stale and nothing that completes could say so. +// +// This entry imports the ledger renderer and the filesystem, not the registry emit match. Path +// strings here are the same literals gunbc.generated_artifact artifact_location uses for these +// two variants; test.claim.generated_artifact_assessment +// `docs_projection_actuator_paths_match_registry_ledger_paths` joins them to artifact_path so a +// rename of either side refuses. + +fn exit_ok(e: ProcessExit) -> Bool { + match e { ExitSuccess => true _ => false } +} + +fn write_docs_projection(path: String, content: String) -> ProcessExit { + let w = Filesystem.Write(path: path, content: content) + if w.success { + ExitSuccess + } else { + exit_failure(reason: concat(concat("write failed for ", path), concat(": ", w.error))) + } +} + +func regen() -> ProcessExit { + let first = write_docs_projection( + path: docs_design_failure_modes_path, + content: expected_design_failure_modes_md() + ) + if exit_ok(e: first) { + write_docs_projection( + path: docs_design_rung_drops_path, + content: expected_design_rung_drops_md() + ) + } else { + first + } +} + +fn agree_one(path: String, expected: String) -> ProcessExit { + let r = Filesystem.Read(path: path) + match filesystem_read_outcome(content: r.content, success: r.success, error: r.error) { + FilesystemReadRefused { error } => exit_failure( + reason: concat(concat("docs projection unreadable: ", path), concat(": ", error)) + ) + FilesystemReadSucceeded { content } => + docs_projection_bytes_agree(path: path, expected: expected, committed: content) + } +} + +func main() -> ProcessExit { + let first = agree_one( + path: docs_design_failure_modes_path, + expected: expected_design_failure_modes_md() + ) + if exit_ok(e: first) { + agree_one( + path: docs_design_rung_drops_path, + expected: expected_design_rung_drops_md() + ) + } else { + first + } +} diff --git a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag new file mode 100644 index 00000000000..7dff4a03e77 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag @@ -0,0 +1,31 @@ +module gunbc.recurring_failure_mode.docs_markdown_projections_have_no_completing_actuator + +import std.types { NonEmptyStr } +import std.decl_ref { DeclarationRef, WholeDeclaration } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode = RecurringFailureMode { + identity: "docs_markdown_projections_have_no_completing_actuator" as NonEmptyStr, + + receipts: [ + "**generated `docs/*.md` ledger projections were gated nowhere that completes: no ProcessExit actuator whose closure is those files, and no regen coverage that owns them** (INVALID STATE: `gunbc.recurring_failure_mode` and `gunbc.rung_drop` are the authorities; `docs/design-failure-modes.md` and `docs/design-rung-drops.md` are the projections DESIGN.md points readers at. The habitual regen, `claim_executor --required-regen`, denominates stage0 mirrors and reports a full round count while those files stay stale. The actuator that does own them, `tools.generated_artifact_gate` `main_wet`, resolves `gunbc.generated_artifact_emit` — every generator in the registry — and on the runners that matter it is SIGKILLed or MemoryStallRefused rather than returning ProcessExit. HARM: three rostered failure-mode rows can sit in the authority, absent from the projection every session loads, and every instrument that finished reported green about a different population. ", + + "SPECIMEN, 2026-09-06: `binder_renaming_changes_variant_field_lookup`, `permissiveness_promoted_into_a_higher_safety_rung`, and `evaluation_model_cleanup_undeclared` were filed in the roster. Nothing that exited cleanly compared the committed markdown to `expected_design_failure_modes_md`. The gap was found by reading the projection, which is not a step any completing gate performed. ", + + "THE TWO NEIGHBOURS THIS IS NOT. `green_reported_over_a_population_the_instrument_does_not_own` is the same split of actuators, climbed for the required composite phase over registry projections plus stage0 mirrors; that climb still pays the whole-registry resolve, so docs ledger drift remains unrefusable whenever that resolve does not return. This row is the completing-actuator grain: a ProcessExit whose import closure is the ledger renderer, not the registry emit match. `sweep_terminated_by_process_death_rather_than_a_typed_refusal` is death instead of a located verdict; here the death is of the WRONG entry, and authors still have a typed command that never ran. ", + + "RECOGNITION RULE: when the file a reader is pointed at is generated, ask which ProcessExit entry writes it, whether that entry's closure is those files rather than an unbounded registry, and whether a required phase runs an agreement over that same closure. A round count from a sibling actuator is not coverage. ", + + "RUNG FOUND AT: mechanically preventable once `tools.docs_projection_gate` `regen` / `main` and the required generated-artifact `docs-projections` population execute. The invalid stale file remains writable. CEILING: structurally guaranteed when required-run dispatch derives an adjudicator from every committed artifact whose location directory is `docs`, so omitting the arm is a compile refusal. ", + + "NEXT-RUNG TRIGGER: required-run dispatch consumes an exhaustive docs-directory projection of `gunbc.generated_artifact` `committed_generated_artifacts`, such that adding a committed `docs/` artifact without an agreement arm will not compile. Filing this row does not itself enrol that derivation.)", + ], + + evidence: [ + DeclarationRef { module_path: "test.claim.docs_projection_gate_test", decl_name: "drifted_docs_projection_bytes_refuse_and_name_the_path", field: WholeDeclaration }, + DeclarationRef { module_path: "test.claim.docs_projection_gate_test", decl_name: "matching_docs_projection_bytes_succeed", field: WholeDeclaration }, + DeclarationRef { module_path: "test.claim.generated_artifact_assessment", decl_name: "docs_projection_actuator_paths_match_registry_ledger_paths", field: WholeDeclaration }, + DeclarationRef { module_path: "tools.docs_projection_gate", decl_name: "regen", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.bin.claim_executor", decl_name: "run", field: WholeDeclaration }, + ], +} diff --git a/dag/gunbc/recurring_failure_mode/roster.dag b/dag/gunbc/recurring_failure_mode/roster.dag index 82ce4fe4b1e..3e221874e0b 100644 --- a/dag/gunbc/recurring_failure_mode/roster.dag +++ b/dag/gunbc/recurring_failure_mode/roster.dag @@ -174,6 +174,7 @@ import gunbc.recurring_failure_mode.native_stack_recursion_unbounded_over_corpus import gunbc.recurring_failure_mode.binder_renaming_changes_variant_field_lookup { binder_renaming_changes_variant_field_lookup } import gunbc.recurring_failure_mode.permissiveness_promoted_into_a_higher_safety_rung { permissiveness_promoted_into_a_higher_safety_rung } import gunbc.recurring_failure_mode.evaluation_model_cleanup_undeclared { evaluation_model_cleanup_undeclared } +import gunbc.recurring_failure_mode.docs_markdown_projections_have_no_completing_actuator { docs_markdown_projections_have_no_completing_actuator } data recurring_failure_mode_roster: List = [ a_type_name_asserts_an_algebra_the_arithmetic_does_not_carry, @@ -338,4 +339,5 @@ data recurring_failure_mode_roster: List = [ permissiveness_promoted_into_a_higher_safety_rung, native_stack_recursion_unbounded_over_corpus_shaped_structure, evaluation_model_cleanup_undeclared, + docs_markdown_projections_have_no_completing_actuator, ] diff --git a/dag/test/claim/docs_projection_gate_test.dag b/dag/test/claim/docs_projection_gate_test.dag new file mode 100644 index 00000000000..6323e91cb3d --- /dev/null +++ b/dag/test/claim/docs_projection_gate_test.dag @@ -0,0 +1,31 @@ +module test.claim.docs_projection_gate_test + +import std.process { ExitSuccess, ExitFailure } +import tools.docs_projection_agreement { docs_projection_bytes_agree } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +test fn drifted_docs_projection_bytes_refuse_and_name_the_path() -> Bool { + match docs_projection_bytes_agree( + path: "docs/design-failure-modes.md", + expected: "authority", + committed: "stale" + ) { + ExitSuccess => false + ExitFailure { code: _, reason } => + string_contains(s: reason, pattern: "docs/design-failure-modes.md") + && string_contains(s: reason, pattern: "docs_projection_gate.dag") + } +} + +test fn matching_docs_projection_bytes_succeed() -> Bool { + match docs_projection_bytes_agree( + path: "docs/design-rung-drops.md", + expected: "same", + committed: "same" + ) { + ExitSuccess => true + ExitFailure { code: _, reason: _ } => false + } +} diff --git a/dag/test/claim/generated_artifact_assessment_witness_test.dag b/dag/test/claim/generated_artifact_assessment_witness_test.dag index 7222b946485..fccabcb03f5 100644 --- a/dag/test/claim/generated_artifact_assessment_witness_test.dag +++ b/dag/test/claim/generated_artifact_assessment_witness_test.dag @@ -20,11 +20,20 @@ import gunbc.generated_artifact_assessment { import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } import std.process { ExitFailure, ExitSuccess } import tools.generated_artifact_gate { generated_artifact_agreement_verdict } +import tools.docs_projection_agreement { + docs_design_failure_modes_path, + docs_design_rung_drops_path +} data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // THE EXCEPTION IS EXACTLY THE TWO LEDGER PROJECTIONS, not all documentation and not a copied // path roster in the driver. Both directions make widening or silently dropping either ledger red. +test fn docs_projection_actuator_paths_match_registry_ledger_paths() -> Bool { + docs_design_failure_modes_path == artifact_path(a: DesignFailureModesArtifact) + && docs_design_rung_drops_path == artifact_path(a: DesignRungDropsArtifact) +} + test fn only_design_ledgers_delegate_concurrent_regeneration_to_heal() -> Bool { let paths = heal_regenerated_after_provisional_merge_paths() count(paths) == 2 diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index 9290c264ca6..a41c1a81870 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -843,6 +843,26 @@ fn run() -> Result { } } + // DOCS LEDGER HALF. A dedicated ProcessExit entry whose closure is the two ledger + // renderers, not gunbc.generated_artifact_emit. Run even after a mirror refusal, and + // before the whole-registry resolve, so a stale docs/design-failure-modes.md cannot + // hide behind a SIGKILL or MemoryStall of the 35-artifact fold. + match v1_compiler::cli_run::run_docs_projection_agreement(&source_roots) { + v1_compiler::cli_run::DocsProjectionAgreement::Clean => { + eprintln!("required-ci: generated-artifact population=docs-projections OK"); + } + v1_compiler::cli_run::DocsProjectionAgreement::Refused { cause } => { + eprintln!( + "required-ci: generated-artifact population=docs-projections REFUSED {cause}" + ); + eprintln!( + "required-ci: generated-artifact regenerate docs/*.md with: {}", + v1_compiler::cli_run::DOCS_PROJECTION_REGEN_COMMAND + ); + phase_failures.push(format!("generated-artifact docs-projections: {cause}")); + } + } + // REGISTRY HALF. Run even after a mirror refusal so the one outcome names the complete // population rather than allowing the first failure to hide the second. let registry_failures_before = phase_failures.len(); diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index c2facff5c8a..687c64bea91 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -42081,9 +42081,10 @@ pub use partition_crate_boundary_host::{ /// through one surface, the way the regen and partition-crate paths do. pub use generated_artifact_boundary_host::{ artifact_disposition, artifact_disposition_name, boundary_divergent, boundary_is_clean, - generated_artifact_body_for_path, generated_artifact_ctx, run_generated_artifact_boundary, - AdjudicatedArtifact, ArtifactDisposition, GeneratedArtifactBoundaryOutcome, - GeneratedArtifactPathBody, UnadjudicatedArtifact, GENERATED_ARTIFACT_PRODUCING_COMMAND, + generated_artifact_body_for_path, generated_artifact_ctx, run_docs_projection_agreement, + run_generated_artifact_boundary, AdjudicatedArtifact, ArtifactDisposition, + DocsProjectionAgreement, GeneratedArtifactBoundaryOutcome, GeneratedArtifactPathBody, + UnadjudicatedArtifact, DOCS_PROJECTION_REGEN_COMMAND, GENERATED_ARTIFACT_PRODUCING_COMMAND, }; /// The emitted-closure compile phase: one entry's closure emitted, written as a crate, and diff --git a/src/v1/stage0/src/generated_artifact_boundary_host.rs b/src/v1/stage0/src/generated_artifact_boundary_host.rs index 5925f5a7f7a..af13a544cd4 100644 --- a/src/v1/stage0/src/generated_artifact_boundary_host.rs +++ b/src/v1/stage0/src/generated_artifact_boundary_host.rs @@ -44,6 +44,59 @@ pub const GENERATED_ARTIFACT_PRODUCING_COMMAND: &str = "gunbc run --source-root dag --source-root src/v2 \ --entry dag/gunbc/instruments/generated_artifact_gate.dag --function main_wet"; +/// Regenerates only the two `docs/*.md` ledger projections and returns `ProcessExit`. +/// Distinct from `GENERATED_ARTIFACT_PRODUCING_COMMAND`: that entry resolves the whole +/// registry emit graph and does not complete on the runners that SIGKILL `main_wet`. +pub const DOCS_PROJECTION_REGEN_COMMAND: &str = "gunbc run --source-root dag --source-root src/v2 \ + --entry dag/gunbc/instruments/docs_projection_gate.dag --function regen"; + +const DOCS_PROJECTION_GATE_ENTRY: &str = "dag/gunbc/instruments/docs_projection_gate.dag"; + +/// Outcome of the docs-ledger agreement entry. Kept separate from +/// `GeneratedArtifactBoundaryOutcome` because that carrier resolves the whole registry; +/// this one must be able to refuse a stale `docs/design-failure-modes.md` even when that +/// resolve never returns. +pub enum DocsProjectionAgreement { + Clean, + Refused { cause: String }, +} + +pub fn run_docs_projection_agreement(source_roots: &[String]) -> DocsProjectionAgreement { + let (graph, indices) = match crate::cli_run::resolve_entry_graph_shared( + source_roots, + DOCS_PROJECTION_GATE_ENTRY, + ) { + Ok(v) => v, + Err(e) => { + return DocsProjectionAgreement::Refused { + cause: format!("resolve {DOCS_PROJECTION_GATE_ENTRY}: {e}"), + } + } + }; + let ctx = crate::cli_run::make_eval_context( + &graph, + indices, + crate::v1_interpreter::ExecutionMode::Hermetic, + ); + let out = match crate::v1_interpreter::run_in_context_with_args(&ctx, "main", &[], false) { + Ok(v) => v, + Err(e) => { + return DocsProjectionAgreement::Refused { + cause: format!("docs projection gate: {e:?}"), + } + } + }; + match super::classify_exit(&out, &ctx) { + super::ExitClass::Success => DocsProjectionAgreement::Clean, + super::ExitClass::Failure { reason, .. } => DocsProjectionAgreement::Refused { + cause: reason.unwrap_or_else(|| "docs projection drift".to_string()), + }, + super::ExitClass::NotProcessExit { type_name } => DocsProjectionAgreement::Refused { + cause: format!("docs projection gate returned {type_name}, not ProcessExit"), + }, + } +} + /// What the generated-artifact population says about one repo-relative path. /// /// Three states because the honest answers are three. `NotGenerated` is a POSITIVE answer — diff --git a/src/v2/workflow/required_floor.dag b/src/v2/workflow/required_floor.dag index d50b9f4b917..bb62e089aab 100644 --- a/src/v2/workflow/required_floor.dag +++ b/src/v2/workflow/required_floor.dag @@ -555,6 +555,7 @@ fn required_gate_prefixes() -> List { "test.claim.typed_module_cache_capacity_witness", "test.claim.whole_corpus_compile_admission_witness", "test.claim.memory_stall_refusal_witness", + "test.claim.docs_projection_gate_test", ] } @@ -594,6 +595,11 @@ fn required_gate_prefixes() -> List { // treadmill: four whole-closure runs holding 2-4.5 hours with no diagnostic and no exit. // Same exact-module-name rule as below, for the same reason. // +// test.claim.docs_projection_gate_test is the same family: two cheap ProcessExit fixtures over +// injected bytes, so a stale docs/*.md projection has a floor-discovered RED that does not +// resolve gunbc.generated_artifact_emit. Exact module name, not `test.claim.docs_`, so it +// cannot sweep a later wet regen helper into the static gate. +// // THE LAST THREE ROWS BEFORE IT, ADDED 2026-08-30, AND WHY THEY BELONG UNDER THE RULING ABOVE // RATHER THAN BESIDE IT. // From 267b4df83513446694b908ae2e389a7478e5cee4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 6 Sep 2026 22:50:55 +0000 Subject: [PATCH 02/22] Correct the docs-projection failure-mode row: identity join is membership, not content. The specimen is three edited rows that stayed named in the projection; a name-set join would have called the file complete. The projector returns String so gunbc run exits 2 without a ProcessExit actuator. Co-authored-by: Cursor --- ...rojections_have_no_completing_actuator.dag | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag index 7dff4a03e77..88214192e3a 100644 --- a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag +++ b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag @@ -8,24 +8,24 @@ data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode identity: "docs_markdown_projections_have_no_completing_actuator" as NonEmptyStr, receipts: [ - "**generated `docs/*.md` ledger projections were gated nowhere that completes: no ProcessExit actuator whose closure is those files, and no regen coverage that owns them** (INVALID STATE: `gunbc.recurring_failure_mode` and `gunbc.rung_drop` are the authorities; `docs/design-failure-modes.md` and `docs/design-rung-drops.md` are the projections DESIGN.md points readers at. The habitual regen, `claim_executor --required-regen`, denominates stage0 mirrors and reports a full round count while those files stay stale. The actuator that does own them, `tools.generated_artifact_gate` `main_wet`, resolves `gunbc.generated_artifact_emit` — every generator in the registry — and on the runners that matter it is SIGKILLed or MemoryStallRefused rather than returning ProcessExit. HARM: three rostered failure-mode rows can sit in the authority, absent from the projection every session loads, and every instrument that finished reported green about a different population. ", + "**generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `gunbc run` maps a non-`ProcessExit` return to exit 2 (`cli_run.exit_status_for_class` `NotProcessExit`) and names the TYPE, not the bytes — `main.rs` `non_process_exit_refuses_rather_than_printing_and_succeeding` is the enrolled control that this is a refusal rather than a print-and-succeed. `claim_executor --required-regen` compares only `is_compared_generated_basename` = `ends_with(\".rs\")` into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. ", - "SPECIMEN, 2026-09-06: `binder_renaming_changes_variant_field_lookup`, `permissiveness_promoted_into_a_higher_safety_rung`, and `evaluation_model_cleanup_undeclared` were filed in the roster. Nothing that exited cleanly compared the committed markdown to `expected_design_failure_modes_md`. The gap was found by reading the projection, which is not a step any completing gate performed. ", + "SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. ", - "THE TWO NEIGHBOURS THIS IS NOT. `green_reported_over_a_population_the_instrument_does_not_own` is the same split of actuators, climbed for the required composite phase over registry projections plus stage0 mirrors; that climb still pays the whole-registry resolve, so docs ledger drift remains unrefusable whenever that resolve does not return. This row is the completing-actuator grain: a ProcessExit whose import closure is the ledger renderer, not the registry emit match. `sweep_terminated_by_process_death_rather_than_a_typed_refusal` is death instead of a located verdict; here the death is of the WRONG entry, and authors still have a typed command that never ran. ", + "THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. ", - "RECOGNITION RULE: when the file a reader is pointed at is generated, ask which ProcessExit entry writes it, whether that entry's closure is those files rather than an unbounded registry, and whether a required phase runs an agreement over that same closure. A round count from a sibling actuator is not coverage. ", + "THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. ", - "RUNG FOUND AT: mechanically preventable once `tools.docs_projection_gate` `regen` / `main` and the required generated-artifact `docs-projections` population execute. The invalid stale file remains writable. CEILING: structurally guaranteed when required-run dispatch derives an adjudicator from every committed artifact whose location directory is `docs`, so omitting the arm is a compile refusal. ", + "RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. ", - "NEXT-RUNG TRIGGER: required-run dispatch consumes an exhaustive docs-directory projection of `gunbc.generated_artifact` `committed_generated_artifacts`, such that adding a committed `docs/` artifact without an agreement arm will not compile. Filing this row does not itself enrol that derivation.)", + "RUNG FOUND AT: outside the ladder until a completing content agreement exists — silent wrongness, not a weak wall. After a ProcessExit projector and a required content compare execute, mechanically preventable: the stale file remains writable. CEILING: structurally guaranteed when membership of every committed generated `docs/` path in a content-agreement adjudicator is derived from `gunbc.generated_artifact` `committed_generated_artifacts`, so omitting the arm is a compile refusal. ", + + "NEXT-RUNG TRIGGER, a capability: required-run dispatch consumes a content agreement — byte identity of generated vs committed — over every committed generated artifact whose location directory is `docs`, derived from the registry so adding a committed `docs/` path without that agreement will not compile. A trigger naming one command, one test file, or `docs_projection_gate.dag` is less than the capability and would fire while edit-only drift can still sit uncompared.)", ], evidence: [ - DeclarationRef { module_path: "test.claim.docs_projection_gate_test", decl_name: "drifted_docs_projection_bytes_refuse_and_name_the_path", field: WholeDeclaration }, - DeclarationRef { module_path: "test.claim.docs_projection_gate_test", decl_name: "matching_docs_projection_bytes_succeed", field: WholeDeclaration }, - DeclarationRef { module_path: "test.claim.generated_artifact_assessment", decl_name: "docs_projection_actuator_paths_match_registry_ledger_paths", field: WholeDeclaration }, - DeclarationRef { module_path: "tools.docs_projection_gate", decl_name: "regen", field: WholeDeclaration }, - DeclarationRef { module_path: "v1_compiler.bin.claim_executor", decl_name: "run", field: WholeDeclaration }, + DeclarationRef { module_path: "gunbc.design_ledgers", decl_name: "expected_design_failure_modes_md", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.required_regen_host", decl_name: "is_compared_generated_basename", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "exit_status_for_class", field: WholeDeclaration }, ], } From 5b058882974848875a37250f5e4f33669d242793 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 6 Sep 2026 23:10:25 +0000 Subject: [PATCH 03/22] Record that NotProcessExit interpolates type_name only, as a deliberate wall. The projector cannot be scraped for markdown; ProcessExit regen is the repair shape, and review 58567 on #9864 is a prior specimen of the same class. Co-authored-by: Cursor --- .../docs_markdown_projections_have_no_completing_actuator.dag | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag index 88214192e3a..07707aeb679 100644 --- a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag +++ b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag @@ -8,10 +8,12 @@ data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode identity: "docs_markdown_projections_have_no_completing_actuator" as NonEmptyStr, receipts: [ - "**generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `gunbc run` maps a non-`ProcessExit` return to exit 2 (`cli_run.exit_status_for_class` `NotProcessExit`) and names the TYPE, not the bytes — `main.rs` `non_process_exit_refuses_rather_than_printing_and_succeeding` is the enrolled control that this is a refusal rather than a print-and-succeed. `claim_executor --required-regen` compares only `is_compared_generated_basename` = `ends_with(\".rs\")` into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. ", + "**generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `cli_run.exit_status_for_class` `NotProcessExit` interpolates only `type_name` — for a String projector the refusal prints the word String, never the markdown — and exits 2. The third line of that message is the design: printing the value and exiting 0 would report success for a run whose outcome is unknown, which is fabricated plausible output, so this is a deliberate wall, not an oversight to route around. A ProcessExit regen is therefore the only correct repair shape, not one option among several. The bootstrap launcher `NotProcessExit` arm is the same shape (type_name, exit 2). Enrolled control: `main.rs` `non_process_exit_refuses_rather_than_printing_and_succeeding`. `claim_executor --required-regen` compares only `is_compared_generated_basename` = `ends_with(\".rs\")` into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. ", "SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. ", + "SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. ", + "THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. ", "THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. ", From d68a76585b812b0d777e4a75e52bc9a011bb252f Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 6 Sep 2026 23:12:11 +0000 Subject: [PATCH 04/22] Retract the type-name-only claim: NotProcessExit.type_name carries format_value on the non-variant arm. Docs-projection content is extractable only by scraping that unversioned diagnostic. The field-name fork is a separate row. Co-authored-by: Cursor --- ...rojections_have_no_completing_actuator.dag | 3 +- dag/gunbc/recurring_failure_mode/roster.dag | 2 ++ ...type_name_field_carries_rendered_value.dag | 28 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag diff --git a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag index 07707aeb679..016d76a60ea 100644 --- a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag +++ b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag @@ -8,7 +8,7 @@ data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode identity: "docs_markdown_projections_have_no_completing_actuator" as NonEmptyStr, receipts: [ - "**generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `cli_run.exit_status_for_class` `NotProcessExit` interpolates only `type_name` — for a String projector the refusal prints the word String, never the markdown — and exits 2. The third line of that message is the design: printing the value and exiting 0 would report success for a run whose outcome is unknown, which is fabricated plausible output, so this is a deliberate wall, not an oversight to route around. A ProcessExit regen is therefore the only correct repair shape, not one option among several. The bootstrap launcher `NotProcessExit` arm is the same shape (type_name, exit 2). Enrolled control: `main.rs` `non_process_exit_refuses_rather_than_printing_and_succeeding`. `claim_executor --required-regen` compares only `is_compared_generated_basename` = `ends_with(\".rs\")` into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. ", + "**generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `classify_exit`'s non-variant arm puts `ctx.format_value(other)` into `NotProcessExit.type_name`; `exit_status_for_class` interpolates that field into stderr and exits 2. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` is exact: nothing on stdout, not a print-and-succeed. The markdown IS in the diagnostic text, EXTRACTABLE ONLY BY SCRAPING AN UNVERSIONED DIAGNOSTIC — the delimiters are the prose wrapping the interpolated field (returned-open-quote and not-ProcessExit-close-quote), unenrolled and free to change with any edit to that message. That is worse than a clean ProcessExit actuator and better than impossible, and it is still a complete argument for a ProcessExit regen. `claim_executor --required-regen` compares only `is_compared_generated_basename` = `ends_with(\".rs\")` into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named `type_name` is a separate row, `type_name_field_carries_rendered_value`, not this class. ", "SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. ", @@ -28,6 +28,7 @@ data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode evidence: [ DeclarationRef { module_path: "gunbc.design_ledgers", decl_name: "expected_design_failure_modes_md", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.required_regen_host", decl_name: "is_compared_generated_basename", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "classify_exit", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "exit_status_for_class", field: WholeDeclaration }, ], } diff --git a/dag/gunbc/recurring_failure_mode/roster.dag b/dag/gunbc/recurring_failure_mode/roster.dag index 3e221874e0b..1d4e70b697a 100644 --- a/dag/gunbc/recurring_failure_mode/roster.dag +++ b/dag/gunbc/recurring_failure_mode/roster.dag @@ -175,6 +175,7 @@ import gunbc.recurring_failure_mode.binder_renaming_changes_variant_field_lookup import gunbc.recurring_failure_mode.permissiveness_promoted_into_a_higher_safety_rung { permissiveness_promoted_into_a_higher_safety_rung } import gunbc.recurring_failure_mode.evaluation_model_cleanup_undeclared { evaluation_model_cleanup_undeclared } import gunbc.recurring_failure_mode.docs_markdown_projections_have_no_completing_actuator { docs_markdown_projections_have_no_completing_actuator } +import gunbc.recurring_failure_mode.type_name_field_carries_rendered_value { type_name_field_carries_rendered_value } data recurring_failure_mode_roster: List = [ a_type_name_asserts_an_algebra_the_arithmetic_does_not_carry, @@ -340,4 +341,5 @@ data recurring_failure_mode_roster: List = [ native_stack_recursion_unbounded_over_corpus_shaped_structure, evaluation_model_cleanup_undeclared, docs_markdown_projections_have_no_completing_actuator, + type_name_field_carries_rendered_value, ] diff --git a/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag b/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag new file mode 100644 index 00000000000..f85a1e1821e --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag @@ -0,0 +1,28 @@ +module gunbc.recurring_failure_mode.type_name_field_carries_rendered_value + +import std.types { NonEmptyStr } +import std.decl_ref { DeclarationRef, WholeDeclaration } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +data type_name_field_carries_rendered_value: RecurringFailureMode = RecurringFailureMode { + identity: "type_name_field_carries_rendered_value" as NonEmptyStr, + + receipts: [ + "**a field named `type_name` holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: `ExitClass::NotProcessExit { type_name }` is one spelling. `classify_exit` Variant arms that are not `ProcessExit` put `ctx.resolve(*type_name)` or `ProcessExit::` into it — an actual type or variant name. The final `other` arm puts `ctx.format_value(other)` — the rendered value — into the same field. `exit_status_for_class` interpolates that field into `returned `{type_name}`` with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. ", + + "SPECIMEN, 2026-09-06, three conclusions from two lanes and their manager on the same function in twenty minutes. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs `NotProcessExit` with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the same interpolation and relayed that a String projector yields the word String. quick-heron-85 held proj3.err at 1062137 bytes opening with expected_design_failure_modes_md returned the markdown heading and closing with not ProcessExit — they extracted between those delimiters. The manager then read `classify_exit`'s final arm and retracted. ", + + "THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. ", + + "RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. ", + + "RUNG FOUND AT: mitigatable — the wrong inference is a human reading; the runtime still refuses with exit 2 and does not print-and-succeed. CEILING: structurally impossible when `NotProcessExit` cannot be constructed with a rendered value under a field named for a type — two variants, or a field named for what it actually holds. ", + + "NEXT-RUNG TRIGGER, a capability: `ExitClass` distinguishes a type-name refusal from a rendered-value refusal in the type, so interpolating one as the other will not compile.)", + ], + + evidence: [ + DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "classify_exit", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "exit_status_for_class", field: WholeDeclaration }, + ], +} From b1f4680b6cbf95bd9343f43f1f992180fd997f75 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 6 Sep 2026 23:43:20 +0000 Subject: [PATCH 05/22] Regenerate docs/design-failure-modes.md so the new docs-projection gate is green on this tree. The roster rows were unprojected (review 61515); receipts also drop interpolating type_name so the regen entry resolves. Co-authored-by: Cursor --- .../docs_markdown_projections_have_no_completing_actuator.dag | 2 +- .../type_name_field_carries_rendered_value.dag | 2 +- docs/design-failure-modes.md | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag index 016d76a60ea..029b97ab630 100644 --- a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag +++ b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag @@ -8,7 +8,7 @@ data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode identity: "docs_markdown_projections_have_no_completing_actuator" as NonEmptyStr, receipts: [ - "**generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `classify_exit`'s non-variant arm puts `ctx.format_value(other)` into `NotProcessExit.type_name`; `exit_status_for_class` interpolates that field into stderr and exits 2. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` is exact: nothing on stdout, not a print-and-succeed. The markdown IS in the diagnostic text, EXTRACTABLE ONLY BY SCRAPING AN UNVERSIONED DIAGNOSTIC — the delimiters are the prose wrapping the interpolated field (returned-open-quote and not-ProcessExit-close-quote), unenrolled and free to change with any edit to that message. That is worse than a clean ProcessExit actuator and better than impossible, and it is still a complete argument for a ProcessExit regen. `claim_executor --required-regen` compares only `is_compared_generated_basename` = `ends_with(\".rs\")` into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named `type_name` is a separate row, `type_name_field_carries_rendered_value`, not this class. ", + "**generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `classify_exit`'s non-variant arm puts ctx.format_value into the NotProcessExit payload field named type_name; `exit_status_for_class` interpolates that field into stderr and exits 2. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` is exact: nothing on stdout, not a print-and-succeed. The markdown IS in the diagnostic text, EXTRACTABLE ONLY BY SCRAPING AN UNVERSIONED DIAGNOSTIC — the delimiters are the prose wrapping the interpolated field (returned-open-quote and not-ProcessExit-close-quote), unenrolled and free to change with any edit to that message. That is worse than a clean ProcessExit actuator and better than impossible, and it is still a complete argument for a ProcessExit regen. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. ", "SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. ", diff --git a/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag b/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag index f85a1e1821e..3695f40d9f0 100644 --- a/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag +++ b/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag @@ -8,7 +8,7 @@ data type_name_field_carries_rendered_value: RecurringFailureMode = RecurringFai identity: "type_name_field_carries_rendered_value" as NonEmptyStr, receipts: [ - "**a field named `type_name` holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: `ExitClass::NotProcessExit { type_name }` is one spelling. `classify_exit` Variant arms that are not `ProcessExit` put `ctx.resolve(*type_name)` or `ProcessExit::` into it — an actual type or variant name. The final `other` arm puts `ctx.format_value(other)` — the rendered value — into the same field. `exit_status_for_class` interpolates that field into `returned `{type_name}`` with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. ", + "**a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. ", "SPECIMEN, 2026-09-06, three conclusions from two lanes and their manager on the same function in twenty minutes. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs `NotProcessExit` with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the same interpolation and relayed that a String projector yields the word String. quick-heron-85 held proj3.err at 1062137 bytes opening with expected_design_failure_modes_md returned the markdown heading and closing with not ProcessExit — they extracted between those delimiters. The manager then read `classify_exit`'s final arm and retracted. ", diff --git a/docs/design-failure-modes.md b/docs/design-failure-modes.md index 30dec706ad5..3f6e5aa0fd8 100644 --- a/docs/design-failure-modes.md +++ b/docs/design-failure-modes.md @@ -128,7 +128,6 @@ The exact `RecurringFailureMode.identity` population, which no other projection - `fail_closed_gate_refuses_its_own_repair` - `a_live_authority_name_carries_a_superseded_claim` - `witness_that_fails_to_compile_is_absent_rather_than_red` -- `excluded_witness_outlives_the_subject_it_names` - `instruction_and_subject_resolved_from_different_revisions` - `reported_required_refusal_does_not_precondition_landing` - `corroboration_without_an_independent_derivation_axis` @@ -309,7 +308,6 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a fail-closed gate predicates evaluation on base-revision health and therefore refuses the change that restores that health.** INVALID STATE: the gate correctly declines to compare an unreadable base, but has no representable arm for `the base defect is exactly the subject this change removes`. Its safety property becomes self-sealing: every ordinary change is rightly refused while the repair is refused for the same reason, so no gate-admitted transition can restore the precondition the gate requires. MEASURED SPECIMEN (2026-09-04): gunbc#10390 left an unattached `//` block at the tail of `dag/test/claim/emit_copy_qualification_witness_test.dag`; the base revision then failed parsing with 16 diagnostics. On gunbc#10428, which moved that block onto a declaration and made the file parse again, the namespace-wave-admission phase reported `NotEvaluated -- dag/test/claim/emit_copy_qualification_witness_test.dag does not parse at the base revision (16 diagnostic(s)), so its base-side declarations cannot be read`. The refusal was correct, and #10428 could repair main only because it landed despite that refusal. Its next run was green. DISTINGUISHING FACTS: nothing widened, defaulted, or fabricated, so this is NOT `absorbing_fallback`; the gate preserved ignorance as NotEvaluated exactly as fail-closed requires. The defect is transition reachability, not verdict truth. Recognition rule: for every gate that requires a healthy base, construct a base violating that precondition and a head differing only by removal of the violation; if the same refusal covers both that repair and an unrelated change, the gate has no repair discriminator and its invariant is self-sealing. HARM: a below-floor parse defect on main made its own direct repair unmergeable by the required admission mechanism, and every other pull request inherited the refusal until an operator used a route outside that mechanism. A gate intended to bound change thereby extended the outage and required an unmodeled exception at the moment its guarantee mattered most. RUNG FOUND AT: mitigatable by operator inspection and exceptional landing, with the invalid transition still writable and no mechanism distinguishing it. CEILING: mechanically preventable, not structural today: admitting a purported repair from the head alone is the obvious fail-open trap, because an ordinary change can conceal, relocate, or replace the unreadable base subject while claiming repair. A safe arm must establish both a bounded defect identity on the base and monotone removal of only that defect without pretending to compare base semantics the parser could not produce. NEXT-RUNG TRIGGER: a revision-scoped recovery-parse and change-classification capability sufficient to locate a bounded base defect in raw source, prove that the head removes that same defect, and prove that no outside-scope semantic change rides with it; the gate may then admit only that typed Repair transition while continuing to refuse every unclassified NotEvaluated change. - a live authority name carries a superseded claim. A rename or authority replacement sweeps prose for the NAME, every occurrence is updated, every updated occurrence resolves -- and the PROPOSITION each name was embedded in is never re-read. The citation is now current and the sentence around it is false. **THIS IS NOT A STALE CITATION AND THE DIFFERENCE IS THE WHOLE CLASS.** A stale name is visibly stale: it fails to resolve, or it names something a reader can see is gone. A REFRESHED name is worse, because the refresh is itself an attestation -- it tells the reader that someone recently looked at this sentence and found it correct. The sweep supplies that signal without having supplied the reading. Neighbouring classes differ at the referent, not here: `positional_citation` and `stable_citation_mutable_referent` are about the pointer decaying underneath a claim that stayed true, and this is the exact inverse -- the pointer is repaired and the claim is what died. **RECEIPT (eager-pike-541, 2026-09-04, gunbc#10358).** The PR renamed `gunbc.model.choice` to `gunbc.model.ollama_choice` and updated the citation in `docs/plans/spark-fleet-hand-edit-gap-analysis.md` section 7 accordingly. The updated paragraph continued to state that serving configuration identity derives from the runner's argv -- a mechanism `ObservedOllamaLaunchConfiguration` had ALREADY REPLACED before the rename was proposed. The rename was correct, the new name was correct, and the sentence carrying it had been false since a prior change. Found by external review as REQUEST_CHANGES (`review 59866`), not by any mechanism in the tree. **WHY NO EXISTING WALL SEES IT, stated as a boundary rather than a gap to be closed by the neighbour.** gunbc's namespace-wave admission (`gunbc.namespace_wave_admission`) enumerates AUTHORED NAME OCCURRENCES resolved per module and adjudicates every changed binding -- on this same PR it correctly demanded 255 `TargetChanged` rows. It does not inspect prose, and it is not supposed to: DESIGN section 4c makes annotations invisible to every semantic pass, and a `.md` plan is further out still. So a fully green namespace wave is positive evidence about the executable binding surface and NO evidence about whether the prose describing that authority remains true. The two are complementary walls, not a strong and a weak version of one wall. **RECEIPT (warm-ant-80, 2026-09-05, source 06a30d90376d5fb1fef91d6963604e3b053b9df1).** The annotation on `gunbc.instruments.opaque_realization_census.read_emitted_tree` says `shell.Find.FilesByNameSorted` reports sort's success even when find fails, and says a partial walk cannot be detected until that transport changes. The named operation already captures find's exit status, sorts only after find succeeds, and returns the captured failure; its adjacent `find_pipe_shell_emit_dissolution_trigger` describes that repair. The pointer still resolves, but the proposition it accompanies has been superseded. While investigating an artifact-digest difference, a manager relied on the annotation and directed this lane to file the repaired transport defect as newly live. Reading the operation stopped that false filing; no new transport defect was established. The source comparison and the withdrawn instruction are the evidence here, not an execution of a broken listing. **SECOND RECEIPT (warm-ant-80, 2026-09-05, same source revision).** The header of `tools.emission_entry_instrument` says a scoped compile fits on the runner that killed a whole-corpus compile, citing `gunbc.whole_corpus_compile_admission`. That authority's scope note explicitly withdraws the fit claim, citing clever-tern-899's 2026-08-28 scoped control produced by `gunbc compile` with entry `src/v2/std/node.dag`: the observed demand exceeded that runner's reported budget. A manager relied on the surviving header to redirect this lane to a route as though its fit were established, then withdrew the instruction after reading the cited authority. The grain claim survives: one entry's closure differs from the whole-corpus population. The runner-specific fit claim does not. The instrument restated a proposition rather than deriving it, so the authority's withdrawal did not reach its restatement. **THE SAME FAILURE WITHOUT A NAME-REFRESH HUNK.** A repair or a withdrawn measurement can invalidate a sibling's prose while leaving every cited name unchanged. The repair's own dissolution trigger does not enumerate or adjudicate those descriptions. Here the surviving citations lent authority to claims that caused wrong instructions. This extends the specimen boundary beyond rename sweeps: a resolving pointer is no attestation that its surrounding proposition was rechecked after the referenced behavior changed. **RUNG: MITIGATABLE.** External review caught this instance; nothing in the tree can. The ceiling is not structural impossibility: the truth of an arbitrary proposition about a mechanism is not decidable from the Node tree, so the honest ceiling is MECHANICALLY PREVENTABLE. **NEXT-RUNG TRIGGER: a claim census** -- an instrument that takes the prose citations a rename or authority replacement TOUCHED and surfaces, for adjudication, the proposition each one sits inside, refusing the change until each is dispositioned. That is a real instrument and it does not exist; naming it here is the trigger, not a claim of coverage. **RECOGNITION RULE: any diff that updates a name inside prose it did not otherwise read.** The tell is a citation-only hunk -- one identifier changed, the surrounding sentence untouched. The obligation is that a sweep which refreshes a name has adopted the claim: re-read the proposition, and either confirm it against the current mechanism or mark it superseded. Updating the name alone is not the cheap half of the job, it is the half that manufactures the false attestation. - **a witness that fails to COMPILE is ABSENT rather than red, and running its SUBJECT proves nothing about it** (a claim file is committed, reviewed and merged while it cannot compile, so none of its assertions ever execute and the guarantee it was written to carry does not exist; the class it polices reports as walled while nothing walls it, which is worse than an absent witness because the file is cited as coverage by its own author and by its reviewers). WHY IT IS A CLASS AND NOT A SLIP: every ordinary signal is compatible with the dead file and NONE OF THEM IS MALFUNCTIONING. It emits no advisories, because a file that does not compile contributes none. It fails no assertions, because none run. It is ABSENT from the floor disposition rather than failing in it, so a disposition read shows nothing to investigate. `cargo fmt` is green because the file is well-formed text. The push is green. And a reviewer can APPROVE on a wholly correct reading of the source, because READING DOES NOT COMPILE. THE CORE IS A TWO-CLAIM COLLAPSE: `the compiler behaves correctly` and `the witness that enrols that behaviour as executing evidence works` are DIFFERENT CLAIMS, and running the subject as a direct fixture establishes only the first. The mechanism of the misread is that holding the stronger claim's output makes the weaker one feel already answered — the author has the passing fixtures in hand, which is exactly why the file meant to carry them never gets checked. RECOGNITION RULE: ask what would have caught it, and IF THE ONLY ANSWER IS A RUN AIMED AT A DIFFERENT QUESTION, the class has no dedicated detector. The specimen surfaced from a whole-tree advisory census re-derivation chasing an unrelated hypothesis about a denominator, as blocking error number one, after the arms were green, the PR was open and an APPROVE was already recorded. THE ONE INSTRUMENT THAT SEES IT is a whole-tree compile INCLUDING the test roots; compiling the subject, or entry-scoping to the witness itself, does not. Rung found at 1, mitigatable, held only by an unrelated run happening to range over the file, which is not a mechanism. CEILING 3, structurally guaranteed: the population is decidable and closed — every file under the claim roots — so a required step that compiles them and refuses a non-compiling claim file makes the state unwritable in an `Accepted` tree. NEXT TRIGGER, capability and not artifact: the required floor must COMPILE every claim-root file and refuse a claim file that does not compile, rather than only executing the claim files that compile — AN EXECUTION ROSTER KEYED TO COMPILING FILES CANNOT SEE A FILE THAT FELL OUT OF IT, the same shape as a check whose population IS its own roster. Specimen gunbc#10337: `brand_nominal_identity_witness_test` named a host builtin in its import list; four direct fixtures had already proven the compiler refuses, and the witness enrolling them had never run. -- **an excluded witness outlives the subject it names, and reads as coverage of a subject it can no longer see** (INVALID STATE: a witness compiles, is committed, and is kept off every executing cadence by a declared exclusion or freeze; the SUBJECT it names is then moved or deleted by a correct edit elsewhere; and because nothing executes the witness, nothing observes that it now names nothing. The roster still lists it, the exclusion row still explains why it is offline, and both statements are true -- while the assertion itself has become false and unfalsifiable at the same moment. HARM: it is cited as coverage, which is the decoration case DESIGN section 4b names, with the aggravating property that its offline standing is DOCUMENTED and therefore looks deliberate rather than neglected. A reader auditing what guards a class finds the witness, finds the exclusion row with its dissolution trigger, and concludes the coverage exists and is merely deferred. What actually exists is a row naming a path the tree does not contain. Every ordinary signal is compatible with it and none is malfunctioning: it compiles, the floor does not report it because the floor never admits it, and the exclusion roster counts it as an excluded member rather than as a broken one. THE SPECIMEN (2026-09-06, complexity-lens lane, live on main at the time of filing): v2.test.claim.complexity.accumulator_copy_roster_gate roster_glob_discovery_zero_suspects_within_ratchet names src/v2/workflow/glob_discovery_law.dag. That file moved to src/v2/test/workflow/ in the #9637 directory reorganisation and the row was not repointed. The entry is frozen in gunbc.witness_deferral_freeze AND discovery-excluded by the gunbc.ci_layer_roots pattern claim/complexity/accumulator_copy_roster_gate, so nothing has executed it since. Before the typed-read repair the row would have ABORTED the process on the missing path; after it, the row would return FALSE. Neither has ever been observed, because the witness does not run. AND THE COST GATE MAKES THE FALSE ROW CHEAPER TO LEAVE THAN TO FIX, WHICH IS THE HALF WORTH RECORDING. The repair is a one-line repoint. But the required floor admits a CHANGED witness regardless of any exclusion -- correctly, since an exclusion a witness could opt into on the commit that touches it would be an escape hatch -- and this entry's witnesses cost several times the per-witness CPU line v2.workflow.required_floor required_floor_claim_cpu_safety_limit_ms declares. So editing the row to make it true ADMITS it, and it is then preempted and BLOCKS the lane; leaving it false costs nothing. The lane that found this repointed the row, measured the block, and reverted the repair -- which is the correct local decision and the reason the class persists. A cost gate that prices repair above neglect converts a fixable staleness into a standing one. WHY IT IS NOT THE ROWS BESIDE IT, checked at identity grain over gunbc.recurring_failure_mode.roster. It is not `witness_that_fails_to_compile_is_absent_rather_than_red`: there the file cannot compile, so it is ABSENT from every roster and the misread is that nobody notices its absence. Here the witness compiles, is PRESENT in the rosters, and its offline standing is declared -- the misread is that a documented exclusion is taken for deferred coverage rather than for no coverage. The repair differs accordingly: that class is closed by compiling every claim-root file, which this witness passes. It is not `stale_claim_survives_its_own_correct_edit`: that class is PROSE that was true when written and became false through a correct edit elsewhere. This is an EXECUTABLE assertion in the same situation, and the difference is not academic -- a prose claim has no mechanism that could ever have caught it, while this one has a mechanism that is deliberately switched off. The staleness is therefore not merely undetected, it is undetectABLE while the exclusion stands, and the exclusion is the thing presented as the reason it is safe. RUNG FOUND AT 1, mitigatable: the harm is bounded only by the fact that an excluded witness makes no false GREEN claim in any run -- it makes no claim at all -- so what is misled is a reader, not a gate. Calling it higher would be inflation; nothing counts these rows or checks their subjects. CEILING 4, structurally impossible, and the reason it is that high rather than a validator is that the bad state is a DANGLING REFERENCE and nothing else. A witness names its subject by a path string today, which is the positional naming DESIGN section 3 already refuses for citations: any edit above or around the file silently invalidates it. If a subject were named by a symbol the containment tree resolves, a moved file could not leave a witness naming nothing -- the reference would fail to resolve and the entry would refuse at compile, whether or not anything ever executes it. NEXT-RUNG TRIGGER, NAMING THE CAPABILITY: witness subjects are named by a resolvable reference rather than by a path literal, so that a subject that moves breaks the naming rather than the meaning. SUFFICIENT FOR the excluded population specifically -- an executing witness already fails loudly when its subject moves, so a trigger satisfied only for witnesses that run restores nothing here; the whole population at risk is the one nothing runs. A roster-completeness check that merely verified every named path EXISTS would reach rung 2 and is worth having as the interim step, but it must not be reported as the ceiling: it leaves the path literal writable and re-checks it from outside rather than making the dangling state unconstructible. RECOGNITION RULE: for any witness that does not execute, ask whether its SUBJECT still exists, and ask what it would return today. If the answer to the second question is unknown to everyone including its author, the row is not deferred coverage; it is an unfalsifiable assertion with a documented reason for being unfalsifiable.) - **a step's INSTRUCTION and the TREE it operates on are resolved from different revisions** (INVALID STATE: the command a step runs -- which entry point, which function, which path -- is taken from revision A, while the working tree that command is pointed at is checked out at revision B, and nothing in the step binds the two or reports which is which. HARM: the step's refusal is REAL, typed and located, and it names a subject that never carried the instruction. Section 5's located diagnostic is intact and points at the wrong tree, so the repair it invites -- edit the module the error names -- is correct-looking and cannot work, because that module is not what disagreed. THIS IS NOT A STALE-BASE PROBLEM AND CALLING IT ONE SENDS THE FIX TO THE WRONG PLACE. Age of the base is neither necessary nor sufficient: a branch whose instruction and tree both come from the OLD revision is perfectly consistent and refuses nothing, and a branch that integrates the new revision is consistent again. What produces the failure is the SPLIT, and a step can hold it open at any base age. The reason it presents as staleness is that the split is invisible while the two revisions happen to agree, which is most of the time. **SPECIMEN, gunbc#10479, run 33926616205 job 101196425514 (2026-09-04).** The heal-generated-artifacts job refused with NoSuchFunction { name: heal_repair_declaration } while evaluating dag/gunbc/instruments/generated_artifact_gate.dag. The function and the invocation of it landed together in ONE commit, 865fddf03d (#10373) -- .dag definition and workflow argv in the same diff -- so the pair is atomic everywhere and no asymmetric merge resolution is involved. The split is the job's own construction: for a pull_request event GitHub supplies the WORKFLOW FILE from the merge ref, and this job declares ref: github.head_ref because it PUSHES the regeneration back to the branch and therefore cannot run detached on refs/pull/N/merge. Its checkout line reads git checkout --progress --force -B refs/remotes/origin/, not a merge ref. Measured at three revisions: branch head a86d7ed225 had definition 0 and invocation 0, a consistent OLD pair; origin/main had 1 and 1, a consistent NEW pair; what EXECUTED was the invocation from the merge ref against the definition from the branch head. git merge-base --is-ancestor 865fddf03d a86d7ed225 is false. Every other required job runs the merge ref and stayed green, which is why exactly one job reddened and why integrating main cleared it with nothing resolved by hand. **AND THE FIRST ACCOUNT OF THIS SPECIMEN WAS WRONG IN THE INSTRUCTIVE DIRECTION, which is why the population statement is written twice here.** It was first filed as any branch based before 865fddf03d will red this way -- a rule keyed on the BASE. A reviewer then established that both halves landed atomically and concluded the opposite, that a consistent old pair cannot refuse and so the cause must be an asymmetric merge resolution that took one side. Both readings check one ref and infer the other. The measurement that decides it is the checkout line in the job log, and neither account had read it. The population is: the branch HEAD predates the commit that added the invocation, AND the entry point is reached by a step that checks out the head rather than the merge ref -- narrower than base age in one direction, and broader than a bad merge resolution in the other. **SECOND SPECIMEN, gunbc#10490, AND IT IS A MATCHED PAIR RATHER THAN A SECOND SIGHTING.** Reached independently by another lane on a case neither party to the first diagnosis chose, and diagnosed correctly there before this row existed -- which is the evidence the roster is for: the class is recognisable in the wild, not only in retrospect. Re-measured here rather than accepted as reported. RED ARM, head c30c049f68: the definition is absent (git merge-base --is-ancestor 865fddf03d c30c049f68 is false), and on run 33930364431 heal-generated-artifacts failed while required-witnesses-build, required-witnesses-floor and the aggregator all succeeded -- one job red, and it is the one job whose ref differs from the workflow file it is running. GREEN ARM, head 5888fb0106 after integrating main: the definition is present. The property flips with the remedy, which makes the pair discriminating rather than illustrative, and per section 4b(4) it stays enrolled as the arm that would catch a regression in the eventual fix. ONE HONEST GAP IN THAT PAIR: at the time of writing, #10490 green run 33932538855 had not concluded, so the executed green arm is gunbc#10479 at 245416762b, where heal passed in 18m56s after exactly this remedy. Naming #10490 as an executed positive control before its run reports would be the unbacked-execution-claim failure inside the row that files a class about misattributed evidence. **SCOPE, STATED BECAUSE THE CLASS IS WRITTEN WIDER THAN ITS EVIDENCE.** The general shape is a step whose checkout ref differs from the ref that supplied the workflow file, and it is written at that grain deliberately: the next instance will be whichever step next needs to write back to the branch. But BOTH executed specimens are heal-generated-artifacts, which is the only such step in the required workflow today, so the evidence covers one job and the generalisation is reasoning. A reader deciding whether their own red is this class should check their step's ref: declaration, not assume the shape has been observed anywhere else. **DISTINCT FROM ITS THREE NEAREST NEIGHBOURS, and the repair law is what separates them.** upstream_carrier_substituted_for_the_consumer_selected_subject is about deriving a CLAIM about a consumer from a carrier the consumer transforms; there the defect is in a measurement and the repair is to measure the composed subject. Here nothing is measured -- the EXECUTOR pairs the two revisions itself, and the repair is in the step. verification_bound_to_the_revision_it_started_on is one tree MOVING across an interval; here the two revisions are fixed for the whole run and differ by construction, so waiting or re-running changes nothing. receipt_names_a_property_not_the_tree_it_holds_of is an expired GREEN that produces no signal at all; this class produces a loud, correctly typed RED whose subject attribution is false, which is a different failure to catch and a different one to trust. **RUNG: 1 (mitigatable).** The line stops, the diagnostic is typed and located, and the harm is bounded to a misattributed red plus the investigation it misdirects -- there is no silent wrongness here. **CEILING: 3 (structurally guaranteed), and not 4, for a stated reason:** which ref supplies a workflow file is GitHub's decision and sits outside the modeled guarantee, so no constructor of ours can make the two-ref pairing unwritable. What IS reachable is that a step cannot emit an unattributed refusal: the invocation is resolved against the tree the step checked out, and an entry that tree does not define refuses by naming BOTH revisions and classifying the cause as an instruction/subject split rather than as a defect in the module it names. **NEXT-RUNG TRIGGER, a capability and not an artifact: a step whose entry point is resolved from the revision it checked out, with the refusal carrying both revision identities.** What does NOT satisfy it: making this one job check out the merge ref, which it cannot do and still push; renaming or restoring the function; or adding a note telling readers to merge main, which is guidance rather than a mechanism. **RECOGNITION RULE: when a job refuses naming an entry point, function or path that is absent from the branch, ask which revision supplied the COMMAND and which supplied the TREE, and demand both as immutable shas.** The tell is that the refusal's own subject has no history on the branch at all -- git log -S on the branch is empty -- while git show : contains the invocation. Two refs, one of them never named in the error. - **a required check reaches a terminal refusal on the head and the same head is then landed by hand, so the check detects the invalid state without being a precondition of landing.** INVALID STATE: a committed emitted mirror differs from what its authority emits; the required comparator enumerates that mirror, observes the differing bytes, and reports failure before the merge, yet the merge completes. This is an admission failure after detection, not an inert or mis-scoped detector. HARM: hand-maintained projections reach main in either direction -- bytes the authority emits can disappear and bytes it does not emit can appear -- while every later branch inherits a red required lane. A separate actuator may repeatedly report green over its own population, making the repository look converged without touching the drifted mirrors. MEASURED SPECIMEN (gunbc#10273, merged as 5a12090e7a, 2026-09-04). The pull request hand-maintained two files in `v1_compiler.emitted_population`: it removed the 47-line rustc-judged `nested_refinement_cast_fixture_closure_discrimination` pair from `compiler_tests.rs` while emission still produced it, and added 39 lines to `std_measure.rs` that emission did not produce. The required build run 33908874519 executed `claim_executor --required-ci --required-lane build`, reported `planned=156 executed=156 adjudicated=156`, and at 19:17:52Z printed `required-ci: regen FAIL generated surface drift: compiler_tests.rs, std_measure.rs`; its aggregate required `witnesses` check concluded FAILURE at 19:30:00Z. The human merge occurred at 19:59:28Z. Thus a terminal refusal existed for twenty-nine minutes before landing. A second pull-request run was created at 19:59:46Z, after the merge, and is not the evidence this receipt rests on. THE SPECIMEN IS AN INSTANCE OF A MEASURED RATE, NOT AN OUTLIER. Reproduced on 2026-09-04 over the 25 most recently merged pull requests, all merged that same day: for each pull request, join its merged head SHA and `mergedAt` to that commit's check runs, retain `name == witnesses` with `completed_at < mergedAt`, and take the latest by completion time. The closed tally was 13 success, 9 failure, 3 undetermined, total 25. Thus 9 of 25 landed after a terminal red on that exact head, and 12 of 25 landed without an established green from this instrument. THE THREE ARE `undetermined`, NOT VIOLATIONS: no check named `witnesses` completed before their merge, but check names date a receipt and this census did not establish what an earlier roster called the executing check. The population is ONE DAY of merges and supports no extrapolation beyond those 25. The query was controlled first on gunbc#10273 and returned its known `failure` at 19:30:00Z; this matters because an earlier instrument passed an unsupported second `--arg` to `gh api --jq`, suppressed the resulting errors, and rendered every failure as an empty result. FOLLOW-UP RECEIPT, INDEPENDENTLY MEASURED 2026-09-05T03:58:38Z (gunbc#10500). This is the `undetermined` bucket, not another terminal refusal crossed and not an addition to the earlier day's census. `gh pr view 10500 --repo gunb-ai/gunbc --json mergedAt,headRefOid,state` identifies the merged head as `5c55457118814759467fe21c35419746ee920ba7` and the merge instant as 2026-09-05T03:49:17Z. Querying `repos/gunb-ai/gunbc/commits/5c55457118814759467fe21c35419746ee920ba7/check-runs?per_page=100&filter=all` returned `total_count=3`, checked nonzero and equal to the enumerated population before counting conclusions: `heal-generated-artifacts` (101243152637) started at 03:46:49Z, `required-witnesses-build` (101243152749) at 03:47:26Z, and `required-witnesses-floor` (101243152786) at 03:47:37Z, all on 2026-09-05 and all before the merge. At this later observation every `completed_at` and `conclusion` was null and every status was `in_progress`; no aggregate check-run named `witnesses` existed. The exact-head actions-runs query returned the `witnesses` pull_request workflow run 33942442212, created at 03:39:06Z and still in_progress. Thus the observed checks had begun before landing and remained nonterminal after it; no terminal verdict was available at the merge instant. A subsequent reading found `heal-generated-artifacts` completed successfully at 2026-09-05T04:04:43Z, strictly after `mergedAt`; the build and floor checks still had null completion fields. For those unfinished checks this receipt retains the dated null-completion observation rather than inventing a later verdict; when completion is reported, compare `completed_at` to `mergedAt`, not the current status label. Use the full head SHA and require a successful API response with a nonempty, fully enumerated population: an error or an empty result is not evidence of no refusal. Independently reading #10469's `mergedAt` gives 2026-09-05T02:18:23Z, placing this landing 1 hour 30 minutes 54 seconds after the row landed. The program that filed the row then populated its predicted undetermined bucket with its own PR while the row was live on main. Documentation had not made a terminal verdict a precondition of that landing path. This establishes neither a terminal-refusal violation of this row's primary subject nor that the merge was wrong, and attributes no actor or admission mechanism. DISTINGUISHING FACTS. `compiler_tests.rs` and `std_measure.rs` are members of `v1_compiler.emitted_population`, whose required regen phase compared all 156 planned mirrors and named both paths. They are not members of the distinct `gunbc.generated_artifact` `generated_artifact_registry`: the heal run on the same head reported `HealNoChange`, while the required generated-artifact phase reported `rostered=38 adjudicated=38 matches=38 drifted=0`. Heal runs `tools.generated_artifact_gate` `main_wet` and stages that 38-artifact registry; it does not run or install the 156-mirror required-regen candidate. Its green was true of its own population and could not converge either file. THE CONTENT WITNESS IS NOT THE MIRROR GATE. `nested_refinement_cast_fixture_closure_discrimination` is `#[ignore]`, and cargo test is on no CI step, so no executing merge-path gate covered what the deleted test asserts. The required regen phase nevertheless covered the containing mirror byte-for-byte and went red. Saying the test did not execute therefore explains why its semantics were not independently exercised; it does not explain or weaken the detected mirror drift. DISTINCT FROM `head_landed_by_hand_before_its_own_verification_reported`: there the landing precedes the head's terminal verdict, so improving detector speed cannot help and the record cannot say whether a reported refusal would have blocked. Here the required verdict was terminal and red twenty-nine minutes before the landing. Waiting longer was impossible as a repair because the answer had already arrived. Distinct from `green_reported_over_a_population_the_instrument_does_not_own`: that row concerns a green being read beyond its denominator. Heal exhibits that neighbouring shape here, but the landing defect is established by the red comparator that did own both files. RECOGNITION RULE: join the required check's subject SHA, conclusion time and conclusion to the landing's subject SHA and merge time. If the same head has a terminal required refusal before `merged_at`, yet lands, the detector is neither absent, inert nor late; the landing path did not make its refusal a precondition. Then enumerate every actuator separately before asking why a later green did not repair the state -- shared words such as generated and regen do not make two populations one. RUNG FOUND AT: mitigatable for admission. The comparator mechanically exposed the mirror drift at rung 2, but this row's subject is the consumer of that refusal: admission did not prevent the landing, and repair depended on a later author noticing the red residual and installing a full candidate. A correct detector whose refusal has no accountable consumer is observation, not rung 2 prevention on the landing path. This subsumes the otherwise-separate class `gate_refusal_has_no_accountable_consumer`: the unconsumed refusal is the mechanism established here, so a second row would give one class two authorities. CEILING: outside the modeled guarantee on the human hosting-platform path, and structurally guaranteed on a landing path this repository constructs; the class takes the weaker boundary honestly. The repository can model a landing whose accepted constructor requires a terminal success for the exact ref, but a person pressing the hosting platform's merge control is an external boundary until that path is removed or made to consume the modeled decision. Calling the existing required context a structural guarantee would contradict the specimen: its refusal was present and the external action still occurred. NEXT-RUNG TRIGGER, the same capability already named by `gunbc.guarantee_stall` `merge_admission_terminal_verdict_stall`: a landing path in which terminal SUCCESS for the exact ref that lands is a precondition, sufficient to refuse both absent evidence and an observed FAILURE, human actor included; until that exists, a landing observation that joins every merged head to the terminal verdict present at its merge instant and reports this class explicitly. Adding another comparator, widening heal, or speeding the existing run does not discharge it: the owned comparator already returned the right answer in time.) - **corroboration without an independent derivation axis** (a second reader reaches the same conclusion as the first, and the agreement is counted as confirmation -- but both readings ran the same query over the same lexical projection of the same subject, so the second signature carries no information the first did not. TWO SIGNATURES, ONE EVIDENCE LINEAGE. What makes it dangerous is that it looks exactly like independent replication from the outside, and it is REPORTED as replication: the second reader honestly says they checked. Specimen, measured 2026-09-04 on gunbc: two sessions independently concluded that HTTP listing pagination was modelled in this tree. Both read `extdeps.github.github` `default_per_page` and the `per_page` fields that consume it, and both substituted PAGE SIZE for CONTINUATION CLOSURE. The tree in fact carried no page parameter, no cursor advance, no Link-header handling and no page fold anywhere, so every listing read in the repository was capped at its first page -- and `extdeps.github.github` declared a `Pagination` type with `per_page`, `cursor` and `has_next` fields that NOTHING consumed, which is a shape with no execution behind it. **THE THREE CLAIMS THE SUBJECT ACTUALLY CARRIED, and conflating them is the mechanism rather than an incidental error: page SIZE (how many items one request asks for), cursor SHAPE (how the next request is named), and continuation CLOSURE (whether the sequence of requests was consumed until the authority said there was no next).** Only the first appears lexically in a request declaration. The third appears nowhere unless a fold exists, so a lexical reading can only ever return evidence about the first, and both readings returned it. **Recognition rule: when a second reading agrees, ask which DERIVATION AXIS it changed.** If both readings are the same query over the same projection, the agreement is one observation reported twice. A reading corroborates only when it moves an axis -- operation-graph reachability rather than token presence, an EXECUTING transition rather than a declaration, or a controlled multi-page mutation rather than a read of the shape that would serve one. Any one of those was available and cheap here, and any one would have returned the truth on first contact. **Distinct from `authority_substitution`, where prose invents an A-to-B arrow that no carrier states:** here every sentence either reader wrote about `per_page` was TRUE, and the defect is that a true fact about claim one was accepted as an answer about claim three. Distinct from `bound_shaped_closure`, which is about a CONSUMER concluding a requirement is discharged because a real number satisfied it; this is about two readers' agreement being read as evidence when it is one reading counted twice. Distinct from `reachability_read_as_occupancy`: that class is one substitution made once, this class is the reason such a substitution SURVIVES review. **DISTINCT FROM `population_selector_that_cannot_admit_a_counterexample`, and the boundary is stated rather than assumed because the two rows were filed the same night off the same specimen.** That row is about how a POPULATION is enumerated: a lexical proxy for the predicate makes the verdict unfalsifiable by construction, and this corroboration failure appears there as a receipt explaining why nobody caught it. THIS row is about when a SECOND READING is evidence at all, and it fires with a perfectly well-formed population -- two readers can share one lineage while reading a log, a dashboard or a diff, where no enumeration is involved and no proxy was adopted. **THE SUBJECT SPLIT IS THE SHARPEST TELL THAT THEY ARE DIFFERENT OBJECTS RATHER THAN TWO NAMES FOR ONE, and an earlier draft of this paragraph drew the line in the wrong place.** It claimed the discriminator was DECIDABILITY -- that the sibling row was statically decidable with ceiling 3 and a constructor for a trigger, against this row being undecidable at ceiling 1. That reading is refuted on the sibling row itself, which withdrew both the rung and the ceiling: whether a selector can admit a counterexample is a SEMANTIC relation between two expressions, not a shared-symbol question a lens can read, so that row is a ratchet at ceiling 1 for the same reason this one is. Two rows that are both undecidable cannot be told apart by decidability. **THE DISCRIMINATOR IS THE SUBJECT.** That row is about how a POPULATION WAS CONSTRUCTED -- a lexical proxy for the predicate makes the verdict unfalsifiable by construction, and this corroboration failure appears there as a receipt explaining why nobody caught it. THIS row is a property of the RELATIONSHIP BETWEEN TWO READERS, and it fires with a perfectly well-formed population: two readers can share one lineage while reading a log, a dashboard or a diff, where no enumeration exists and no proxy was adopted. A repair that closes either one leaves the other exactly where it stood. **RUNG FOUND AT: 1 (mitigatable)** -- the error was caught by a third reader who changed the axis, which is a person and not a mechanism. **CEILING: 1, and the reason is decidability rather than unbuilt work.** Whether two readings share an unstated premise is a property of how two agents derived a conclusion, not of any artifact in the tree; DESIGN section 5 names this the ratchet-forever case, and calling it mechanically preventable would be the `never` trap. What IS decidable, and was therefore repaired rather than rostered, is the specimen's own class: a truncated listing is unconstructible FOR ANY CONSUMER THAT READS THROUGH THE FOLD, because `std.page_fold` `PageFoldOutcome` carries the items only on the arm reached when the serving authority itself declared the listing closed, every other arm is a typed, located refusal carrying no partial result, and `PageFoldState` is `sole_constructor` so a closed-looking state cannot be forged to reach that arm without an authority that said the listing ended. **THE SCOPE OF THAT SENTENCE IS THE FOLD, NOT THE TREE, AND THE DIFFERENCE IS THE WHOLE REASON THIS ROW EXISTS.** No production listing operation consumes `std.page_fold` yet: the GitHub listing surfaces still expose only a page-size parameter, nothing imports `extdeps.github.pagination`, and `page_fold_live` is reached today only by its own witnesses. So every real listing read in this repository is STILL capped at its first page, and the repair to date is that the capability now exists and is executable rather than that any caller has been moved onto it. Writing this row as though the class were closed would be the same substitution the row is about -- a shape that exists read as a capability that runs -- committed by the very entry that names it. **THE REMAINING INTEGRATION OBLIGATION, stated so it can be checked rather than remembered:** one real paginated operation -- `extdeps.github.workflow_runs` `ListForRef` is the intended first -- must demand pages through `page_fold_live` and close on the authority's own Link-header end, with its own live-upstream witness. Until a production caller reads through the fold, the tree's truncation is mitigated by nothing at all; it is merely now REPAIRABLE without further modeling. That work is deliberately scoped out of the PR that landed this row, and this sentence is what makes its absence countable instead of forgotten. The `Pagination` type with no consumers was deleted in the same motion, since a declared shape with no execution behind it is the surface this class reads as capability. **NEXT-RUNG TRIGGER: none, and stating that honestly is the point** -- this row is a review-time recognition rule, and enumerating agreements more carefully does not retire it. What retires an INSTANCE is the axis change: name, before looking, what a second reading would have to execute for its agreement to mean anything.) From 33be45d0d990f81d98f0992d055fa6fd8690ead4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 6 Sep 2026 23:52:18 +0000 Subject: [PATCH 06/22] Use artifact_path as the docs-ledger location authority and record the executed stderr scrape. Deletes the duplicated path literals and the join test that policed them. Regenerates the failure-mode projection after the false type-name-only assertion is replaced with the EXIT=2 empty-stdout receipt. Co-authored-by: Cursor --- .../instruments/docs_projection_agreement.dag | 10 ++---- .../instruments/docs_projection_gate.dag | 33 +++++++++---------- ...rojections_have_no_completing_actuator.dag | 2 +- ...type_name_field_carries_rendered_value.dag | 2 +- ...rated_artifact_assessment_witness_test.dag | 11 ------- docs/design-failure-modes.md | 4 +++ 6 files changed, 23 insertions(+), 39 deletions(-) diff --git a/dag/gunbc/instruments/docs_projection_agreement.dag b/dag/gunbc/instruments/docs_projection_agreement.dag index 886313c3ce4..145c68195ae 100644 --- a/dag/gunbc/instruments/docs_projection_agreement.dag +++ b/dag/gunbc/instruments/docs_projection_agreement.dag @@ -2,17 +2,11 @@ module tools.docs_projection_agreement import std.process { ProcessExit, ExitSuccess, exit_failure } -// Same literals as gunbc.generated_artifact artifact_location for the two ledger variants. -// The join is test.claim.docs_projection_path_join_test / generated_artifact_assessment: -// artifact_path(DesignFailureModesArtifact) == docs_design_failure_modes_path. -data docs_design_failure_modes_path: String = "docs/design-failure-modes.md" -data docs_design_rung_drops_path: String = "docs/design-rung-drops.md" - // ONE PURE VERDICT OVER ONE PATH'S EXPECTED AND COMMITTED BYTES. Production regen and the // required generated-artifact phase both reach a ProcessExit through this function, so a fixture // can present drifted ledger bytes without resolving gunbc.generated_artifact_emit or writing -// the live tree. The path is an argument because the diagnostic must name the file; the bytes -// are arguments because a live-tree read is a different question (the wet gate). +// the live tree. The path is an argument because the diagnostic must name the file; production +// callers pass artifact_path from gunbc.generated_artifact, the single location authority. fn docs_projection_bytes_agree(path: String, expected: String, committed: String) -> ProcessExit { if expected == committed { diff --git a/dag/gunbc/instruments/docs_projection_gate.dag b/dag/gunbc/instruments/docs_projection_gate.dag index 184fd196d68..5ccf35867a2 100644 --- a/dag/gunbc/instruments/docs_projection_gate.dag +++ b/dag/gunbc/instruments/docs_projection_gate.dag @@ -3,26 +3,23 @@ module tools.docs_projection_gate import std.process { ProcessExit, ExitSuccess, exit_failure } import extdeps.filesystem.filesystem_io { Filesystem, filesystem_read_outcome, FilesystemReadSucceeded, FilesystemReadRefused } import gunbc.design_ledgers { expected_design_failure_modes_md, expected_design_rung_drops_md } -import tools.docs_projection_agreement { - docs_projection_bytes_agree, - docs_design_failure_modes_path, - docs_design_rung_drops_path +import gunbc.generated_artifact { + artifact_path, + DesignFailureModesArtifact, + DesignRungDropsArtifact } +import tools.docs_projection_agreement { docs_projection_bytes_agree } // THE CLEANLY-EXITING ACTUATOR FOR docs/*.md LEDGER PROJECTIONS. // // gunbc.generated_artifact_gate main_wet folds the WHOLE committed registry, and resolving // gunbc.generated_artifact_emit pulls every generator (plans, workflows, CAD, stage0 mirrors). -// Authors filing a recurring-failure-mode row then ran claim_executor --required-regen (which -// never owns these files) or main_wet (which does not return a ProcessExit on the runners that -// SIGKILL it), so three rostered rows sat in the authority while docs/design-failure-modes.md -// stayed stale and nothing that completes could say so. -// -// This entry imports the ledger renderer and the filesystem, not the registry emit match. Path -// strings here are the same literals gunbc.generated_artifact artifact_location uses for these -// two variants; test.claim.generated_artifact_assessment -// `docs_projection_actuator_paths_match_registry_ledger_paths` joins them to artifact_path so a -// rename of either side refuses. +// This entry still imports gunbc.generated_artifact for artifact_path only — the location +// authority — not generated_artifact_emit. Authors filing a recurring-failure-mode row then +// ran claim_executor --required-regen (which never owns these files) or main_wet (which does +// not return a ProcessExit on the runners that SIGKILL it), so three rostered rows sat in the +// authority while docs/design-failure-modes.md stayed stale and nothing that completes could +// say so. fn exit_ok(e: ProcessExit) -> Bool { match e { ExitSuccess => true _ => false } @@ -39,12 +36,12 @@ fn write_docs_projection(path: String, content: String) -> ProcessExit { func regen() -> ProcessExit { let first = write_docs_projection( - path: docs_design_failure_modes_path, + path: artifact_path(a: DesignFailureModesArtifact), content: expected_design_failure_modes_md() ) if exit_ok(e: first) { write_docs_projection( - path: docs_design_rung_drops_path, + path: artifact_path(a: DesignRungDropsArtifact), content: expected_design_rung_drops_md() ) } else { @@ -65,12 +62,12 @@ fn agree_one(path: String, expected: String) -> ProcessExit { func main() -> ProcessExit { let first = agree_one( - path: docs_design_failure_modes_path, + path: artifact_path(a: DesignFailureModesArtifact), expected: expected_design_failure_modes_md() ) if exit_ok(e: first) { agree_one( - path: docs_design_rung_drops_path, + path: artifact_path(a: DesignRungDropsArtifact), expected: expected_design_rung_drops_md() ) } else { diff --git a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag index 029b97ab630..7e2ab9f33e7 100644 --- a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag +++ b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag @@ -8,7 +8,7 @@ data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode identity: "docs_markdown_projections_have_no_completing_actuator" as NonEmptyStr, receipts: [ - "**generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `classify_exit`'s non-variant arm puts ctx.format_value into the NotProcessExit payload field named type_name; `exit_status_for_class` interpolates that field into stderr and exits 2. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` is exact: nothing on stdout, not a print-and-succeed. The markdown IS in the diagnostic text, EXTRACTABLE ONLY BY SCRAPING AN UNVERSIONED DIAGNOSTIC — the delimiters are the prose wrapping the interpolated field (returned-open-quote and not-ProcessExit-close-quote), unenrolled and free to change with any edit to that message. That is worse than a clean ProcessExit actuator and better than impossible, and it is still a complete argument for a ProcessExit regen. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. ", + "**generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. The refusal exits 2 with EMPTY STDOUT and interpolates the rendered value into the diagnostic on stderr: classify_exit's non-variant arm is type_name: ctx.format_value(other). Executed: wise-badger-902, EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown heading; quick-heron-85, proj3.err 1062137 bytes, same delimiters. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` forbids print-AND-SUCCEED, and holds. The content is therefore obtainable ONLY by scraping a half-megabyte stderr diagnostic between delimiters that are message prose (returned-open-quote and not-ProcessExit-close-quote), unversioned and enrolled by nothing. Extractable only by scraping an unversioned diagnostic — which is why the ProcessExit regen is the repair. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. ", "SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. ", diff --git a/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag b/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag index 3695f40d9f0..c3e93587ad6 100644 --- a/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag +++ b/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag @@ -10,7 +10,7 @@ data type_name_field_carries_rendered_value: RecurringFailureMode = RecurringFai receipts: [ "**a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. ", - "SPECIMEN, 2026-09-06, three conclusions from two lanes and their manager on the same function in twenty minutes. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs `NotProcessExit` with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the same interpolation and relayed that a String projector yields the word String. quick-heron-85 held proj3.err at 1062137 bytes opening with expected_design_failure_modes_md returned the markdown heading and closing with not ProcessExit — they extracted between those delimiters. The manager then read `classify_exit`'s final arm and retracted. ", + "SPECIMEN, 2026-09-06, executed both ways. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs NotProcessExit with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the interpolation site and ruled the same. quick-heron-85 held proj3.err at 1062137 bytes. The manager then RAN the projector: EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown. The field name said type; the field held format_value. ", "THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. ", diff --git a/dag/test/claim/generated_artifact_assessment_witness_test.dag b/dag/test/claim/generated_artifact_assessment_witness_test.dag index fccabcb03f5..0b3bb0d334d 100644 --- a/dag/test/claim/generated_artifact_assessment_witness_test.dag +++ b/dag/test/claim/generated_artifact_assessment_witness_test.dag @@ -20,20 +20,9 @@ import gunbc.generated_artifact_assessment { import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } import std.process { ExitFailure, ExitSuccess } import tools.generated_artifact_gate { generated_artifact_agreement_verdict } -import tools.docs_projection_agreement { - docs_design_failure_modes_path, - docs_design_rung_drops_path -} data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly -// THE EXCEPTION IS EXACTLY THE TWO LEDGER PROJECTIONS, not all documentation and not a copied -// path roster in the driver. Both directions make widening or silently dropping either ledger red. -test fn docs_projection_actuator_paths_match_registry_ledger_paths() -> Bool { - docs_design_failure_modes_path == artifact_path(a: DesignFailureModesArtifact) - && docs_design_rung_drops_path == artifact_path(a: DesignRungDropsArtifact) -} - test fn only_design_ledgers_delegate_concurrent_regeneration_to_heal() -> Bool { let paths = heal_regenerated_after_provisional_merge_paths() count(paths) == 2 diff --git a/docs/design-failure-modes.md b/docs/design-failure-modes.md index 3f6e5aa0fd8..5d404f3c9df 100644 --- a/docs/design-failure-modes.md +++ b/docs/design-failure-modes.md @@ -165,6 +165,8 @@ The exact `RecurringFailureMode.identity` population, which no other projection - `permissiveness_promoted_into_a_higher_safety_rung` - `native_stack_recursion_unbounded_over_corpus_shaped_structure` - `evaluation_model_cleanup_undeclared` +- `docs_markdown_projections_have_no_completing_actuator` +- `type_name_field_carries_rendered_value` --- @@ -345,3 +347,5 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a refusal that is the FINAL policy is written up as a rung below a more capable one, and a next-rung trigger is filed promising to admit the case it deliberately refuses** (INVALID STATE: a class refuses some input; an author reasons that a future mechanism could HANDLE that input rather than refuse it, concludes the refusal is therefore below its ceiling, and files a DESIGN 4b(2) next-rung trigger naming the more capable mechanism. HARM: the trigger becomes a standing obligation to BREAK the semantics the refusal was chosen to protect, and it is discharged by making the system MORE PERMISSIVE, which moves the class DOWN the ladder while the roster records a climb. The row then reads as a known gap on the way to being closed, so the deliberate boundary loses its author and the next person to touch it satisfies the trigger in good faith. **THE RECOGNITION RULE, SCOPED TO WHAT IT CAN ACTUALLY CARRY: EXPANDING A DELIBERATELY CHOSEN REFUSAL BOUNDARY IS NOT A SAFETY CLIMB MERELY BECAUSE IT ADMITS MORE CASES.** Read the proposed trigger and ask whether discharging it would make the system ACCEPT AN INPUT THE DESIGN DELIBERATELY REFUSES. If so, the trigger is a capability request wearing a safety rung's clothes and belongs in a product decision rather than on the ladder. THE LADDER MEASURES HOW A DECISION IS ENFORCED, NOT HOW MANY CASES IT ADMITS -- but only the DECISION's enforcement, which is why the boundary must be a chosen one for this row to apply. **THE RULE WAS STATED TOO WIDELY IN THIS ROW'S FIRST WRITING AND IS CORRECTED HERE RATHER THAN SOFTENED.** It said a genuine next-rung trigger ALWAYS preserves which inputs are admitted and which refused, changing only whether the refusal is checked or unwritable. THAT IS FALSE ACROSS THE LADDER, and the counterexample is an ordinary rung-1 to rung-2 climb: a failure that previously OCCURRED and was contained -- by a total operation, a rollback, an isolation boundary -- begins being BLOCKED once a gate exposes it, so the admitted set genuinely shrinks and behaviour genuinely changes. A universal rule that forbids that would refuse most real climbs, and a row whose recognition rule fires on legitimate work is worse than no row: it teaches reviewers to discount it. So the class is scoped to a DELIBERATELY CHOSEN refusal boundary, which is the population both receipts below actually inhabit. **THE ORDER OF THE TWO QUESTIONS IS WHAT ACTUALLY GOES WRONG, AND IT IS AN ORDER ERROR RATHER THAN AN IGNORANCE ERROR.** 4b(2) says a class below its ceiling must name its next-rung trigger. That obligation is CONDITIONAL, and the condition is the part that gets skipped: the class must first be ESTABLISHED to sit below a ceiling. An author who feels the obligation before performing the derivation will always find a trigger, because a more capable mechanism can be imagined for any refusal. So the diligent-seeming move -- 'the rule says name a trigger, so I will name one' -- is exactly the move that manufactures the defect, and reviewers reward it because a row with a trigger looks more complete than a row without one. **BOUNDARY AGAINST ceiling_never_exercised_for_a_population_the_census_cannot_plan AND AGAINST AN UNDERIVED CEILING, which are the two nearest neighbours and arrive from the opposite direction.** Asserting a ceiling NUMBER without deriving it claims the class cannot climb; this class assumes without deriving that the class CAN climb. Both skip the same derivation and they fail in opposite directions, which is why they are separate rows rather than one: the repair for an underived ceiling is to derive it and possibly climb, and the repair here is to derive it and possibly DELETE THE TRIGGER. A single row would make one of those two repairs invisible. **RECEIPT, gunbc.scm.merge_base, 2026-09-06.** The squash merge refuses divergent per-path content at MANIFEST GRAIN rather than merging file contents line by line. This author wrote that absent line-level merging was 'a stated scope with a next-rung trigger: a text-difference capability'. The side-chat reviewer refused it: refusing divergent file contents can be the FINAL safety policy of the operation, and a text-difference capability increases ADMITTED MERGE POWER rather than safety. If the semantics are deliberately 'never synthesize file content neither author supplied', then text-level auto-merge VIOLATES those semantics instead of climbing toward them, and the trigger would have been an obligation to break the design. The trigger was not written; what was written instead is that manifest-grain conflict is a deliberate semantic boundary, with whether line-level merging is wanted AT ALL left open as a product question rather than presumed. **A SECOND RECEIPT ON THE SAME LANE AND THE SAME DAY, WHICH IS WHY THIS IS A CLASS AND NOT AN ANECDOTE.** gunbc.scm.integration records the consumed source as a commit reference, which stops being a durable anchor if source commit records are ever PHYSICALLY DELETED rather than merely kept out of target ancestry. The reflex was to file that as a next-rung trigger. It is not one: retirement is a capability nobody has asked for, nothing establishes that this cut sits below a ceiling, and a trigger would have committed a future author to build durable provenance for a deletion feature that may never exist. It is recorded in that module as a FUTURE BOUNDARY -- what would have to be designed IF the capability is ever wanted -- which carries the same information and creates no obligation. THE REPAIR IS USUALLY A RESTATEMENT, NOT A DELETION: the knowledge is worth keeping, and only its status as a debt is false. **CEILING: UNDERIVED, AND STATED SO RATHER THAN GUESSED, on this row's own rule.** Deciding mechanically whether a proposed trigger widens the admitted set requires comparing the semantics before and after a mechanism that does not exist yet, and this author has not established that comparison is decidable from modeled facts. Asserting a rung here would be the underived-ceiling error in a row about underived ceilings. What is honest today: the class is caught by review, it has been caught twice by one reviewer on one lane, and the instrument that found both was a question a person asked rather than anything executed. NEXT TRIGGER: none is named, because naming one would require knowing the ceiling. - **a walk whose native-stack depth grows with the depth of corpus-shaped data, with no declared bound** (the compiler recurses once per level of a structure whose depth is an input, not a constant. Past some corpus the process aborts on stack exhaustion. HARM: the abort is not a diagnostic -- it is a SIGSEGV turned into `thread ... has overflowed its stack` and an abort, so it carries no span, no diagnostic identity, and no partial result. The run neither succeeds nor refuses, and every downstream reader sees a crashed process rather than a located refusal. Worse, the abort is silent about its own cause -- it kills the process before the summary line, so the very output that would characterise the input is the output you lose. **THE DISTINGUISHING FACT IS THAT THE RECURSION NEED NOT APPEAR IN OUR SOURCE AT ALL.** The class has two instances and they differ in exactly this, which is why one row covers both and why looking only for authored recursion finds half of it. AUTHORED INSTANCE: `v1.compiler.infer_types` `node_type_shape` and `node_type_shape_argument_list` are mutually recursive over type structure with no depth bound, rendering `Primitive(...)`, `Named(...)`, `Product(...)`, `Coproduct(...)` by descending into children. A reader can find this one, and grep for `fn` names finds it. COMPILER-GENERATED INSTANCE: `v1.std.core` `Node` carries `children: Rc>>` (and `params`, `uses`, `properties`, `body`, `type_annotation` likewise) with no hand-written `Drop`, so the DESTRUCTOR rustc synthesises recurses once per level on every release of a deep graph. **No reading of our own source can find it, because it is not in our source.** Grepping for recursion, auditing every `fn`, and eliminating every in-window candidate by inspection all report health -- and did, on the specimen below, where three code candidates in the failing window were correctly eliminated by reading while the actual recursion was in glue no one had written. **RECOGNITION RULE, and it is about the DATA, not about the code: any type whose values are trees or chains of unbounded depth carries this class in every operation that touches a whole value -- including the ones you did not write.** Construction, traversal, rendering, cloning, comparison, hashing, serialisation and RELEASE are all depth-recursive by default. The question to ask of a recursive carrier is not `which of my functions recurse` but `what happens to a value of this type at 10^6 levels`, and the honest answer is usually that nobody knows because nobody has one. **RECEIPT, EXECUTED, WITH A PAIRED CONTROL AND ONE CHANGED LINE** (2026-09-05, quick-heron-85, during the NS-N containment cut). A two-file subject -- `std.measure` plus `test.claim.long.reference_closure_equivalence_test`, compiled with `src/v2` and `src/v1` as name-pool roots -- aborts with `stack overflow` after four pipeline phases and before its diagnostic summary prints. THE STACK-THRESHOLD INSTRUMENT TURNS THE BINARY INTO A SCALAR: bisecting `ulimit -s` gives the minimum stack at which each configuration completes. Each file ALONE completes at 1024 KiB; the PAIR completes at 147456 KiB and overflows at 139264 KiB. A factor of at least 144, closed on both sides, which is what establishes the recursion as FINITE AND DEEP rather than unbounded-in-the-limit -- an unlimited stack completes. THE DISCRIMINATOR IS THE ONE-LINE PROBE, not the threshold: `std::mem::forget(resolved.clone())` at the head of `v1.compiler.compile` `emit_resolved_for_target_selected`, holding files, binary invocation, source roots, stack size and diagnostics fixed and changing ONLY whether the tree is ever released. Paired at the same stock 8192 KiB: unpatched OVERFLOW, leaked binary exit 1, zero `stack overflow`, and the summary line prints (496 blocking, 2643 advisory). The destructor was the recursion. **THE PROBE MUST FORGET A CLONE, NOT THE VALUE**, and this is the part worth carrying to the next investigator: `resolved` is an `Rc`, so forgetting IT merely drops one handle and moves the destructor to whoever holds the last one -- reading as a false PERSIST and killing a true hypothesis. Forgetting a CLONE pins the outer refcount above zero, so the contents can never be released no matter who else holds a handle. That converts `check the precondition` into `remove the failure mode`, which is the difference between a probe you have to trust and one that cannot lie. **WHAT DID NOT DISCRIMINATE, recorded because each looked decisive and cost a cycle:** diagnostic COUNT is blind -- a probe rendering 8,000 diagnostics does not overflow while this configuration's 3,139 does; per-diagnostic SHAPE is blind unless the shape is in the graph; and a 2,000-level-deep authored expression chain completes. The depth that matters is the depth of the structure RELEASED, which no probe varying the diagnostic list can reach. **RUNG FOUND AT: 1, MITIGATABLE, by fail-stop and process isolation -- AND SECTION 5's FAIL-CLOSED INTERFACE IS STILL VIOLATED. Both halves belong in the sentence.** The mitigation is real and is what earns the rung: the abort is a fail-stop at a process boundary, and that boundary PREVENTS AN ACCEPTED RESULT OR ANY OTHER SUCCESS PUBLICATION -- the run dies before its summary line, so no consumer receives a partial or fabricated answer. Section 4b names isolation among rung-1 mechanisms, so the absence of a typed result does not deny rung 1. **BUT RUNG 1 DOES NOT REHABILITATE THE SIGSEGV**: section 5 independently requires every path to succeed fully or fail with a TYPED, LOCATED diagnostic, and an abort carrying no subject, no span and no diagnostic identity fails that badly. The deficit is a rung-1 class with a standing section 5 interface violation, which is what makes it rank for repair. **AN EARLIER REVISION OF THIS ROW PLACED THE CLASS OUTSIDE THE LADDER, AND THAT WAS WRONG IN A WAY WORTH KEEPING VISIBLE** (corrected 2026-09-06, native review 5124409497). Section 4b's only exclusions are silent wrongness below the floor and the adjacent column of external reality, undecidable properties and undeclared intent; this class is none of those, and the row REFUTED ITS OWN EXCLUSION two sentences later by stating that the depth property is decidable, that nothing waits on external reality, and that both a declared bound and an iterative implementation are authorable. **EXCUSING A CLASS FROM HAVING A RUNG HAS THE SAME PRACTICAL EFFECT AS INFLATING ONE -- IT NEVER RANKS**, which is the failure this ledger exists to prevent, arriving from the low side rather than the high one. **CEILING: 3, STRUCTURALLY GUARANTEED -- NOT 4, BECAUSE DECIDABLE DOES NOT IMPLY RUNG 4.** Depth is decidable and nothing waits on external reality, but decidability establishes only that a structural decision procedure is POSSIBLE. If an unbounded recursive carrier remains WRITABLE while the compiler derives a proof or a refusal for it, that is rung 3. **RUNG 4 REQUIRES THAT THE INVALID STATE HAVE NO CONSTRUCTOR IN THE CANONICAL MODEL, AND THIS ROW CANNOT NAME THE CONSTRUCTOR-ELIMINATION PATH**: no canonical carrier model is in hand in which data-dependent native-stack recursion is inexpressible, and a recursive carrier is a thing the substrate is meant to be able to declare. The ceiling is therefore raised to 4 only by a future row that NAMES that model; until then, claiming 4 here would be the inflation this ledger refuses. **THE REPAIR MAY NOT BE A BIGGER STACK, A FATTER THREAD, OR A RAISED BUDGET, AND THE LEAK IS AN INSTRUMENT RATHER THAN AN ARM.** All four leave the abort exactly where it was and move the corpus at which it fires, which is section 5's absorbing fallback in its purest form: the failure arm WIDENS instead of refusing, the widening is denominated in the corpus rather than the change, and the deficit stops ranking for repair because the symptom moved. The two honest arms are a release and traversal whose depth does not grow with the data -- an explicit iterative teardown and an explicit worklist walk -- or a DECLARED depth bound enforced at construction, refusing a too-deep structure with a typed, located diagnostic naming the carrier and the level. **THE TRIGGER BELOW IS A DELIBERATE 1-TO-3 JUMP, STATED SO IT IS NOT MISREAD AS THE ADJACENT RUNG.** Deriving the risk from the declared carrier and failing compilation when an unbounded carrier lacks an iterative release or a declared bound is a STRUCTURAL GUARANTEE, rung 3 -- it skips rung 2 rather than passing through it. The rung-2 form, if the jump proves too large to take in one step, is a generated lens or an adversarial-depth witness that reliably exposes and blocks the class WHILE THE INVALID IMPLEMENTATION STAYS WRITABLE and safety depends on that mechanism executing and staying enrolled. **NEXT-RUNG TRIGGER, and it names a CAPABILITY rather than a repair to one carrier:** a mechanism that derives, from a declared carrier, whether any operation on it -- including generated ones -- can recurse without a bound, so that authoring an unbounded-depth carrier without an iterative release or a declared bound fails to compile. Repairing `Node` alone does not retire this row; it removes one instance, and `node_type_shape` would still stand, as would every carrier authored tomorrow. **BOUNDED AGAINST TWO NEIGHBOURS.** Section 5's absorbing fallback is a mechanism that WIDENS when it cannot answer precisely; here nothing widens and nothing is answered -- the process dies. And `right_censored_cost_read_as_exact` concerns a measurement truncated by a limit; the resemblance is superficial, because there the run completes and reports a bounded number, whereas here the limit destroys the run and its report together, which is precisely why the threshold instrument had to be built to get any number at all. **WHY THIS RECEIPT NAMES NO PRODUCER, STATED RATHER THAN LEFT AS AN OMISSION** (raised as a section 6 objection, review 61308). Section 6 requires a measurement to be cited by naming the producer that re-derives it, because A TRANSCRIBED NUMBER IS UNREACHABLE FROM THE THING THAT OWNS IT AND ROTS WITHOUT ANYONE TOUCHING EITHER END. **THE OWNER OF THESE FIGURES NO LONGER EXISTS.** Every number here was observed on a configuration requiring a resolver rule that is not on main and, by ruling, never lands in that shape; the specimen is extinct, so NO ENTRY POINT CAN RE-DERIVE IT AND NONE IS OWED -- which is the case section 6 names in its own final clause, a one-off rather than an instrument. What is recorded is therefore a HISTORICAL OBSERVATION OF AN EXTINCT CONDITION, not a standing measurement, and it cannot rot in section 6's sense because it asserts nothing about the current tree. Without this sentence a reader concludes the producer was omitted rather than impossible, which is why the absence is declared instead of left to be noticed. **THE POPULATION ON MAIN IS UNMEASURED AND THE ROW DOES NOT CLAIM OTHERWISE, WHICH IS A DENOMINATOR FACT AND NOT BOOKKEEPING** (established 2026-09-06, after this row was first written, by the three-cell probe that identified what builds the chain). THE SOLE SPECIMEN WAS PRODUCED BY A RESOLVER RULE THAT IS NOT PRESENT ON MAIN: admitting the one refused callable owner collapses the specimen from 1,800,008 to 85, with the deepest site moving to an unrelated module, and the same instrument reads 85 under main and under the merge base. **THE TWO LEVELS ARE CONDITIONAL OBSERVATIONS AND MUST NOT BE LIFTED OUT AS PROPERTIES OF "MAIN" AND "THE CUT".** They were measured under one fixed execution condition -- that corpus, that traversal order, that pool construction, that binary and instrumentation -- and the pool construction in force is itself order-dependent, so the absolute levels are traversal-order-contaminated. WHAT GENERALISES IS THE DISCRIMINATING EFFECT OF CHANGING THE PREDICATE, not the numbers: a defective traversal regime can move the absolute state presented to every cell alike, but it cannot manufacture a DIFFERENCE between cells when that regime was held fixed. So the collapse stands as causal evidence with its held condition attached, and 85 is not a fact about main. **WHETHER ORDINARY CORPUS GROWTH ALONE CAN REACH COMPARABLE DEPTH IS UNMEASURED, AND NOBODY HAS SHOWN THAT IT CAN.** THE PRODUCING DEFECT IS NAMED SO THE TWO ROWS CONNECT RATHER THAN LEAVING THE NEXT READER TO RE-DERIVE IT: the depth was reached because an UNRESOLVABLE CALL WAS ELABORATED RATHER THAN REFUSED, which is filed as the call-site form of `gunbc.recurring_failure_mode.state_space_conflation` -- the callable-lookup carrier collapses builtin, locally-bound and genuinely-missing into one constructor, and the consumer reads that collapsed absence as affirmative formal authority. **STATED IN BOTH DIRECTIONS, BECAUSE EACH HALF GUARDS AGAINST THE OPPOSITE MISREAD.** A reader must not conclude that corpus growth alone reaches this depth -- that is the unmeasured half above. A reader must ALSO not conclude that the structure was a harness artefact: NOTHING WAS FORCED IN THE RUN THAT PRODUCED IT. The 1,800,008 baseline is an ordinary compile under an ordinary resolver rule; the forced admissions were the two PROBES placed either side of that baseline to identify its cause. The structure was real, an accepted compile built it, and the class it exhibits is independent of whichever defect supplied the depth -- which is why this row stands on its own and does not wait on that defect's repair. **THE SECOND DEFECT THIS SPECIMEN EXPOSES IS NOT THIS ROW AND MUST NOT BE ABSORBED INTO IT:** nothing in two source files is 10^5 levels deep, so some pipeline stage BUILDS a structure far deeper than its input -- a chain where a flat structure was intended. That is a separate defect, for which this class is only the reporter, and it will be pathological for every consumer that walks the structure rather than only the one that releases it.) - **DESTRUCTIVE CLEANUP CORRECTNESS DEPENDING ON AN UNDECLARED EVALUATION-MODEL PROPERTY. THREE TRANSPORTS ARE CORRECT BY ACCIDENT OF REALIZATION, NOT BY CONSTRUCTION. INVALID STATE:** the interpreter implements `let` as eager/strict (eval_let evaluates the value expression unconditionally at the point of binding). Nothing declares this as a language guarantee — it is a property of the current interpreter realization, not a declared semantic axis. The companion divergence — that interpreter `&&` evaluates both operands while emitted Rust `&&` short-circuits — is already filed as realization_arms_diverge_on_whether_the_program_refuses. It does not affect these transports: their cleanups are let-bound, not &&-bound, and under eager let a short-circuit && cannot change any conjunction's value because it skips only the right operand when the left is false — a path where the answer is already determined and the skipped operand is a pure field read (.success) on a record whose effect already fired at its let binding. STRUCTURALLY, no transport in this class depends on && eagerness, and no addition to the corpus can create such a dependence as long as let stays eager. If `let` became lazy, cleanup would never run in the namespace_import_closure_behavioral_transport (where cleanup is bound but never consumed), and would be unobserved in the other two (where cleanup .success would not be consumed, though the eager `let` at binding point would still fire). The distinction matters: 'effect does not reach the filesystem' is a leak; 'effect reaches the filesystem but its success is unobserved' is an unobserved cleanup failure — different harms, different severities.POPULATION, BOUNDED AND ENUMERABLE — THE THREE TRANSPORTS: `tools.self_host_logic_behavioral_transport::slb_comparison_observation` (let-bound cleanup_src/cleanup_out consumed in the staged conjunction), `tools.emit_host_transport::run_rust_smoke` / `::run_python_smoke` / `::run_go_smoke` (let-bound cleanup consumed in a conjunction), `tools.namespace_import_closure_behavioral_transport::nic_emitted_crate_evidence` (let-bound cleanup_src/cleanup_out NEVER consumed — correct only because let is eager).CEILING: cleanup that does not depend on the evaluation model at all — an effect the transport sequences explicitly rather than one it gets from strictness. Every transport's destructive cleanup would be structurally sequenced (e.g. a `do cleanup after body` pattern) rather than relying on let-eagerness for its effect to reach the filesystem.NEXT-RUNG TRIGGER, A CAPABILITY — either (a) the evaluation model declared as a typed guarantee that a check can execute against — a property of the interpreter that is named, bounded, and verifiable, not prose — or (b) every transport whose correctness turns on eager evaluation sequences its destructive effects explicitly rather than inheriting them from let-strictness. Both capabilities are genuine transitions; neither is satisfied by documentation or by this row.CORPUS POPULATION MEASUREMENT — the && divergence has no live hazard in this corpus: zero sites place a non-total or effectful operation directly as the right operand of `&&` (the two found are read-only). Twenty sites let-bind destructive effects and then consume their .success in a `&&` chain; under the structural argument above, none depends on && eagerness. HOWEVER, A=0 is a SURVIVOR PROPERTY OF THE STRICTER ARM HAVING FILTERED THE CORPUS, not evidence the class is harmless. The interpreter evaluates both operands unconditionally, so any author who wrote `guard && dangerous` would have hit the refusal while authoring and rewritten before commit. Under a short-circuit `&&`, authors would write these patterns freely and they would compile — and the emitted arm would silently produce a wrong answer where the interpreter refuses. That is the existing row's subject.INSTRUMENT — DISCRIMINATING PROBES, THEIR NAMES AND INVOCATION, SO A FUTURE READER CAN RE-DERIVE THIS FINDING AND RE-RUN IT. Five executed probes in module `test.claim.eval_model_probe` (dag/test/claim/eval_model_probe_test.dag): `let_is_eager_when_unused` (let-binds a Remove without consuming its result; PASS means eager), `let_is_eager_when_consumed` (positive control), `and_demands_right_even_when_left_false` (let-binds false and Remove separately, && computes; PASS means Remove ran despite false), `and_short_circuit_direct_probe` (false && Remove directly; PASS means Remove ran), `and_with_true_left_removes_file` (positive control). Each was run as: `gunbc run --source-root dag --source-root src/v2 --entry dag/test/claim/eval_model_probe_test.dag --function --claim-run`. All five returned PASS (exit 0). Probes observed the filesystem side effect out-of-band (shell.Test.IsFile) rather than consuming a returned value, so no measurement-created dependency could confound the result. +- **generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `classify_exit`'s non-variant arm puts ctx.format_value into the NotProcessExit payload field named type_name; `exit_status_for_class` interpolates that field into stderr and exits 2. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` is exact: nothing on stdout, not a print-and-succeed. The markdown IS in the diagnostic text, EXTRACTABLE ONLY BY SCRAPING AN UNVERSIONED DIAGNOSTIC — the delimiters are the prose wrapping the interpolated field (returned-open-quote and not-ProcessExit-close-quote), unenrolled and free to change with any edit to that message. That is worse than a clean ProcessExit actuator and better than impossible, and it is still a complete argument for a ProcessExit regen. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. RUNG FOUND AT: outside the ladder until a completing content agreement exists — silent wrongness, not a weak wall. After a ProcessExit projector and a required content compare execute, mechanically preventable: the stale file remains writable. CEILING: structurally guaranteed when membership of every committed generated `docs/` path in a content-agreement adjudicator is derived from `gunbc.generated_artifact` `committed_generated_artifacts`, so omitting the arm is a compile refusal. NEXT-RUNG TRIGGER, a capability: required-run dispatch consumes a content agreement — byte identity of generated vs committed — over every committed generated artifact whose location directory is `docs`, derived from the registry so adding a committed `docs/` path without that agreement will not compile. A trigger naming one command, one test file, or `docs_projection_gate.dag` is less than the capability and would fire while edit-only drift can still sit uncompared.) +- **a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. SPECIMEN, 2026-09-06, three conclusions from two lanes and their manager on the same function in twenty minutes. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs `NotProcessExit` with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the same interpolation and relayed that a String projector yields the word String. quick-heron-85 held proj3.err at 1062137 bytes opening with expected_design_failure_modes_md returned the markdown heading and closing with not ProcessExit — they extracted between those delimiters. The manager then read `classify_exit`'s final arm and retracted. THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. RUNG FOUND AT: mitigatable — the wrong inference is a human reading; the runtime still refuses with exit 2 and does not print-and-succeed. CEILING: structurally impossible when `NotProcessExit` cannot be constructed with a rendered value under a field named for a type — two variants, or a field named for what it actually holds. NEXT-RUNG TRIGGER, a capability: `ExitClass` distinguishes a type-name refusal from a rendered-value refusal in the type, so interpolating one as the other will not compile.) From f53f5383a728b89c9c0c3072499e86efd250e6c2 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 00:10:02 +0000 Subject: [PATCH 07/22] Record that the NotProcessExit control never calls classify_exit, then regenerate the projection. The enrolled test hand-builds type_name Bool, so its RED cannot see the rendered-value arm. docs_projection_gate main exited 0 on that regenerated tree. Co-authored-by: Cursor --- .../type_name_field_carries_rendered_value.dag | 2 ++ docs/design-failure-modes.md | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag b/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag index c3e93587ad6..d5df646e3d3 100644 --- a/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag +++ b/dag/gunbc/recurring_failure_mode/type_name_field_carries_rendered_value.dag @@ -12,6 +12,8 @@ data type_name_field_carries_rendered_value: RecurringFailureMode = RecurringFai "SPECIMEN, 2026-09-06, executed both ways. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs NotProcessExit with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the interpolation site and ruled the same. quick-heron-85 held proj3.err at 1062137 bytes. The manager then RAN the projector: EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown. The field name said type; the field held format_value. ", + "WHY THE FORK SURVIVED, and it is a section 4b evidence gap not a naming smell. `non_process_exit_refuses_rather_than_printing_and_succeeding` hand-constructs ExitClass::NotProcessExit with type_name the literal Bool and starts from an already-classified value. It never calls `classify_exit`. Nothing executing joins the field's name to its contents on the non-variant path — the only enrolled evidence feeds the field a literal that matches its name, which is exactly the input that cannot discriminate. The control's RED is unauthorable for this defect, so it was permanently green over the case that mattered and got cited as coverage. A field carrying two facts is ordinary; a field carrying two facts with a control that only ever sees one of them is why three of us read adjacent code correctly and reached three answers. ", + "THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. ", "RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. ", diff --git a/docs/design-failure-modes.md b/docs/design-failure-modes.md index 5d404f3c9df..86de138a72c 100644 --- a/docs/design-failure-modes.md +++ b/docs/design-failure-modes.md @@ -347,5 +347,5 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a refusal that is the FINAL policy is written up as a rung below a more capable one, and a next-rung trigger is filed promising to admit the case it deliberately refuses** (INVALID STATE: a class refuses some input; an author reasons that a future mechanism could HANDLE that input rather than refuse it, concludes the refusal is therefore below its ceiling, and files a DESIGN 4b(2) next-rung trigger naming the more capable mechanism. HARM: the trigger becomes a standing obligation to BREAK the semantics the refusal was chosen to protect, and it is discharged by making the system MORE PERMISSIVE, which moves the class DOWN the ladder while the roster records a climb. The row then reads as a known gap on the way to being closed, so the deliberate boundary loses its author and the next person to touch it satisfies the trigger in good faith. **THE RECOGNITION RULE, SCOPED TO WHAT IT CAN ACTUALLY CARRY: EXPANDING A DELIBERATELY CHOSEN REFUSAL BOUNDARY IS NOT A SAFETY CLIMB MERELY BECAUSE IT ADMITS MORE CASES.** Read the proposed trigger and ask whether discharging it would make the system ACCEPT AN INPUT THE DESIGN DELIBERATELY REFUSES. If so, the trigger is a capability request wearing a safety rung's clothes and belongs in a product decision rather than on the ladder. THE LADDER MEASURES HOW A DECISION IS ENFORCED, NOT HOW MANY CASES IT ADMITS -- but only the DECISION's enforcement, which is why the boundary must be a chosen one for this row to apply. **THE RULE WAS STATED TOO WIDELY IN THIS ROW'S FIRST WRITING AND IS CORRECTED HERE RATHER THAN SOFTENED.** It said a genuine next-rung trigger ALWAYS preserves which inputs are admitted and which refused, changing only whether the refusal is checked or unwritable. THAT IS FALSE ACROSS THE LADDER, and the counterexample is an ordinary rung-1 to rung-2 climb: a failure that previously OCCURRED and was contained -- by a total operation, a rollback, an isolation boundary -- begins being BLOCKED once a gate exposes it, so the admitted set genuinely shrinks and behaviour genuinely changes. A universal rule that forbids that would refuse most real climbs, and a row whose recognition rule fires on legitimate work is worse than no row: it teaches reviewers to discount it. So the class is scoped to a DELIBERATELY CHOSEN refusal boundary, which is the population both receipts below actually inhabit. **THE ORDER OF THE TWO QUESTIONS IS WHAT ACTUALLY GOES WRONG, AND IT IS AN ORDER ERROR RATHER THAN AN IGNORANCE ERROR.** 4b(2) says a class below its ceiling must name its next-rung trigger. That obligation is CONDITIONAL, and the condition is the part that gets skipped: the class must first be ESTABLISHED to sit below a ceiling. An author who feels the obligation before performing the derivation will always find a trigger, because a more capable mechanism can be imagined for any refusal. So the diligent-seeming move -- 'the rule says name a trigger, so I will name one' -- is exactly the move that manufactures the defect, and reviewers reward it because a row with a trigger looks more complete than a row without one. **BOUNDARY AGAINST ceiling_never_exercised_for_a_population_the_census_cannot_plan AND AGAINST AN UNDERIVED CEILING, which are the two nearest neighbours and arrive from the opposite direction.** Asserting a ceiling NUMBER without deriving it claims the class cannot climb; this class assumes without deriving that the class CAN climb. Both skip the same derivation and they fail in opposite directions, which is why they are separate rows rather than one: the repair for an underived ceiling is to derive it and possibly climb, and the repair here is to derive it and possibly DELETE THE TRIGGER. A single row would make one of those two repairs invisible. **RECEIPT, gunbc.scm.merge_base, 2026-09-06.** The squash merge refuses divergent per-path content at MANIFEST GRAIN rather than merging file contents line by line. This author wrote that absent line-level merging was 'a stated scope with a next-rung trigger: a text-difference capability'. The side-chat reviewer refused it: refusing divergent file contents can be the FINAL safety policy of the operation, and a text-difference capability increases ADMITTED MERGE POWER rather than safety. If the semantics are deliberately 'never synthesize file content neither author supplied', then text-level auto-merge VIOLATES those semantics instead of climbing toward them, and the trigger would have been an obligation to break the design. The trigger was not written; what was written instead is that manifest-grain conflict is a deliberate semantic boundary, with whether line-level merging is wanted AT ALL left open as a product question rather than presumed. **A SECOND RECEIPT ON THE SAME LANE AND THE SAME DAY, WHICH IS WHY THIS IS A CLASS AND NOT AN ANECDOTE.** gunbc.scm.integration records the consumed source as a commit reference, which stops being a durable anchor if source commit records are ever PHYSICALLY DELETED rather than merely kept out of target ancestry. The reflex was to file that as a next-rung trigger. It is not one: retirement is a capability nobody has asked for, nothing establishes that this cut sits below a ceiling, and a trigger would have committed a future author to build durable provenance for a deletion feature that may never exist. It is recorded in that module as a FUTURE BOUNDARY -- what would have to be designed IF the capability is ever wanted -- which carries the same information and creates no obligation. THE REPAIR IS USUALLY A RESTATEMENT, NOT A DELETION: the knowledge is worth keeping, and only its status as a debt is false. **CEILING: UNDERIVED, AND STATED SO RATHER THAN GUESSED, on this row's own rule.** Deciding mechanically whether a proposed trigger widens the admitted set requires comparing the semantics before and after a mechanism that does not exist yet, and this author has not established that comparison is decidable from modeled facts. Asserting a rung here would be the underived-ceiling error in a row about underived ceilings. What is honest today: the class is caught by review, it has been caught twice by one reviewer on one lane, and the instrument that found both was a question a person asked rather than anything executed. NEXT TRIGGER: none is named, because naming one would require knowing the ceiling. - **a walk whose native-stack depth grows with the depth of corpus-shaped data, with no declared bound** (the compiler recurses once per level of a structure whose depth is an input, not a constant. Past some corpus the process aborts on stack exhaustion. HARM: the abort is not a diagnostic -- it is a SIGSEGV turned into `thread ... has overflowed its stack` and an abort, so it carries no span, no diagnostic identity, and no partial result. The run neither succeeds nor refuses, and every downstream reader sees a crashed process rather than a located refusal. Worse, the abort is silent about its own cause -- it kills the process before the summary line, so the very output that would characterise the input is the output you lose. **THE DISTINGUISHING FACT IS THAT THE RECURSION NEED NOT APPEAR IN OUR SOURCE AT ALL.** The class has two instances and they differ in exactly this, which is why one row covers both and why looking only for authored recursion finds half of it. AUTHORED INSTANCE: `v1.compiler.infer_types` `node_type_shape` and `node_type_shape_argument_list` are mutually recursive over type structure with no depth bound, rendering `Primitive(...)`, `Named(...)`, `Product(...)`, `Coproduct(...)` by descending into children. A reader can find this one, and grep for `fn` names finds it. COMPILER-GENERATED INSTANCE: `v1.std.core` `Node` carries `children: Rc>>` (and `params`, `uses`, `properties`, `body`, `type_annotation` likewise) with no hand-written `Drop`, so the DESTRUCTOR rustc synthesises recurses once per level on every release of a deep graph. **No reading of our own source can find it, because it is not in our source.** Grepping for recursion, auditing every `fn`, and eliminating every in-window candidate by inspection all report health -- and did, on the specimen below, where three code candidates in the failing window were correctly eliminated by reading while the actual recursion was in glue no one had written. **RECOGNITION RULE, and it is about the DATA, not about the code: any type whose values are trees or chains of unbounded depth carries this class in every operation that touches a whole value -- including the ones you did not write.** Construction, traversal, rendering, cloning, comparison, hashing, serialisation and RELEASE are all depth-recursive by default. The question to ask of a recursive carrier is not `which of my functions recurse` but `what happens to a value of this type at 10^6 levels`, and the honest answer is usually that nobody knows because nobody has one. **RECEIPT, EXECUTED, WITH A PAIRED CONTROL AND ONE CHANGED LINE** (2026-09-05, quick-heron-85, during the NS-N containment cut). A two-file subject -- `std.measure` plus `test.claim.long.reference_closure_equivalence_test`, compiled with `src/v2` and `src/v1` as name-pool roots -- aborts with `stack overflow` after four pipeline phases and before its diagnostic summary prints. THE STACK-THRESHOLD INSTRUMENT TURNS THE BINARY INTO A SCALAR: bisecting `ulimit -s` gives the minimum stack at which each configuration completes. Each file ALONE completes at 1024 KiB; the PAIR completes at 147456 KiB and overflows at 139264 KiB. A factor of at least 144, closed on both sides, which is what establishes the recursion as FINITE AND DEEP rather than unbounded-in-the-limit -- an unlimited stack completes. THE DISCRIMINATOR IS THE ONE-LINE PROBE, not the threshold: `std::mem::forget(resolved.clone())` at the head of `v1.compiler.compile` `emit_resolved_for_target_selected`, holding files, binary invocation, source roots, stack size and diagnostics fixed and changing ONLY whether the tree is ever released. Paired at the same stock 8192 KiB: unpatched OVERFLOW, leaked binary exit 1, zero `stack overflow`, and the summary line prints (496 blocking, 2643 advisory). The destructor was the recursion. **THE PROBE MUST FORGET A CLONE, NOT THE VALUE**, and this is the part worth carrying to the next investigator: `resolved` is an `Rc`, so forgetting IT merely drops one handle and moves the destructor to whoever holds the last one -- reading as a false PERSIST and killing a true hypothesis. Forgetting a CLONE pins the outer refcount above zero, so the contents can never be released no matter who else holds a handle. That converts `check the precondition` into `remove the failure mode`, which is the difference between a probe you have to trust and one that cannot lie. **WHAT DID NOT DISCRIMINATE, recorded because each looked decisive and cost a cycle:** diagnostic COUNT is blind -- a probe rendering 8,000 diagnostics does not overflow while this configuration's 3,139 does; per-diagnostic SHAPE is blind unless the shape is in the graph; and a 2,000-level-deep authored expression chain completes. The depth that matters is the depth of the structure RELEASED, which no probe varying the diagnostic list can reach. **RUNG FOUND AT: 1, MITIGATABLE, by fail-stop and process isolation -- AND SECTION 5's FAIL-CLOSED INTERFACE IS STILL VIOLATED. Both halves belong in the sentence.** The mitigation is real and is what earns the rung: the abort is a fail-stop at a process boundary, and that boundary PREVENTS AN ACCEPTED RESULT OR ANY OTHER SUCCESS PUBLICATION -- the run dies before its summary line, so no consumer receives a partial or fabricated answer. Section 4b names isolation among rung-1 mechanisms, so the absence of a typed result does not deny rung 1. **BUT RUNG 1 DOES NOT REHABILITATE THE SIGSEGV**: section 5 independently requires every path to succeed fully or fail with a TYPED, LOCATED diagnostic, and an abort carrying no subject, no span and no diagnostic identity fails that badly. The deficit is a rung-1 class with a standing section 5 interface violation, which is what makes it rank for repair. **AN EARLIER REVISION OF THIS ROW PLACED THE CLASS OUTSIDE THE LADDER, AND THAT WAS WRONG IN A WAY WORTH KEEPING VISIBLE** (corrected 2026-09-06, native review 5124409497). Section 4b's only exclusions are silent wrongness below the floor and the adjacent column of external reality, undecidable properties and undeclared intent; this class is none of those, and the row REFUTED ITS OWN EXCLUSION two sentences later by stating that the depth property is decidable, that nothing waits on external reality, and that both a declared bound and an iterative implementation are authorable. **EXCUSING A CLASS FROM HAVING A RUNG HAS THE SAME PRACTICAL EFFECT AS INFLATING ONE -- IT NEVER RANKS**, which is the failure this ledger exists to prevent, arriving from the low side rather than the high one. **CEILING: 3, STRUCTURALLY GUARANTEED -- NOT 4, BECAUSE DECIDABLE DOES NOT IMPLY RUNG 4.** Depth is decidable and nothing waits on external reality, but decidability establishes only that a structural decision procedure is POSSIBLE. If an unbounded recursive carrier remains WRITABLE while the compiler derives a proof or a refusal for it, that is rung 3. **RUNG 4 REQUIRES THAT THE INVALID STATE HAVE NO CONSTRUCTOR IN THE CANONICAL MODEL, AND THIS ROW CANNOT NAME THE CONSTRUCTOR-ELIMINATION PATH**: no canonical carrier model is in hand in which data-dependent native-stack recursion is inexpressible, and a recursive carrier is a thing the substrate is meant to be able to declare. The ceiling is therefore raised to 4 only by a future row that NAMES that model; until then, claiming 4 here would be the inflation this ledger refuses. **THE REPAIR MAY NOT BE A BIGGER STACK, A FATTER THREAD, OR A RAISED BUDGET, AND THE LEAK IS AN INSTRUMENT RATHER THAN AN ARM.** All four leave the abort exactly where it was and move the corpus at which it fires, which is section 5's absorbing fallback in its purest form: the failure arm WIDENS instead of refusing, the widening is denominated in the corpus rather than the change, and the deficit stops ranking for repair because the symptom moved. The two honest arms are a release and traversal whose depth does not grow with the data -- an explicit iterative teardown and an explicit worklist walk -- or a DECLARED depth bound enforced at construction, refusing a too-deep structure with a typed, located diagnostic naming the carrier and the level. **THE TRIGGER BELOW IS A DELIBERATE 1-TO-3 JUMP, STATED SO IT IS NOT MISREAD AS THE ADJACENT RUNG.** Deriving the risk from the declared carrier and failing compilation when an unbounded carrier lacks an iterative release or a declared bound is a STRUCTURAL GUARANTEE, rung 3 -- it skips rung 2 rather than passing through it. The rung-2 form, if the jump proves too large to take in one step, is a generated lens or an adversarial-depth witness that reliably exposes and blocks the class WHILE THE INVALID IMPLEMENTATION STAYS WRITABLE and safety depends on that mechanism executing and staying enrolled. **NEXT-RUNG TRIGGER, and it names a CAPABILITY rather than a repair to one carrier:** a mechanism that derives, from a declared carrier, whether any operation on it -- including generated ones -- can recurse without a bound, so that authoring an unbounded-depth carrier without an iterative release or a declared bound fails to compile. Repairing `Node` alone does not retire this row; it removes one instance, and `node_type_shape` would still stand, as would every carrier authored tomorrow. **BOUNDED AGAINST TWO NEIGHBOURS.** Section 5's absorbing fallback is a mechanism that WIDENS when it cannot answer precisely; here nothing widens and nothing is answered -- the process dies. And `right_censored_cost_read_as_exact` concerns a measurement truncated by a limit; the resemblance is superficial, because there the run completes and reports a bounded number, whereas here the limit destroys the run and its report together, which is precisely why the threshold instrument had to be built to get any number at all. **WHY THIS RECEIPT NAMES NO PRODUCER, STATED RATHER THAN LEFT AS AN OMISSION** (raised as a section 6 objection, review 61308). Section 6 requires a measurement to be cited by naming the producer that re-derives it, because A TRANSCRIBED NUMBER IS UNREACHABLE FROM THE THING THAT OWNS IT AND ROTS WITHOUT ANYONE TOUCHING EITHER END. **THE OWNER OF THESE FIGURES NO LONGER EXISTS.** Every number here was observed on a configuration requiring a resolver rule that is not on main and, by ruling, never lands in that shape; the specimen is extinct, so NO ENTRY POINT CAN RE-DERIVE IT AND NONE IS OWED -- which is the case section 6 names in its own final clause, a one-off rather than an instrument. What is recorded is therefore a HISTORICAL OBSERVATION OF AN EXTINCT CONDITION, not a standing measurement, and it cannot rot in section 6's sense because it asserts nothing about the current tree. Without this sentence a reader concludes the producer was omitted rather than impossible, which is why the absence is declared instead of left to be noticed. **THE POPULATION ON MAIN IS UNMEASURED AND THE ROW DOES NOT CLAIM OTHERWISE, WHICH IS A DENOMINATOR FACT AND NOT BOOKKEEPING** (established 2026-09-06, after this row was first written, by the three-cell probe that identified what builds the chain). THE SOLE SPECIMEN WAS PRODUCED BY A RESOLVER RULE THAT IS NOT PRESENT ON MAIN: admitting the one refused callable owner collapses the specimen from 1,800,008 to 85, with the deepest site moving to an unrelated module, and the same instrument reads 85 under main and under the merge base. **THE TWO LEVELS ARE CONDITIONAL OBSERVATIONS AND MUST NOT BE LIFTED OUT AS PROPERTIES OF "MAIN" AND "THE CUT".** They were measured under one fixed execution condition -- that corpus, that traversal order, that pool construction, that binary and instrumentation -- and the pool construction in force is itself order-dependent, so the absolute levels are traversal-order-contaminated. WHAT GENERALISES IS THE DISCRIMINATING EFFECT OF CHANGING THE PREDICATE, not the numbers: a defective traversal regime can move the absolute state presented to every cell alike, but it cannot manufacture a DIFFERENCE between cells when that regime was held fixed. So the collapse stands as causal evidence with its held condition attached, and 85 is not a fact about main. **WHETHER ORDINARY CORPUS GROWTH ALONE CAN REACH COMPARABLE DEPTH IS UNMEASURED, AND NOBODY HAS SHOWN THAT IT CAN.** THE PRODUCING DEFECT IS NAMED SO THE TWO ROWS CONNECT RATHER THAN LEAVING THE NEXT READER TO RE-DERIVE IT: the depth was reached because an UNRESOLVABLE CALL WAS ELABORATED RATHER THAN REFUSED, which is filed as the call-site form of `gunbc.recurring_failure_mode.state_space_conflation` -- the callable-lookup carrier collapses builtin, locally-bound and genuinely-missing into one constructor, and the consumer reads that collapsed absence as affirmative formal authority. **STATED IN BOTH DIRECTIONS, BECAUSE EACH HALF GUARDS AGAINST THE OPPOSITE MISREAD.** A reader must not conclude that corpus growth alone reaches this depth -- that is the unmeasured half above. A reader must ALSO not conclude that the structure was a harness artefact: NOTHING WAS FORCED IN THE RUN THAT PRODUCED IT. The 1,800,008 baseline is an ordinary compile under an ordinary resolver rule; the forced admissions were the two PROBES placed either side of that baseline to identify its cause. The structure was real, an accepted compile built it, and the class it exhibits is independent of whichever defect supplied the depth -- which is why this row stands on its own and does not wait on that defect's repair. **THE SECOND DEFECT THIS SPECIMEN EXPOSES IS NOT THIS ROW AND MUST NOT BE ABSORBED INTO IT:** nothing in two source files is 10^5 levels deep, so some pipeline stage BUILDS a structure far deeper than its input -- a chain where a flat structure was intended. That is a separate defect, for which this class is only the reporter, and it will be pathological for every consumer that walks the structure rather than only the one that releases it.) - **DESTRUCTIVE CLEANUP CORRECTNESS DEPENDING ON AN UNDECLARED EVALUATION-MODEL PROPERTY. THREE TRANSPORTS ARE CORRECT BY ACCIDENT OF REALIZATION, NOT BY CONSTRUCTION. INVALID STATE:** the interpreter implements `let` as eager/strict (eval_let evaluates the value expression unconditionally at the point of binding). Nothing declares this as a language guarantee — it is a property of the current interpreter realization, not a declared semantic axis. The companion divergence — that interpreter `&&` evaluates both operands while emitted Rust `&&` short-circuits — is already filed as realization_arms_diverge_on_whether_the_program_refuses. It does not affect these transports: their cleanups are let-bound, not &&-bound, and under eager let a short-circuit && cannot change any conjunction's value because it skips only the right operand when the left is false — a path where the answer is already determined and the skipped operand is a pure field read (.success) on a record whose effect already fired at its let binding. STRUCTURALLY, no transport in this class depends on && eagerness, and no addition to the corpus can create such a dependence as long as let stays eager. If `let` became lazy, cleanup would never run in the namespace_import_closure_behavioral_transport (where cleanup is bound but never consumed), and would be unobserved in the other two (where cleanup .success would not be consumed, though the eager `let` at binding point would still fire). The distinction matters: 'effect does not reach the filesystem' is a leak; 'effect reaches the filesystem but its success is unobserved' is an unobserved cleanup failure — different harms, different severities.POPULATION, BOUNDED AND ENUMERABLE — THE THREE TRANSPORTS: `tools.self_host_logic_behavioral_transport::slb_comparison_observation` (let-bound cleanup_src/cleanup_out consumed in the staged conjunction), `tools.emit_host_transport::run_rust_smoke` / `::run_python_smoke` / `::run_go_smoke` (let-bound cleanup consumed in a conjunction), `tools.namespace_import_closure_behavioral_transport::nic_emitted_crate_evidence` (let-bound cleanup_src/cleanup_out NEVER consumed — correct only because let is eager).CEILING: cleanup that does not depend on the evaluation model at all — an effect the transport sequences explicitly rather than one it gets from strictness. Every transport's destructive cleanup would be structurally sequenced (e.g. a `do cleanup after body` pattern) rather than relying on let-eagerness for its effect to reach the filesystem.NEXT-RUNG TRIGGER, A CAPABILITY — either (a) the evaluation model declared as a typed guarantee that a check can execute against — a property of the interpreter that is named, bounded, and verifiable, not prose — or (b) every transport whose correctness turns on eager evaluation sequences its destructive effects explicitly rather than inheriting them from let-strictness. Both capabilities are genuine transitions; neither is satisfied by documentation or by this row.CORPUS POPULATION MEASUREMENT — the && divergence has no live hazard in this corpus: zero sites place a non-total or effectful operation directly as the right operand of `&&` (the two found are read-only). Twenty sites let-bind destructive effects and then consume their .success in a `&&` chain; under the structural argument above, none depends on && eagerness. HOWEVER, A=0 is a SURVIVOR PROPERTY OF THE STRICTER ARM HAVING FILTERED THE CORPUS, not evidence the class is harmless. The interpreter evaluates both operands unconditionally, so any author who wrote `guard && dangerous` would have hit the refusal while authoring and rewritten before commit. Under a short-circuit `&&`, authors would write these patterns freely and they would compile — and the emitted arm would silently produce a wrong answer where the interpreter refuses. That is the existing row's subject.INSTRUMENT — DISCRIMINATING PROBES, THEIR NAMES AND INVOCATION, SO A FUTURE READER CAN RE-DERIVE THIS FINDING AND RE-RUN IT. Five executed probes in module `test.claim.eval_model_probe` (dag/test/claim/eval_model_probe_test.dag): `let_is_eager_when_unused` (let-binds a Remove without consuming its result; PASS means eager), `let_is_eager_when_consumed` (positive control), `and_demands_right_even_when_left_false` (let-binds false and Remove separately, && computes; PASS means Remove ran despite false), `and_short_circuit_direct_probe` (false && Remove directly; PASS means Remove ran), `and_with_true_left_removes_file` (positive control). Each was run as: `gunbc run --source-root dag --source-root src/v2 --entry dag/test/claim/eval_model_probe_test.dag --function --claim-run`. All five returned PASS (exit 0). Probes observed the filesystem side effect out-of-band (shell.Test.IsFile) rather than consuming a returned value, so no measurement-created dependency could confound the result. -- **generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. `classify_exit`'s non-variant arm puts ctx.format_value into the NotProcessExit payload field named type_name; `exit_status_for_class` interpolates that field into stderr and exits 2. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` is exact: nothing on stdout, not a print-and-succeed. The markdown IS in the diagnostic text, EXTRACTABLE ONLY BY SCRAPING AN UNVERSIONED DIAGNOSTIC — the delimiters are the prose wrapping the interpolated field (returned-open-quote and not-ProcessExit-close-quote), unenrolled and free to change with any edit to that message. That is worse than a clean ProcessExit actuator and better than impossible, and it is still a complete argument for a ProcessExit regen. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. RUNG FOUND AT: outside the ladder until a completing content agreement exists — silent wrongness, not a weak wall. After a ProcessExit projector and a required content compare execute, mechanically preventable: the stale file remains writable. CEILING: structurally guaranteed when membership of every committed generated `docs/` path in a content-agreement adjudicator is derived from `gunbc.generated_artifact` `committed_generated_artifacts`, so omitting the arm is a compile refusal. NEXT-RUNG TRIGGER, a capability: required-run dispatch consumes a content agreement — byte identity of generated vs committed — over every committed generated artifact whose location directory is `docs`, derived from the registry so adding a committed `docs/` path without that agreement will not compile. A trigger naming one command, one test file, or `docs_projection_gate.dag` is less than the capability and would fire while edit-only drift can still sit uncompared.) -- **a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. SPECIMEN, 2026-09-06, three conclusions from two lanes and their manager on the same function in twenty minutes. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs `NotProcessExit` with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the same interpolation and relayed that a String projector yields the word String. quick-heron-85 held proj3.err at 1062137 bytes opening with expected_design_failure_modes_md returned the markdown heading and closing with not ProcessExit — they extracted between those delimiters. The manager then read `classify_exit`'s final arm and retracted. THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. RUNG FOUND AT: mitigatable — the wrong inference is a human reading; the runtime still refuses with exit 2 and does not print-and-succeed. CEILING: structurally impossible when `NotProcessExit` cannot be constructed with a rendered value under a field named for a type — two variants, or a field named for what it actually holds. NEXT-RUNG TRIGGER, a capability: `ExitClass` distinguishes a type-name refusal from a rendered-value refusal in the type, so interpolating one as the other will not compile.) +- **generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. The refusal exits 2 with EMPTY STDOUT and interpolates the rendered value into the diagnostic on stderr: classify_exit's non-variant arm is type_name: ctx.format_value(other). Executed: wise-badger-902, EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown heading; quick-heron-85, proj3.err 1062137 bytes, same delimiters. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` forbids print-AND-SUCCEED, and holds. The content is therefore obtainable ONLY by scraping a half-megabyte stderr diagnostic between delimiters that are message prose (returned-open-quote and not-ProcessExit-close-quote), unversioned and enrolled by nothing. Extractable only by scraping an unversioned diagnostic — which is why the ProcessExit regen is the repair. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. RUNG FOUND AT: outside the ladder until a completing content agreement exists — silent wrongness, not a weak wall. After a ProcessExit projector and a required content compare execute, mechanically preventable: the stale file remains writable. CEILING: structurally guaranteed when membership of every committed generated `docs/` path in a content-agreement adjudicator is derived from `gunbc.generated_artifact` `committed_generated_artifacts`, so omitting the arm is a compile refusal. NEXT-RUNG TRIGGER, a capability: required-run dispatch consumes a content agreement — byte identity of generated vs committed — over every committed generated artifact whose location directory is `docs`, derived from the registry so adding a committed `docs/` path without that agreement will not compile. A trigger naming one command, one test file, or `docs_projection_gate.dag` is less than the capability and would fire while edit-only drift can still sit uncompared.) +- **a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. SPECIMEN, 2026-09-06, executed both ways. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs NotProcessExit with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the interpolation site and ruled the same. quick-heron-85 held proj3.err at 1062137 bytes. The manager then RAN the projector: EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown. The field name said type; the field held format_value. THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. RUNG FOUND AT: mitigatable — the wrong inference is a human reading; the runtime still refuses with exit 2 and does not print-and-succeed. CEILING: structurally impossible when `NotProcessExit` cannot be constructed with a rendered value under a field named for a type — two variants, or a field named for what it actually holds. NEXT-RUNG TRIGGER, a capability: `ExitClass` distinguishes a type-name refusal from a rendered-value refusal in the type, so interpolating one as the other will not compile.) From ffaebe69e7282c9f71235ab8b08a6d9e94a2b0dc Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 00:39:04 +0000 Subject: [PATCH 08/22] Lift exit_ok to std.process so the three gate copies are one constructor. docs_projection_gate, ci_gates, and generated_artifact_gate now consume it; floor_effect_gate_witness follows. Co-authored-by: Cursor --- dag/gunbc/instruments/ci_gates.dag | 6 +----- dag/gunbc/instruments/docs_projection_gate.dag | 6 +----- .../instruments/floor_effect_gate_witness.dag | 17 +++++++++-------- .../instruments/generated_artifact_gate.dag | 6 +----- dag/std/process.dag | 7 +++++++ 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/dag/gunbc/instruments/ci_gates.dag b/dag/gunbc/instruments/ci_gates.dag index 2259cee8ad4..9bf104c12f6 100644 --- a/dag/gunbc/instruments/ci_gates.dag +++ b/dag/gunbc/instruments/ci_gates.dag @@ -1,6 +1,6 @@ module tools.ci_gates -import std.process { ProcessExit, ExitSuccess, exit_failure } +import std.process { ProcessExit, ExitSuccess, exit_failure, exit_ok } import gunbc.ci_gate { EmitHostGate, Gate, CheapClaimPoolGate } import gunbc.ci_spec { gunbc_ci_gates, CiSpec, @@ -18,10 +18,6 @@ import tools.prose_row_introduction_gate { run_prose_row_introduction_gate } import gunbc.rung_drop { rung_drop_declaration_message } import gunbc.rung_drop.source_root_ingest_gate_rung_drop { source_root_ingest_gate_rung_drop } -fn exit_ok(e: ProcessExit) -> Bool { - match e { ExitSuccess => true _ => false } -} - fn run_spec_gate(g: Gate) -> ProcessExit { match g { EmitHostGate => run_emit_host_ci_gate() diff --git a/dag/gunbc/instruments/docs_projection_gate.dag b/dag/gunbc/instruments/docs_projection_gate.dag index 5ccf35867a2..994a9431d0c 100644 --- a/dag/gunbc/instruments/docs_projection_gate.dag +++ b/dag/gunbc/instruments/docs_projection_gate.dag @@ -1,6 +1,6 @@ module tools.docs_projection_gate -import std.process { ProcessExit, ExitSuccess, exit_failure } +import std.process { ProcessExit, ExitSuccess, exit_failure, exit_ok } import extdeps.filesystem.filesystem_io { Filesystem, filesystem_read_outcome, FilesystemReadSucceeded, FilesystemReadRefused } import gunbc.design_ledgers { expected_design_failure_modes_md, expected_design_rung_drops_md } import gunbc.generated_artifact { @@ -21,10 +21,6 @@ import tools.docs_projection_agreement { docs_projection_bytes_agree } // authority while docs/design-failure-modes.md stayed stale and nothing that completes could // say so. -fn exit_ok(e: ProcessExit) -> Bool { - match e { ExitSuccess => true _ => false } -} - fn write_docs_projection(path: String, content: String) -> ProcessExit { let w = Filesystem.Write(path: path, content: content) if w.success { diff --git a/dag/gunbc/instruments/floor_effect_gate_witness.dag b/dag/gunbc/instruments/floor_effect_gate_witness.dag index 32e7d9b5a44..412536883e1 100644 --- a/dag/gunbc/instruments/floor_effect_gate_witness.dag +++ b/dag/gunbc/instruments/floor_effect_gate_witness.dag @@ -5,7 +5,8 @@ import gunbc.ci_spec { ExtdepsExternalAuthorityGate, DagCompileCleanGate, GeneratedArtifactDriftGate, ExtdepsScopePlacementGate, ProseRowIntroductionGate } -import tools.ci_gates { exit_ok, run_spec_gate } +import std.process { exit_ok } +import tools.ci_gates { run_spec_gate } import tools.extdeps_scope_placement_gate { scope_placement_failure_receipt } import tools.prose_row_introduction_gate { prose_row_gate_failure_receipt } import tools.dag_compile_clean_gate { dag_compile_clean_failure_receipt } @@ -37,27 +38,27 @@ import tools.generated_artifact_gate { generated_artifact_drift_failure_receipt // projection from this entry's declarations (see loudness_witness_note sequencing note). func emit_host_gate_passes() -> Bool { - tools.ci_gates.exit_ok(e: run_spec_gate(g: EmitHostGate)) + exit_ok(e: run_spec_gate(g: EmitHostGate)) } func cheap_claim_pool_gate_passes() -> Bool { - tools.ci_gates.exit_ok(e: run_spec_gate(g: CheapClaimPoolGate)) + exit_ok(e: run_spec_gate(g: CheapClaimPoolGate)) } func extdeps_external_authority_gate_passes() -> Bool { - tools.ci_gates.exit_ok(e: run_spec_gate(g: ExtdepsExternalAuthorityGate)) + exit_ok(e: run_spec_gate(g: ExtdepsExternalAuthorityGate)) } func dag_compile_clean_gate_passes() -> Bool { - tools.ci_gates.exit_ok(e: run_spec_gate(g: DagCompileCleanGate)) + exit_ok(e: run_spec_gate(g: DagCompileCleanGate)) } func generated_artifact_drift_gate_passes() -> Bool { - tools.ci_gates.exit_ok(e: run_spec_gate(g: GeneratedArtifactDriftGate)) + exit_ok(e: run_spec_gate(g: GeneratedArtifactDriftGate)) } func extdeps_scope_placement_gate_passes() -> Bool { - tools.ci_gates.exit_ok(e: run_spec_gate(g: ExtdepsScopePlacementGate)) + exit_ok(e: run_spec_gate(g: ExtdepsScopePlacementGate)) } func extdeps_scope_placement_gate_failure_receipt() -> String { @@ -65,7 +66,7 @@ func extdeps_scope_placement_gate_failure_receipt() -> String { } func prose_row_introduction_gate_passes() -> Bool { - tools.ci_gates.exit_ok(e: run_spec_gate(g: ProseRowIntroductionGate)) + exit_ok(e: run_spec_gate(g: ProseRowIntroductionGate)) } func prose_row_introduction_gate_failure_receipt() -> String { diff --git a/dag/gunbc/instruments/generated_artifact_gate.dag b/dag/gunbc/instruments/generated_artifact_gate.dag index a59c08382ad..c303380a1c2 100644 --- a/dag/gunbc/instruments/generated_artifact_gate.dag +++ b/dag/gunbc/instruments/generated_artifact_gate.dag @@ -1,7 +1,7 @@ module tools.generated_artifact_gate -import std.process { ProcessExit, ExitSuccess, exit_failure } +import std.process { ProcessExit, ExitSuccess, exit_failure, exit_ok } import extdeps.filesystem.filesystem_io { Filesystem } import gunbc.ci_gate { gate_receipt_failure_detail } import gunbc.generated_artifact { @@ -46,10 +46,6 @@ fn generated_artifact_agreement_verdict(findings: GeneratedArtifactPopulationFin } } -fn exit_ok(e: ProcessExit) -> Bool { - match e { ExitSuccess => true _ => false } -} - // THE REMEDY IS PER FINDING, NOT PER RUN, AND REMOVING THE GLOBAL SENTENCE IS THE POINT. This body // used to append one closing instruction to every nonempty population -- do not hand-edit, // regenerate via main_wet -- which is correct for a content difference and WRONG for every other diff --git a/dag/std/process.dag b/dag/std/process.dag index b7f1f55550a..5ef8e940651 100644 --- a/dag/std/process.dag +++ b/dag/std/process.dag @@ -14,3 +14,10 @@ type ProcessExit fn exit_failure(reason: String) -> ProcessExit { ExitFailure { code: exit_code_general_error, reason: reason } } + +fn exit_ok(e: ProcessExit) -> Bool { + match e { + ExitSuccess => true + ExitFailure { code: _, reason: _ } => false + } +} From ba76ffba62547b1511149827c13cbd4384a72ed2 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 00:41:23 +0000 Subject: [PATCH 09/22] Give the docs-projection regen recipe one home in the agreement module. The host no longer reprints a second copy of the same command on the required-ci refusal. Co-authored-by: Cursor --- dag/gunbc/generated_artifact_boundary_seed_growth.dag | 5 ----- dag/gunbc/instruments/docs_projection_agreement.dag | 5 ++++- dag/test/claim/docs_projection_gate_test.dag | 4 ++-- src/v1/stage0/src/bin/claim_executor.rs | 4 ---- src/v1/stage0/src/cli_run.rs | 2 +- src/v1/stage0/src/generated_artifact_boundary_host.rs | 5 ++--- 6 files changed, 9 insertions(+), 16 deletions(-) diff --git a/dag/gunbc/generated_artifact_boundary_seed_growth.dag b/dag/gunbc/generated_artifact_boundary_seed_growth.dag index b3426e0a9ff..c31e2ba4905 100644 --- a/dag/gunbc/generated_artifact_boundary_seed_growth.dag +++ b/dag/gunbc/generated_artifact_boundary_seed_growth.dag @@ -185,11 +185,6 @@ data generated_artifact_boundary_seed_growth_justification: SeedGrowthJustificat decl_name: "run_generated_artifact_boundary", field: WholeDeclaration }, - DeclarationRef { - module_path: "v1_compiler.generated_artifact_boundary_host", - decl_name: "DOCS_PROJECTION_REGEN_COMMAND", - field: WholeDeclaration - }, DeclarationRef { module_path: "v1_compiler.generated_artifact_boundary_host", decl_name: "DOCS_PROJECTION_GATE_ENTRY", diff --git a/dag/gunbc/instruments/docs_projection_agreement.dag b/dag/gunbc/instruments/docs_projection_agreement.dag index 145c68195ae..1fa7f9b45b7 100644 --- a/dag/gunbc/instruments/docs_projection_agreement.dag +++ b/dag/gunbc/instruments/docs_projection_agreement.dag @@ -8,13 +8,16 @@ import std.process { ProcessExit, ExitSuccess, exit_failure } // the live tree. The path is an argument because the diagnostic must name the file; production // callers pass artifact_path from gunbc.generated_artifact, the single location authority. +data docs_projection_regen_command: String = + "gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/instruments/docs_projection_gate.dag --function regen" + fn docs_projection_bytes_agree(path: String, expected: String, committed: String) -> ProcessExit { if expected == committed { ExitSuccess } else { exit_failure(reason: concat( "generated-artifact drift: ", - concat(path, " is not derived from its .dag authority; regenerate with gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/instruments/docs_projection_gate.dag --function regen") + concat(path, concat(" is not derived from its .dag authority; regenerate with ", docs_projection_regen_command)) )) } } diff --git a/dag/test/claim/docs_projection_gate_test.dag b/dag/test/claim/docs_projection_gate_test.dag index 6323e91cb3d..e8e319a37a5 100644 --- a/dag/test/claim/docs_projection_gate_test.dag +++ b/dag/test/claim/docs_projection_gate_test.dag @@ -1,7 +1,7 @@ module test.claim.docs_projection_gate_test import std.process { ExitSuccess, ExitFailure } -import tools.docs_projection_agreement { docs_projection_bytes_agree } +import tools.docs_projection_agreement { docs_projection_bytes_agree, docs_projection_regen_command } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly @@ -15,7 +15,7 @@ test fn drifted_docs_projection_bytes_refuse_and_name_the_path() -> Bool { ExitSuccess => false ExitFailure { code: _, reason } => string_contains(s: reason, pattern: "docs/design-failure-modes.md") - && string_contains(s: reason, pattern: "docs_projection_gate.dag") + && string_contains(s: reason, pattern: docs_projection_regen_command) } } diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index a41c1a81870..9b9e40aca00 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -855,10 +855,6 @@ fn run() -> Result { eprintln!( "required-ci: generated-artifact population=docs-projections REFUSED {cause}" ); - eprintln!( - "required-ci: generated-artifact regenerate docs/*.md with: {}", - v1_compiler::cli_run::DOCS_PROJECTION_REGEN_COMMAND - ); phase_failures.push(format!("generated-artifact docs-projections: {cause}")); } } diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 687c64bea91..450b1e34935 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -42084,7 +42084,7 @@ pub use generated_artifact_boundary_host::{ generated_artifact_body_for_path, generated_artifact_ctx, run_docs_projection_agreement, run_generated_artifact_boundary, AdjudicatedArtifact, ArtifactDisposition, DocsProjectionAgreement, GeneratedArtifactBoundaryOutcome, GeneratedArtifactPathBody, - UnadjudicatedArtifact, DOCS_PROJECTION_REGEN_COMMAND, GENERATED_ARTIFACT_PRODUCING_COMMAND, + UnadjudicatedArtifact, GENERATED_ARTIFACT_PRODUCING_COMMAND, }; /// The emitted-closure compile phase: one entry's closure emitted, written as a crate, and diff --git a/src/v1/stage0/src/generated_artifact_boundary_host.rs b/src/v1/stage0/src/generated_artifact_boundary_host.rs index af13a544cd4..2ffffaaae16 100644 --- a/src/v1/stage0/src/generated_artifact_boundary_host.rs +++ b/src/v1/stage0/src/generated_artifact_boundary_host.rs @@ -47,9 +47,8 @@ pub const GENERATED_ARTIFACT_PRODUCING_COMMAND: &str = /// Regenerates only the two `docs/*.md` ledger projections and returns `ProcessExit`. /// Distinct from `GENERATED_ARTIFACT_PRODUCING_COMMAND`: that entry resolves the whole /// registry emit graph and does not complete on the runners that SIGKILL `main_wet`. -pub const DOCS_PROJECTION_REGEN_COMMAND: &str = "gunbc run --source-root dag --source-root src/v2 \ - --entry dag/gunbc/instruments/docs_projection_gate.dag --function regen"; - +/// The operator-facing recipe is `tools.docs_projection_agreement` `docs_projection_regen_command` +/// and is already inside the gate's `ProcessExit` reason; this host does not mint a second copy. const DOCS_PROJECTION_GATE_ENTRY: &str = "dag/gunbc/instruments/docs_projection_gate.dag"; /// Outcome of the docs-ledger agreement entry. Kept separate from From 508476d5c45f36c20e090c6deb4a0b0f4432e421 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 00:42:18 +0000 Subject: [PATCH 10/22] Ground the two-ledger heal population as policy, not a copied count. Restores the rationale that only the unbounded roster projections heal, without citing the deleted path-join test. Co-authored-by: Cursor --- .../claim/generated_artifact_assessment_witness_test.dag | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dag/test/claim/generated_artifact_assessment_witness_test.dag b/dag/test/claim/generated_artifact_assessment_witness_test.dag index 0b3bb0d334d..f1a33b26dd5 100644 --- a/dag/test/claim/generated_artifact_assessment_witness_test.dag +++ b/dag/test/claim/generated_artifact_assessment_witness_test.dag @@ -23,6 +23,11 @@ import tools.generated_artifact_gate { generated_artifact_agreement_verdict } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly +// THE EXCEPTION IS EXACTLY THE TWO LEDGER PROJECTIONS, not all documentation: only +// DesignFailureModesArtifact and DesignRungDropsArtifact answer HealRegeneratesAfterProvisionalMerge, +// because their authority is a small roster append while the projection is unbounded. count == 2 +// is that policy population, not a measurement copied from the current tree. + test fn only_design_ledgers_delegate_concurrent_regeneration_to_heal() -> Bool { let paths = heal_regenerated_after_provisional_merge_paths() count(paths) == 2 From 1acf6804910b7b9f7a7256d199e69c8618d83388 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 00:53:34 +0000 Subject: [PATCH 11/22] Record heron's unparseable-authority incident as the detector-gap specimen. The quoting injury stays a neighbouring class; this row only claims the missing completing gate. Regen attaches comments to the declarations they describe so the projector parses. Co-authored-by: Cursor --- .../instruments/docs_projection_agreement.dag | 6 ++---- .../instruments/docs_projection_gate.dag | 19 +++++++++---------- ...rojections_have_no_completing_actuator.dag | 2 ++ docs/design-failure-modes.md | 2 +- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/dag/gunbc/instruments/docs_projection_agreement.dag b/dag/gunbc/instruments/docs_projection_agreement.dag index 1fa7f9b45b7..e3762aa45d7 100644 --- a/dag/gunbc/instruments/docs_projection_agreement.dag +++ b/dag/gunbc/instruments/docs_projection_agreement.dag @@ -2,15 +2,13 @@ module tools.docs_projection_agreement import std.process { ProcessExit, ExitSuccess, exit_failure } +data docs_projection_regen_command: String = "gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/instruments/docs_projection_gate.dag --function regen" + // ONE PURE VERDICT OVER ONE PATH'S EXPECTED AND COMMITTED BYTES. Production regen and the // required generated-artifact phase both reach a ProcessExit through this function, so a fixture // can present drifted ledger bytes without resolving gunbc.generated_artifact_emit or writing // the live tree. The path is an argument because the diagnostic must name the file; production // callers pass artifact_path from gunbc.generated_artifact, the single location authority. - -data docs_projection_regen_command: String = - "gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/instruments/docs_projection_gate.dag --function regen" - fn docs_projection_bytes_agree(path: String, expected: String, committed: String) -> ProcessExit { if expected == committed { ExitSuccess diff --git a/dag/gunbc/instruments/docs_projection_gate.dag b/dag/gunbc/instruments/docs_projection_gate.dag index 994a9431d0c..0e862ee0f6b 100644 --- a/dag/gunbc/instruments/docs_projection_gate.dag +++ b/dag/gunbc/instruments/docs_projection_gate.dag @@ -10,6 +10,15 @@ import gunbc.generated_artifact { } import tools.docs_projection_agreement { docs_projection_bytes_agree } +fn write_docs_projection(path: String, content: String) -> ProcessExit { + let w = Filesystem.Write(path: path, content: content) + if w.success { + ExitSuccess + } else { + exit_failure(reason: concat(concat("write failed for ", path), concat(": ", w.error))) + } +} + // THE CLEANLY-EXITING ACTUATOR FOR docs/*.md LEDGER PROJECTIONS. // // gunbc.generated_artifact_gate main_wet folds the WHOLE committed registry, and resolving @@ -20,16 +29,6 @@ import tools.docs_projection_agreement { docs_projection_bytes_agree } // not return a ProcessExit on the runners that SIGKILL it), so three rostered rows sat in the // authority while docs/design-failure-modes.md stayed stale and nothing that completes could // say so. - -fn write_docs_projection(path: String, content: String) -> ProcessExit { - let w = Filesystem.Write(path: path, content: content) - if w.success { - ExitSuccess - } else { - exit_failure(reason: concat(concat("write failed for ", path), concat(": ", w.error))) - } -} - func regen() -> ProcessExit { let first = write_docs_projection( path: artifact_path(a: DesignFailureModesArtifact), diff --git a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag index 7e2ab9f33e7..428845559be 100644 --- a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag +++ b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag @@ -14,6 +14,8 @@ data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode "SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. ", + "INCIDENT, not a fixture and not a perturbation ordered for this PR, produced by quick-heron-85 without looking for this class, commit b1c6378c6d: their authority row unresolved_callee_conflated_with_dispatched_elsewhere was UNPARSEABLE for a full commit. An authored receipt contained a quoted phrase using bare double quotes inside a double-quoted string; the string terminated early and the parser refused undefined variable this and undefined variable leaf. THE AUTHORITY DID NOT COMPILE, so docs/design-failure-modes.md kept rendering the previous vintage while presenting as current. They found it only by running the projection; reading the row does not surface the break. This PR's gate would go red on that commit because an unparseable authority makes the projector refuse — from the committed markdown alone, STALE bytes and an UNBUILDABLE authority are indistinguishable. THIS ROW IS THE DETECTOR GAP. The quoting injury that silently disabled the row they were filing is a neighbouring class they are filing separately; its next-rung trigger is string escaping in the .dag lexer. This gate makes that injury loud; it does not make it unwritable. Do not read this row as closing that class. ", + "THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. ", "THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. ", diff --git a/docs/design-failure-modes.md b/docs/design-failure-modes.md index 86de138a72c..cafc4caa9e4 100644 --- a/docs/design-failure-modes.md +++ b/docs/design-failure-modes.md @@ -348,4 +348,4 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a walk whose native-stack depth grows with the depth of corpus-shaped data, with no declared bound** (the compiler recurses once per level of a structure whose depth is an input, not a constant. Past some corpus the process aborts on stack exhaustion. HARM: the abort is not a diagnostic -- it is a SIGSEGV turned into `thread ... has overflowed its stack` and an abort, so it carries no span, no diagnostic identity, and no partial result. The run neither succeeds nor refuses, and every downstream reader sees a crashed process rather than a located refusal. Worse, the abort is silent about its own cause -- it kills the process before the summary line, so the very output that would characterise the input is the output you lose. **THE DISTINGUISHING FACT IS THAT THE RECURSION NEED NOT APPEAR IN OUR SOURCE AT ALL.** The class has two instances and they differ in exactly this, which is why one row covers both and why looking only for authored recursion finds half of it. AUTHORED INSTANCE: `v1.compiler.infer_types` `node_type_shape` and `node_type_shape_argument_list` are mutually recursive over type structure with no depth bound, rendering `Primitive(...)`, `Named(...)`, `Product(...)`, `Coproduct(...)` by descending into children. A reader can find this one, and grep for `fn` names finds it. COMPILER-GENERATED INSTANCE: `v1.std.core` `Node` carries `children: Rc>>` (and `params`, `uses`, `properties`, `body`, `type_annotation` likewise) with no hand-written `Drop`, so the DESTRUCTOR rustc synthesises recurses once per level on every release of a deep graph. **No reading of our own source can find it, because it is not in our source.** Grepping for recursion, auditing every `fn`, and eliminating every in-window candidate by inspection all report health -- and did, on the specimen below, where three code candidates in the failing window were correctly eliminated by reading while the actual recursion was in glue no one had written. **RECOGNITION RULE, and it is about the DATA, not about the code: any type whose values are trees or chains of unbounded depth carries this class in every operation that touches a whole value -- including the ones you did not write.** Construction, traversal, rendering, cloning, comparison, hashing, serialisation and RELEASE are all depth-recursive by default. The question to ask of a recursive carrier is not `which of my functions recurse` but `what happens to a value of this type at 10^6 levels`, and the honest answer is usually that nobody knows because nobody has one. **RECEIPT, EXECUTED, WITH A PAIRED CONTROL AND ONE CHANGED LINE** (2026-09-05, quick-heron-85, during the NS-N containment cut). A two-file subject -- `std.measure` plus `test.claim.long.reference_closure_equivalence_test`, compiled with `src/v2` and `src/v1` as name-pool roots -- aborts with `stack overflow` after four pipeline phases and before its diagnostic summary prints. THE STACK-THRESHOLD INSTRUMENT TURNS THE BINARY INTO A SCALAR: bisecting `ulimit -s` gives the minimum stack at which each configuration completes. Each file ALONE completes at 1024 KiB; the PAIR completes at 147456 KiB and overflows at 139264 KiB. A factor of at least 144, closed on both sides, which is what establishes the recursion as FINITE AND DEEP rather than unbounded-in-the-limit -- an unlimited stack completes. THE DISCRIMINATOR IS THE ONE-LINE PROBE, not the threshold: `std::mem::forget(resolved.clone())` at the head of `v1.compiler.compile` `emit_resolved_for_target_selected`, holding files, binary invocation, source roots, stack size and diagnostics fixed and changing ONLY whether the tree is ever released. Paired at the same stock 8192 KiB: unpatched OVERFLOW, leaked binary exit 1, zero `stack overflow`, and the summary line prints (496 blocking, 2643 advisory). The destructor was the recursion. **THE PROBE MUST FORGET A CLONE, NOT THE VALUE**, and this is the part worth carrying to the next investigator: `resolved` is an `Rc`, so forgetting IT merely drops one handle and moves the destructor to whoever holds the last one -- reading as a false PERSIST and killing a true hypothesis. Forgetting a CLONE pins the outer refcount above zero, so the contents can never be released no matter who else holds a handle. That converts `check the precondition` into `remove the failure mode`, which is the difference between a probe you have to trust and one that cannot lie. **WHAT DID NOT DISCRIMINATE, recorded because each looked decisive and cost a cycle:** diagnostic COUNT is blind -- a probe rendering 8,000 diagnostics does not overflow while this configuration's 3,139 does; per-diagnostic SHAPE is blind unless the shape is in the graph; and a 2,000-level-deep authored expression chain completes. The depth that matters is the depth of the structure RELEASED, which no probe varying the diagnostic list can reach. **RUNG FOUND AT: 1, MITIGATABLE, by fail-stop and process isolation -- AND SECTION 5's FAIL-CLOSED INTERFACE IS STILL VIOLATED. Both halves belong in the sentence.** The mitigation is real and is what earns the rung: the abort is a fail-stop at a process boundary, and that boundary PREVENTS AN ACCEPTED RESULT OR ANY OTHER SUCCESS PUBLICATION -- the run dies before its summary line, so no consumer receives a partial or fabricated answer. Section 4b names isolation among rung-1 mechanisms, so the absence of a typed result does not deny rung 1. **BUT RUNG 1 DOES NOT REHABILITATE THE SIGSEGV**: section 5 independently requires every path to succeed fully or fail with a TYPED, LOCATED diagnostic, and an abort carrying no subject, no span and no diagnostic identity fails that badly. The deficit is a rung-1 class with a standing section 5 interface violation, which is what makes it rank for repair. **AN EARLIER REVISION OF THIS ROW PLACED THE CLASS OUTSIDE THE LADDER, AND THAT WAS WRONG IN A WAY WORTH KEEPING VISIBLE** (corrected 2026-09-06, native review 5124409497). Section 4b's only exclusions are silent wrongness below the floor and the adjacent column of external reality, undecidable properties and undeclared intent; this class is none of those, and the row REFUTED ITS OWN EXCLUSION two sentences later by stating that the depth property is decidable, that nothing waits on external reality, and that both a declared bound and an iterative implementation are authorable. **EXCUSING A CLASS FROM HAVING A RUNG HAS THE SAME PRACTICAL EFFECT AS INFLATING ONE -- IT NEVER RANKS**, which is the failure this ledger exists to prevent, arriving from the low side rather than the high one. **CEILING: 3, STRUCTURALLY GUARANTEED -- NOT 4, BECAUSE DECIDABLE DOES NOT IMPLY RUNG 4.** Depth is decidable and nothing waits on external reality, but decidability establishes only that a structural decision procedure is POSSIBLE. If an unbounded recursive carrier remains WRITABLE while the compiler derives a proof or a refusal for it, that is rung 3. **RUNG 4 REQUIRES THAT THE INVALID STATE HAVE NO CONSTRUCTOR IN THE CANONICAL MODEL, AND THIS ROW CANNOT NAME THE CONSTRUCTOR-ELIMINATION PATH**: no canonical carrier model is in hand in which data-dependent native-stack recursion is inexpressible, and a recursive carrier is a thing the substrate is meant to be able to declare. The ceiling is therefore raised to 4 only by a future row that NAMES that model; until then, claiming 4 here would be the inflation this ledger refuses. **THE REPAIR MAY NOT BE A BIGGER STACK, A FATTER THREAD, OR A RAISED BUDGET, AND THE LEAK IS AN INSTRUMENT RATHER THAN AN ARM.** All four leave the abort exactly where it was and move the corpus at which it fires, which is section 5's absorbing fallback in its purest form: the failure arm WIDENS instead of refusing, the widening is denominated in the corpus rather than the change, and the deficit stops ranking for repair because the symptom moved. The two honest arms are a release and traversal whose depth does not grow with the data -- an explicit iterative teardown and an explicit worklist walk -- or a DECLARED depth bound enforced at construction, refusing a too-deep structure with a typed, located diagnostic naming the carrier and the level. **THE TRIGGER BELOW IS A DELIBERATE 1-TO-3 JUMP, STATED SO IT IS NOT MISREAD AS THE ADJACENT RUNG.** Deriving the risk from the declared carrier and failing compilation when an unbounded carrier lacks an iterative release or a declared bound is a STRUCTURAL GUARANTEE, rung 3 -- it skips rung 2 rather than passing through it. The rung-2 form, if the jump proves too large to take in one step, is a generated lens or an adversarial-depth witness that reliably exposes and blocks the class WHILE THE INVALID IMPLEMENTATION STAYS WRITABLE and safety depends on that mechanism executing and staying enrolled. **NEXT-RUNG TRIGGER, and it names a CAPABILITY rather than a repair to one carrier:** a mechanism that derives, from a declared carrier, whether any operation on it -- including generated ones -- can recurse without a bound, so that authoring an unbounded-depth carrier without an iterative release or a declared bound fails to compile. Repairing `Node` alone does not retire this row; it removes one instance, and `node_type_shape` would still stand, as would every carrier authored tomorrow. **BOUNDED AGAINST TWO NEIGHBOURS.** Section 5's absorbing fallback is a mechanism that WIDENS when it cannot answer precisely; here nothing widens and nothing is answered -- the process dies. And `right_censored_cost_read_as_exact` concerns a measurement truncated by a limit; the resemblance is superficial, because there the run completes and reports a bounded number, whereas here the limit destroys the run and its report together, which is precisely why the threshold instrument had to be built to get any number at all. **WHY THIS RECEIPT NAMES NO PRODUCER, STATED RATHER THAN LEFT AS AN OMISSION** (raised as a section 6 objection, review 61308). Section 6 requires a measurement to be cited by naming the producer that re-derives it, because A TRANSCRIBED NUMBER IS UNREACHABLE FROM THE THING THAT OWNS IT AND ROTS WITHOUT ANYONE TOUCHING EITHER END. **THE OWNER OF THESE FIGURES NO LONGER EXISTS.** Every number here was observed on a configuration requiring a resolver rule that is not on main and, by ruling, never lands in that shape; the specimen is extinct, so NO ENTRY POINT CAN RE-DERIVE IT AND NONE IS OWED -- which is the case section 6 names in its own final clause, a one-off rather than an instrument. What is recorded is therefore a HISTORICAL OBSERVATION OF AN EXTINCT CONDITION, not a standing measurement, and it cannot rot in section 6's sense because it asserts nothing about the current tree. Without this sentence a reader concludes the producer was omitted rather than impossible, which is why the absence is declared instead of left to be noticed. **THE POPULATION ON MAIN IS UNMEASURED AND THE ROW DOES NOT CLAIM OTHERWISE, WHICH IS A DENOMINATOR FACT AND NOT BOOKKEEPING** (established 2026-09-06, after this row was first written, by the three-cell probe that identified what builds the chain). THE SOLE SPECIMEN WAS PRODUCED BY A RESOLVER RULE THAT IS NOT PRESENT ON MAIN: admitting the one refused callable owner collapses the specimen from 1,800,008 to 85, with the deepest site moving to an unrelated module, and the same instrument reads 85 under main and under the merge base. **THE TWO LEVELS ARE CONDITIONAL OBSERVATIONS AND MUST NOT BE LIFTED OUT AS PROPERTIES OF "MAIN" AND "THE CUT".** They were measured under one fixed execution condition -- that corpus, that traversal order, that pool construction, that binary and instrumentation -- and the pool construction in force is itself order-dependent, so the absolute levels are traversal-order-contaminated. WHAT GENERALISES IS THE DISCRIMINATING EFFECT OF CHANGING THE PREDICATE, not the numbers: a defective traversal regime can move the absolute state presented to every cell alike, but it cannot manufacture a DIFFERENCE between cells when that regime was held fixed. So the collapse stands as causal evidence with its held condition attached, and 85 is not a fact about main. **WHETHER ORDINARY CORPUS GROWTH ALONE CAN REACH COMPARABLE DEPTH IS UNMEASURED, AND NOBODY HAS SHOWN THAT IT CAN.** THE PRODUCING DEFECT IS NAMED SO THE TWO ROWS CONNECT RATHER THAN LEAVING THE NEXT READER TO RE-DERIVE IT: the depth was reached because an UNRESOLVABLE CALL WAS ELABORATED RATHER THAN REFUSED, which is filed as the call-site form of `gunbc.recurring_failure_mode.state_space_conflation` -- the callable-lookup carrier collapses builtin, locally-bound and genuinely-missing into one constructor, and the consumer reads that collapsed absence as affirmative formal authority. **STATED IN BOTH DIRECTIONS, BECAUSE EACH HALF GUARDS AGAINST THE OPPOSITE MISREAD.** A reader must not conclude that corpus growth alone reaches this depth -- that is the unmeasured half above. A reader must ALSO not conclude that the structure was a harness artefact: NOTHING WAS FORCED IN THE RUN THAT PRODUCED IT. The 1,800,008 baseline is an ordinary compile under an ordinary resolver rule; the forced admissions were the two PROBES placed either side of that baseline to identify its cause. The structure was real, an accepted compile built it, and the class it exhibits is independent of whichever defect supplied the depth -- which is why this row stands on its own and does not wait on that defect's repair. **THE SECOND DEFECT THIS SPECIMEN EXPOSES IS NOT THIS ROW AND MUST NOT BE ABSORBED INTO IT:** nothing in two source files is 10^5 levels deep, so some pipeline stage BUILDS a structure far deeper than its input -- a chain where a flat structure was intended. That is a separate defect, for which this class is only the reporter, and it will be pathological for every consumer that walks the structure rather than only the one that releases it.) - **DESTRUCTIVE CLEANUP CORRECTNESS DEPENDING ON AN UNDECLARED EVALUATION-MODEL PROPERTY. THREE TRANSPORTS ARE CORRECT BY ACCIDENT OF REALIZATION, NOT BY CONSTRUCTION. INVALID STATE:** the interpreter implements `let` as eager/strict (eval_let evaluates the value expression unconditionally at the point of binding). Nothing declares this as a language guarantee — it is a property of the current interpreter realization, not a declared semantic axis. The companion divergence — that interpreter `&&` evaluates both operands while emitted Rust `&&` short-circuits — is already filed as realization_arms_diverge_on_whether_the_program_refuses. It does not affect these transports: their cleanups are let-bound, not &&-bound, and under eager let a short-circuit && cannot change any conjunction's value because it skips only the right operand when the left is false — a path where the answer is already determined and the skipped operand is a pure field read (.success) on a record whose effect already fired at its let binding. STRUCTURALLY, no transport in this class depends on && eagerness, and no addition to the corpus can create such a dependence as long as let stays eager. If `let` became lazy, cleanup would never run in the namespace_import_closure_behavioral_transport (where cleanup is bound but never consumed), and would be unobserved in the other two (where cleanup .success would not be consumed, though the eager `let` at binding point would still fire). The distinction matters: 'effect does not reach the filesystem' is a leak; 'effect reaches the filesystem but its success is unobserved' is an unobserved cleanup failure — different harms, different severities.POPULATION, BOUNDED AND ENUMERABLE — THE THREE TRANSPORTS: `tools.self_host_logic_behavioral_transport::slb_comparison_observation` (let-bound cleanup_src/cleanup_out consumed in the staged conjunction), `tools.emit_host_transport::run_rust_smoke` / `::run_python_smoke` / `::run_go_smoke` (let-bound cleanup consumed in a conjunction), `tools.namespace_import_closure_behavioral_transport::nic_emitted_crate_evidence` (let-bound cleanup_src/cleanup_out NEVER consumed — correct only because let is eager).CEILING: cleanup that does not depend on the evaluation model at all — an effect the transport sequences explicitly rather than one it gets from strictness. Every transport's destructive cleanup would be structurally sequenced (e.g. a `do cleanup after body` pattern) rather than relying on let-eagerness for its effect to reach the filesystem.NEXT-RUNG TRIGGER, A CAPABILITY — either (a) the evaluation model declared as a typed guarantee that a check can execute against — a property of the interpreter that is named, bounded, and verifiable, not prose — or (b) every transport whose correctness turns on eager evaluation sequences its destructive effects explicitly rather than inheriting them from let-strictness. Both capabilities are genuine transitions; neither is satisfied by documentation or by this row.CORPUS POPULATION MEASUREMENT — the && divergence has no live hazard in this corpus: zero sites place a non-total or effectful operation directly as the right operand of `&&` (the two found are read-only). Twenty sites let-bind destructive effects and then consume their .success in a `&&` chain; under the structural argument above, none depends on && eagerness. HOWEVER, A=0 is a SURVIVOR PROPERTY OF THE STRICTER ARM HAVING FILTERED THE CORPUS, not evidence the class is harmless. The interpreter evaluates both operands unconditionally, so any author who wrote `guard && dangerous` would have hit the refusal while authoring and rewritten before commit. Under a short-circuit `&&`, authors would write these patterns freely and they would compile — and the emitted arm would silently produce a wrong answer where the interpreter refuses. That is the existing row's subject.INSTRUMENT — DISCRIMINATING PROBES, THEIR NAMES AND INVOCATION, SO A FUTURE READER CAN RE-DERIVE THIS FINDING AND RE-RUN IT. Five executed probes in module `test.claim.eval_model_probe` (dag/test/claim/eval_model_probe_test.dag): `let_is_eager_when_unused` (let-binds a Remove without consuming its result; PASS means eager), `let_is_eager_when_consumed` (positive control), `and_demands_right_even_when_left_false` (let-binds false and Remove separately, && computes; PASS means Remove ran despite false), `and_short_circuit_direct_probe` (false && Remove directly; PASS means Remove ran), `and_with_true_left_removes_file` (positive control). Each was run as: `gunbc run --source-root dag --source-root src/v2 --entry dag/test/claim/eval_model_probe_test.dag --function --claim-run`. All five returned PASS (exit 0). Probes observed the filesystem side effect out-of-band (shell.Test.IsFile) rather than consuming a returned value, so no measurement-created dependency could confound the result. - **generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. The refusal exits 2 with EMPTY STDOUT and interpolates the rendered value into the diagnostic on stderr: classify_exit's non-variant arm is type_name: ctx.format_value(other). Executed: wise-badger-902, EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown heading; quick-heron-85, proj3.err 1062137 bytes, same delimiters. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` forbids print-AND-SUCCEED, and holds. The content is therefore obtainable ONLY by scraping a half-megabyte stderr diagnostic between delimiters that are message prose (returned-open-quote and not-ProcessExit-close-quote), unversioned and enrolled by nothing. Extractable only by scraping an unversioned diagnostic — which is why the ProcessExit regen is the repair. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. RUNG FOUND AT: outside the ladder until a completing content agreement exists — silent wrongness, not a weak wall. After a ProcessExit projector and a required content compare execute, mechanically preventable: the stale file remains writable. CEILING: structurally guaranteed when membership of every committed generated `docs/` path in a content-agreement adjudicator is derived from `gunbc.generated_artifact` `committed_generated_artifacts`, so omitting the arm is a compile refusal. NEXT-RUNG TRIGGER, a capability: required-run dispatch consumes a content agreement — byte identity of generated vs committed — over every committed generated artifact whose location directory is `docs`, derived from the registry so adding a committed `docs/` path without that agreement will not compile. A trigger naming one command, one test file, or `docs_projection_gate.dag` is less than the capability and would fire while edit-only drift can still sit uncompared.) -- **a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. SPECIMEN, 2026-09-06, executed both ways. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs NotProcessExit with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the interpolation site and ruled the same. quick-heron-85 held proj3.err at 1062137 bytes. The manager then RAN the projector: EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown. The field name said type; the field held format_value. THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. RUNG FOUND AT: mitigatable — the wrong inference is a human reading; the runtime still refuses with exit 2 and does not print-and-succeed. CEILING: structurally impossible when `NotProcessExit` cannot be constructed with a rendered value under a field named for a type — two variants, or a field named for what it actually holds. NEXT-RUNG TRIGGER, a capability: `ExitClass` distinguishes a type-name refusal from a rendered-value refusal in the type, so interpolating one as the other will not compile.) +- **a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. SPECIMEN, 2026-09-06, executed both ways. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs NotProcessExit with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the interpolation site and ruled the same. quick-heron-85 held proj3.err at 1062137 bytes. The manager then RAN the projector: EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown. The field name said type; the field held format_value. WHY THE FORK SURVIVED, and it is a section 4b evidence gap not a naming smell. `non_process_exit_refuses_rather_than_printing_and_succeeding` hand-constructs ExitClass::NotProcessExit with type_name the literal Bool and starts from an already-classified value. It never calls `classify_exit`. Nothing executing joins the field's name to its contents on the non-variant path — the only enrolled evidence feeds the field a literal that matches its name, which is exactly the input that cannot discriminate. The control's RED is unauthorable for this defect, so it was permanently green over the case that mattered and got cited as coverage. A field carrying two facts is ordinary; a field carrying two facts with a control that only ever sees one of them is why three of us read adjacent code correctly and reached three answers. THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. RUNG FOUND AT: mitigatable — the wrong inference is a human reading; the runtime still refuses with exit 2 and does not print-and-succeed. CEILING: structurally impossible when `NotProcessExit` cannot be constructed with a rendered value under a field named for a type — two variants, or a field named for what it actually holds. NEXT-RUNG TRIGGER, a capability: `ExitClass` distinguishes a type-name refusal from a rendered-value refusal in the type, so interpolating one as the other will not compile.) From a42baa3270c8891dfe305dc26aaecb3a3726f37e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 01:20:00 +0000 Subject: [PATCH 12/22] Eliminate std.process Bool exit_ok; join seed-growth trigger to the roster. Call sites match ProcessExit. Heal registration-without-completion goes in the failure-mode row. The seed-growth trigger names hand_authored_declarations instead of a transcribed 20. Co-authored-by: Cursor --- ...enerated_artifact_boundary_seed_growth.dag | 6 +-- dag/gunbc/instruments/ci_gates.dag | 7 +++- .../instruments/docs_projection_gate.dag | 16 ++++---- .../instruments/floor_effect_gate_witness.dag | 37 +++++++++++++++---- .../instruments/generated_artifact_gate.dag | 27 +++++++------- ...rojections_have_no_completing_actuator.dag | 2 +- dag/std/process.dag | 7 ---- docs/design-failure-modes.md | 2 +- 8 files changed, 59 insertions(+), 45 deletions(-) diff --git a/dag/gunbc/generated_artifact_boundary_seed_growth.dag b/dag/gunbc/generated_artifact_boundary_seed_growth.dag index c31e2ba4905..93474533ec2 100644 --- a/dag/gunbc/generated_artifact_boundary_seed_growth.dag +++ b/dag/gunbc/generated_artifact_boundary_seed_growth.dag @@ -76,13 +76,13 @@ import gunbc.seed_growth { SeedGrowthJustification } // line to the emitted lib.rs and its SeedRetainedIntrinsicRegistration carries has_pub_mod: false. // What grows is the SeedRetained roster, enumerated below. // -// HAND-ITEM DELTA: +20, all enumerated below rather than counted, and ZERO of them uncitable -- +// HAND-ITEM DELTA: every host declaration is enumerated below rather than counted, and ZERO of them uncitable -- // there is not one impl block in the file, which is deliberate and cost something. The first cut // carried four inherent methods, and every impl method is uncitable as a DeclarationRef because // std.decl_ref offers WholeDeclaration or NamedField and neither names a method on an impl block, // so they would have grown the class gunbc.seed_growth_admission reports as // seed_growth_uncitable_item_keys. They are free functions instead. THE DELTA IS A NET FIGURE AND -// THE NET IS NOT +20 ACROSS THE CHANGE: bin/claim_executor.rs LOSES three declarations +// THE NET IS NOT A COUNT ACROSS THE CHANGE: bin/claim_executor.rs LOSES three declarations // (GeneratedArtifactPathBody, generated_artifact_ctx, generated_artifact_body_for_path) which are // hoisted rather than added, so the corpus-wide hand-item delta for those three is ZERO and only // their new home changed. They are listed below because the roster names where an obligation LIVES, @@ -233,6 +233,6 @@ data generated_artifact_boundary_seed_growth_justification: SeedGrowthJustificat ], reason: "The phase compares a modeled projection against BYTES ON DISK, and reading the committed file is a host effect the required floor's hermetic envelope refuses by construction -- so the leaf is Rust for the same reason ingestion is. The rejected alternative was running the existing tools.generated_artifact_gate main through the interpreter in Wet mode, refused because it makes the interpreter load-bearing for a NEW required phase while two lanes delete it, and because that gate body also reaches artifact_extra_valid's ci_yml_parses, so the required phase would acquire a second subject nobody asked it to check. What the growth buys is that a committed projection diverging from its authority stops the line at all: for the whole interval since the 2026-08-15 floor cut, DESIGN.md and ROADMAP.md were compared by nothing, and the first execution of this phase found four divergences, one of them wrong since #9394.", owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, - trigger: "Delete the 20 generated_artifact_boundary_host declarations when the required run can execute a .dag adjudicator over this population -- that is, when a modeled operation can read a committed file's bytes inside the required envelope, at which point the whole comparison is gunbc.generated_artifact_emit plus one modeled read and this host has nothing left to do. Delete the separately rostered v1_compiler.bin.claim_executor run modification when required-run dispatch consumes gunbc.required_ci_phase_roster RequiredCiPhase directly under ROADMAP node v1-hand-queue-drain. A PARTIAL migration is admissible and is the expected shape: the four pure declarations here (AdjudicatedArtifact, ArtifactDisposition, artifact_disposition, artifact_disposition_name) are a fold over two strings and dissolve the moment anything models them, independently of the read. What does NOT dissolve them is moving them to another host file, and what does NOT dissolve them is a witness asserting this host agrees with the model -- that would raise the rung without removing the duplicate.", + trigger: "Delete every v1_compiler.generated_artifact_boundary_host row in hand_authored_declarations when the required run can execute a .dag adjudicator over this population -- that is, when a modeled operation can read a committed file's bytes inside the required envelope, at which point the whole comparison is gunbc.generated_artifact_emit plus one modeled read and this host has nothing left to do. Delete the separately rostered v1_compiler.bin.claim_executor run modification when required-run dispatch consumes gunbc.required_ci_phase_roster RequiredCiPhase directly under ROADMAP node v1-hand-queue-drain. A PARTIAL migration is admissible and is the expected shape: the four pure declarations here (AdjudicatedArtifact, ArtifactDisposition, artifact_disposition, artifact_disposition_name) are a fold over two strings and dissolve the moment anything models them, independently of the read. What does NOT dissolve them is moving them to another host file, and what does NOT dissolve them is a witness asserting this host agrees with the model -- that would raise the rung without removing the duplicate.", current_boundary: "src/v1/stage0/src/generated_artifact_boundary_host.rs; src/v1/stage0/src/cli_run.rs (one #[path] mod line and one pub use, adding no declaration); src/v1/stage0/src/bin/claim_executor.rs run (ExistingSeedItemModified: the required generated-artifact phase body consumes gunbc.required_ci_phase_roster RequiredCiPhase; no new Rust declaration); dag/gunbc/generated_artifact_boundary_seed_growth.dag" } diff --git a/dag/gunbc/instruments/ci_gates.dag b/dag/gunbc/instruments/ci_gates.dag index 9bf104c12f6..c188e6dc84e 100644 --- a/dag/gunbc/instruments/ci_gates.dag +++ b/dag/gunbc/instruments/ci_gates.dag @@ -1,6 +1,6 @@ module tools.ci_gates -import std.process { ProcessExit, ExitSuccess, exit_failure, exit_ok } +import std.process { ProcessExit, ExitSuccess, ExitFailure, exit_failure } import gunbc.ci_gate { EmitHostGate, Gate, CheapClaimPoolGate } import gunbc.ci_spec { gunbc_ci_gates, CiSpec, @@ -35,7 +35,10 @@ fn run_spec_gate(g: Gate) -> ProcessExit { fn run_gate_list(gates: List) -> ProcessExit { fold(gates, init: ExitSuccess, f: fn(acc, g) { - if exit_ok(e: acc) { run_spec_gate(g: g) } else { acc } + match acc { + ExitSuccess => run_spec_gate(g: g) + ExitFailure { code, reason } => ExitFailure { code: code, reason: reason } + } }) } diff --git a/dag/gunbc/instruments/docs_projection_gate.dag b/dag/gunbc/instruments/docs_projection_gate.dag index 0e862ee0f6b..9f9ece60279 100644 --- a/dag/gunbc/instruments/docs_projection_gate.dag +++ b/dag/gunbc/instruments/docs_projection_gate.dag @@ -1,6 +1,6 @@ module tools.docs_projection_gate -import std.process { ProcessExit, ExitSuccess, exit_failure, exit_ok } +import std.process { ProcessExit, ExitSuccess, ExitFailure, exit_failure } import extdeps.filesystem.filesystem_io { Filesystem, filesystem_read_outcome, FilesystemReadSucceeded, FilesystemReadRefused } import gunbc.design_ledgers { expected_design_failure_modes_md, expected_design_rung_drops_md } import gunbc.generated_artifact { @@ -34,13 +34,12 @@ func regen() -> ProcessExit { path: artifact_path(a: DesignFailureModesArtifact), content: expected_design_failure_modes_md() ) - if exit_ok(e: first) { - write_docs_projection( + match first { + ExitSuccess => write_docs_projection( path: artifact_path(a: DesignRungDropsArtifact), content: expected_design_rung_drops_md() ) - } else { - first + ExitFailure { code, reason } => ExitFailure { code: code, reason: reason } } } @@ -60,12 +59,11 @@ func main() -> ProcessExit { path: artifact_path(a: DesignFailureModesArtifact), expected: expected_design_failure_modes_md() ) - if exit_ok(e: first) { - agree_one( + match first { + ExitSuccess => agree_one( path: artifact_path(a: DesignRungDropsArtifact), expected: expected_design_rung_drops_md() ) - } else { - first + ExitFailure { code, reason } => ExitFailure { code: code, reason: reason } } } diff --git a/dag/gunbc/instruments/floor_effect_gate_witness.dag b/dag/gunbc/instruments/floor_effect_gate_witness.dag index 412536883e1..2f19a55e540 100644 --- a/dag/gunbc/instruments/floor_effect_gate_witness.dag +++ b/dag/gunbc/instruments/floor_effect_gate_witness.dag @@ -5,7 +5,7 @@ import gunbc.ci_spec { ExtdepsExternalAuthorityGate, DagCompileCleanGate, GeneratedArtifactDriftGate, ExtdepsScopePlacementGate, ProseRowIntroductionGate } -import std.process { exit_ok } +import std.process { ExitSuccess, ExitFailure } import tools.ci_gates { run_spec_gate } import tools.extdeps_scope_placement_gate { scope_placement_failure_receipt } import tools.prose_row_introduction_gate { prose_row_gate_failure_receipt } @@ -38,27 +38,45 @@ import tools.generated_artifact_gate { generated_artifact_drift_failure_receipt // projection from this entry's declarations (see loudness_witness_note sequencing note). func emit_host_gate_passes() -> Bool { - exit_ok(e: run_spec_gate(g: EmitHostGate)) + match run_spec_gate(g: EmitHostGate) { + ExitSuccess => true + ExitFailure { code, reason } => false + } } func cheap_claim_pool_gate_passes() -> Bool { - exit_ok(e: run_spec_gate(g: CheapClaimPoolGate)) + match run_spec_gate(g: CheapClaimPoolGate) { + ExitSuccess => true + ExitFailure { code, reason } => false + } } func extdeps_external_authority_gate_passes() -> Bool { - exit_ok(e: run_spec_gate(g: ExtdepsExternalAuthorityGate)) + match run_spec_gate(g: ExtdepsExternalAuthorityGate) { + ExitSuccess => true + ExitFailure { code, reason } => false + } } func dag_compile_clean_gate_passes() -> Bool { - exit_ok(e: run_spec_gate(g: DagCompileCleanGate)) + match run_spec_gate(g: DagCompileCleanGate) { + ExitSuccess => true + ExitFailure { code, reason } => false + } } func generated_artifact_drift_gate_passes() -> Bool { - exit_ok(e: run_spec_gate(g: GeneratedArtifactDriftGate)) + match run_spec_gate(g: GeneratedArtifactDriftGate) { + ExitSuccess => true + ExitFailure { code, reason } => false + } } func extdeps_scope_placement_gate_passes() -> Bool { - exit_ok(e: run_spec_gate(g: ExtdepsScopePlacementGate)) + match run_spec_gate(g: ExtdepsScopePlacementGate) { + ExitSuccess => true + ExitFailure { code, reason } => false + } } func extdeps_scope_placement_gate_failure_receipt() -> String { @@ -66,7 +84,10 @@ func extdeps_scope_placement_gate_failure_receipt() -> String { } func prose_row_introduction_gate_passes() -> Bool { - exit_ok(e: run_spec_gate(g: ProseRowIntroductionGate)) + match run_spec_gate(g: ProseRowIntroductionGate) { + ExitSuccess => true + ExitFailure { code, reason } => false + } } func prose_row_introduction_gate_failure_receipt() -> String { diff --git a/dag/gunbc/instruments/generated_artifact_gate.dag b/dag/gunbc/instruments/generated_artifact_gate.dag index c303380a1c2..3fed36fde3f 100644 --- a/dag/gunbc/instruments/generated_artifact_gate.dag +++ b/dag/gunbc/instruments/generated_artifact_gate.dag @@ -1,7 +1,7 @@ module tools.generated_artifact_gate -import std.process { ProcessExit, ExitSuccess, exit_failure, exit_ok } +import std.process { ProcessExit, ExitSuccess, ExitFailure, exit_failure } import extdeps.filesystem.filesystem_io { Filesystem } import gunbc.ci_gate { gate_receipt_failure_detail } import gunbc.generated_artifact { @@ -240,16 +240,16 @@ func main_wet_one(path: String) -> ProcessExit { func main_wet() -> ProcessExit { fold(committed_generated_artifacts(), init: ExitSuccess, f: fn(acc, a) { - if exit_ok(e: acc) { - match artifact_generate(a: a) { - ArtifactGenerationRefused { reason } => exit_failure(reason: concat(concat("generation refused for ", artifact_path(a: a)), concat(": ", reason))) - ArtifactGenerated { content } => { - let w = Filesystem.Write(path: artifact_path(a: a), content: content) - if w.success { ExitSuccess } else { exit_failure(reason: concat(concat("write failed for ", artifact_path(a: a)), concat(": ", w.error))) } + match acc { + ExitFailure { code, reason } => ExitFailure { code: code, reason: reason } + ExitSuccess => + match artifact_generate(a: a) { + ArtifactGenerationRefused { reason } => exit_failure(reason: concat(concat("generation refused for ", artifact_path(a: a)), concat(": ", reason))) + ArtifactGenerated { content } => { + let w = Filesystem.Write(path: artifact_path(a: a), content: content) + if w.success { ExitSuccess } else { exit_failure(reason: concat(concat("write failed for ", artifact_path(a: a)), concat(": ", w.error))) } + } } - } - } else { - acc } }) } @@ -355,9 +355,8 @@ func heal_repair_declaration() -> ProcessExit { func main_wet_verified() -> ProcessExit { let first = main_wet() - if exit_ok(e: first) { - run_generated_artifact_drift_gate_body() - } else { - first + match first { + ExitSuccess => run_generated_artifact_drift_gate_body() + ExitFailure { code, reason } => ExitFailure { code: code, reason: reason } } } diff --git a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag index 428845559be..baaa05f3127 100644 --- a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag +++ b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag @@ -18,7 +18,7 @@ data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode "THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. ", - "THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. ", + "THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. Heal-generated-artifacts already stages docs/design-failure-modes.md by name after generated_artifact_gate main_wet; that is not an absent gate. The path is registered, and the actuator dies on a fold whose cost is the whole registry rather than the two files that changed. Registration without a completing actuator is worse than absence, because it looks covered. ", "RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. ", diff --git a/dag/std/process.dag b/dag/std/process.dag index 5ef8e940651..b7f1f55550a 100644 --- a/dag/std/process.dag +++ b/dag/std/process.dag @@ -14,10 +14,3 @@ type ProcessExit fn exit_failure(reason: String) -> ProcessExit { ExitFailure { code: exit_code_general_error, reason: reason } } - -fn exit_ok(e: ProcessExit) -> Bool { - match e { - ExitSuccess => true - ExitFailure { code: _, reason: _ } => false - } -} diff --git a/docs/design-failure-modes.md b/docs/design-failure-modes.md index cafc4caa9e4..7722e1ccdf5 100644 --- a/docs/design-failure-modes.md +++ b/docs/design-failure-modes.md @@ -347,5 +347,5 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a refusal that is the FINAL policy is written up as a rung below a more capable one, and a next-rung trigger is filed promising to admit the case it deliberately refuses** (INVALID STATE: a class refuses some input; an author reasons that a future mechanism could HANDLE that input rather than refuse it, concludes the refusal is therefore below its ceiling, and files a DESIGN 4b(2) next-rung trigger naming the more capable mechanism. HARM: the trigger becomes a standing obligation to BREAK the semantics the refusal was chosen to protect, and it is discharged by making the system MORE PERMISSIVE, which moves the class DOWN the ladder while the roster records a climb. The row then reads as a known gap on the way to being closed, so the deliberate boundary loses its author and the next person to touch it satisfies the trigger in good faith. **THE RECOGNITION RULE, SCOPED TO WHAT IT CAN ACTUALLY CARRY: EXPANDING A DELIBERATELY CHOSEN REFUSAL BOUNDARY IS NOT A SAFETY CLIMB MERELY BECAUSE IT ADMITS MORE CASES.** Read the proposed trigger and ask whether discharging it would make the system ACCEPT AN INPUT THE DESIGN DELIBERATELY REFUSES. If so, the trigger is a capability request wearing a safety rung's clothes and belongs in a product decision rather than on the ladder. THE LADDER MEASURES HOW A DECISION IS ENFORCED, NOT HOW MANY CASES IT ADMITS -- but only the DECISION's enforcement, which is why the boundary must be a chosen one for this row to apply. **THE RULE WAS STATED TOO WIDELY IN THIS ROW'S FIRST WRITING AND IS CORRECTED HERE RATHER THAN SOFTENED.** It said a genuine next-rung trigger ALWAYS preserves which inputs are admitted and which refused, changing only whether the refusal is checked or unwritable. THAT IS FALSE ACROSS THE LADDER, and the counterexample is an ordinary rung-1 to rung-2 climb: a failure that previously OCCURRED and was contained -- by a total operation, a rollback, an isolation boundary -- begins being BLOCKED once a gate exposes it, so the admitted set genuinely shrinks and behaviour genuinely changes. A universal rule that forbids that would refuse most real climbs, and a row whose recognition rule fires on legitimate work is worse than no row: it teaches reviewers to discount it. So the class is scoped to a DELIBERATELY CHOSEN refusal boundary, which is the population both receipts below actually inhabit. **THE ORDER OF THE TWO QUESTIONS IS WHAT ACTUALLY GOES WRONG, AND IT IS AN ORDER ERROR RATHER THAN AN IGNORANCE ERROR.** 4b(2) says a class below its ceiling must name its next-rung trigger. That obligation is CONDITIONAL, and the condition is the part that gets skipped: the class must first be ESTABLISHED to sit below a ceiling. An author who feels the obligation before performing the derivation will always find a trigger, because a more capable mechanism can be imagined for any refusal. So the diligent-seeming move -- 'the rule says name a trigger, so I will name one' -- is exactly the move that manufactures the defect, and reviewers reward it because a row with a trigger looks more complete than a row without one. **BOUNDARY AGAINST ceiling_never_exercised_for_a_population_the_census_cannot_plan AND AGAINST AN UNDERIVED CEILING, which are the two nearest neighbours and arrive from the opposite direction.** Asserting a ceiling NUMBER without deriving it claims the class cannot climb; this class assumes without deriving that the class CAN climb. Both skip the same derivation and they fail in opposite directions, which is why they are separate rows rather than one: the repair for an underived ceiling is to derive it and possibly climb, and the repair here is to derive it and possibly DELETE THE TRIGGER. A single row would make one of those two repairs invisible. **RECEIPT, gunbc.scm.merge_base, 2026-09-06.** The squash merge refuses divergent per-path content at MANIFEST GRAIN rather than merging file contents line by line. This author wrote that absent line-level merging was 'a stated scope with a next-rung trigger: a text-difference capability'. The side-chat reviewer refused it: refusing divergent file contents can be the FINAL safety policy of the operation, and a text-difference capability increases ADMITTED MERGE POWER rather than safety. If the semantics are deliberately 'never synthesize file content neither author supplied', then text-level auto-merge VIOLATES those semantics instead of climbing toward them, and the trigger would have been an obligation to break the design. The trigger was not written; what was written instead is that manifest-grain conflict is a deliberate semantic boundary, with whether line-level merging is wanted AT ALL left open as a product question rather than presumed. **A SECOND RECEIPT ON THE SAME LANE AND THE SAME DAY, WHICH IS WHY THIS IS A CLASS AND NOT AN ANECDOTE.** gunbc.scm.integration records the consumed source as a commit reference, which stops being a durable anchor if source commit records are ever PHYSICALLY DELETED rather than merely kept out of target ancestry. The reflex was to file that as a next-rung trigger. It is not one: retirement is a capability nobody has asked for, nothing establishes that this cut sits below a ceiling, and a trigger would have committed a future author to build durable provenance for a deletion feature that may never exist. It is recorded in that module as a FUTURE BOUNDARY -- what would have to be designed IF the capability is ever wanted -- which carries the same information and creates no obligation. THE REPAIR IS USUALLY A RESTATEMENT, NOT A DELETION: the knowledge is worth keeping, and only its status as a debt is false. **CEILING: UNDERIVED, AND STATED SO RATHER THAN GUESSED, on this row's own rule.** Deciding mechanically whether a proposed trigger widens the admitted set requires comparing the semantics before and after a mechanism that does not exist yet, and this author has not established that comparison is decidable from modeled facts. Asserting a rung here would be the underived-ceiling error in a row about underived ceilings. What is honest today: the class is caught by review, it has been caught twice by one reviewer on one lane, and the instrument that found both was a question a person asked rather than anything executed. NEXT TRIGGER: none is named, because naming one would require knowing the ceiling. - **a walk whose native-stack depth grows with the depth of corpus-shaped data, with no declared bound** (the compiler recurses once per level of a structure whose depth is an input, not a constant. Past some corpus the process aborts on stack exhaustion. HARM: the abort is not a diagnostic -- it is a SIGSEGV turned into `thread ... has overflowed its stack` and an abort, so it carries no span, no diagnostic identity, and no partial result. The run neither succeeds nor refuses, and every downstream reader sees a crashed process rather than a located refusal. Worse, the abort is silent about its own cause -- it kills the process before the summary line, so the very output that would characterise the input is the output you lose. **THE DISTINGUISHING FACT IS THAT THE RECURSION NEED NOT APPEAR IN OUR SOURCE AT ALL.** The class has two instances and they differ in exactly this, which is why one row covers both and why looking only for authored recursion finds half of it. AUTHORED INSTANCE: `v1.compiler.infer_types` `node_type_shape` and `node_type_shape_argument_list` are mutually recursive over type structure with no depth bound, rendering `Primitive(...)`, `Named(...)`, `Product(...)`, `Coproduct(...)` by descending into children. A reader can find this one, and grep for `fn` names finds it. COMPILER-GENERATED INSTANCE: `v1.std.core` `Node` carries `children: Rc>>` (and `params`, `uses`, `properties`, `body`, `type_annotation` likewise) with no hand-written `Drop`, so the DESTRUCTOR rustc synthesises recurses once per level on every release of a deep graph. **No reading of our own source can find it, because it is not in our source.** Grepping for recursion, auditing every `fn`, and eliminating every in-window candidate by inspection all report health -- and did, on the specimen below, where three code candidates in the failing window were correctly eliminated by reading while the actual recursion was in glue no one had written. **RECOGNITION RULE, and it is about the DATA, not about the code: any type whose values are trees or chains of unbounded depth carries this class in every operation that touches a whole value -- including the ones you did not write.** Construction, traversal, rendering, cloning, comparison, hashing, serialisation and RELEASE are all depth-recursive by default. The question to ask of a recursive carrier is not `which of my functions recurse` but `what happens to a value of this type at 10^6 levels`, and the honest answer is usually that nobody knows because nobody has one. **RECEIPT, EXECUTED, WITH A PAIRED CONTROL AND ONE CHANGED LINE** (2026-09-05, quick-heron-85, during the NS-N containment cut). A two-file subject -- `std.measure` plus `test.claim.long.reference_closure_equivalence_test`, compiled with `src/v2` and `src/v1` as name-pool roots -- aborts with `stack overflow` after four pipeline phases and before its diagnostic summary prints. THE STACK-THRESHOLD INSTRUMENT TURNS THE BINARY INTO A SCALAR: bisecting `ulimit -s` gives the minimum stack at which each configuration completes. Each file ALONE completes at 1024 KiB; the PAIR completes at 147456 KiB and overflows at 139264 KiB. A factor of at least 144, closed on both sides, which is what establishes the recursion as FINITE AND DEEP rather than unbounded-in-the-limit -- an unlimited stack completes. THE DISCRIMINATOR IS THE ONE-LINE PROBE, not the threshold: `std::mem::forget(resolved.clone())` at the head of `v1.compiler.compile` `emit_resolved_for_target_selected`, holding files, binary invocation, source roots, stack size and diagnostics fixed and changing ONLY whether the tree is ever released. Paired at the same stock 8192 KiB: unpatched OVERFLOW, leaked binary exit 1, zero `stack overflow`, and the summary line prints (496 blocking, 2643 advisory). The destructor was the recursion. **THE PROBE MUST FORGET A CLONE, NOT THE VALUE**, and this is the part worth carrying to the next investigator: `resolved` is an `Rc`, so forgetting IT merely drops one handle and moves the destructor to whoever holds the last one -- reading as a false PERSIST and killing a true hypothesis. Forgetting a CLONE pins the outer refcount above zero, so the contents can never be released no matter who else holds a handle. That converts `check the precondition` into `remove the failure mode`, which is the difference between a probe you have to trust and one that cannot lie. **WHAT DID NOT DISCRIMINATE, recorded because each looked decisive and cost a cycle:** diagnostic COUNT is blind -- a probe rendering 8,000 diagnostics does not overflow while this configuration's 3,139 does; per-diagnostic SHAPE is blind unless the shape is in the graph; and a 2,000-level-deep authored expression chain completes. The depth that matters is the depth of the structure RELEASED, which no probe varying the diagnostic list can reach. **RUNG FOUND AT: 1, MITIGATABLE, by fail-stop and process isolation -- AND SECTION 5's FAIL-CLOSED INTERFACE IS STILL VIOLATED. Both halves belong in the sentence.** The mitigation is real and is what earns the rung: the abort is a fail-stop at a process boundary, and that boundary PREVENTS AN ACCEPTED RESULT OR ANY OTHER SUCCESS PUBLICATION -- the run dies before its summary line, so no consumer receives a partial or fabricated answer. Section 4b names isolation among rung-1 mechanisms, so the absence of a typed result does not deny rung 1. **BUT RUNG 1 DOES NOT REHABILITATE THE SIGSEGV**: section 5 independently requires every path to succeed fully or fail with a TYPED, LOCATED diagnostic, and an abort carrying no subject, no span and no diagnostic identity fails that badly. The deficit is a rung-1 class with a standing section 5 interface violation, which is what makes it rank for repair. **AN EARLIER REVISION OF THIS ROW PLACED THE CLASS OUTSIDE THE LADDER, AND THAT WAS WRONG IN A WAY WORTH KEEPING VISIBLE** (corrected 2026-09-06, native review 5124409497). Section 4b's only exclusions are silent wrongness below the floor and the adjacent column of external reality, undecidable properties and undeclared intent; this class is none of those, and the row REFUTED ITS OWN EXCLUSION two sentences later by stating that the depth property is decidable, that nothing waits on external reality, and that both a declared bound and an iterative implementation are authorable. **EXCUSING A CLASS FROM HAVING A RUNG HAS THE SAME PRACTICAL EFFECT AS INFLATING ONE -- IT NEVER RANKS**, which is the failure this ledger exists to prevent, arriving from the low side rather than the high one. **CEILING: 3, STRUCTURALLY GUARANTEED -- NOT 4, BECAUSE DECIDABLE DOES NOT IMPLY RUNG 4.** Depth is decidable and nothing waits on external reality, but decidability establishes only that a structural decision procedure is POSSIBLE. If an unbounded recursive carrier remains WRITABLE while the compiler derives a proof or a refusal for it, that is rung 3. **RUNG 4 REQUIRES THAT THE INVALID STATE HAVE NO CONSTRUCTOR IN THE CANONICAL MODEL, AND THIS ROW CANNOT NAME THE CONSTRUCTOR-ELIMINATION PATH**: no canonical carrier model is in hand in which data-dependent native-stack recursion is inexpressible, and a recursive carrier is a thing the substrate is meant to be able to declare. The ceiling is therefore raised to 4 only by a future row that NAMES that model; until then, claiming 4 here would be the inflation this ledger refuses. **THE REPAIR MAY NOT BE A BIGGER STACK, A FATTER THREAD, OR A RAISED BUDGET, AND THE LEAK IS AN INSTRUMENT RATHER THAN AN ARM.** All four leave the abort exactly where it was and move the corpus at which it fires, which is section 5's absorbing fallback in its purest form: the failure arm WIDENS instead of refusing, the widening is denominated in the corpus rather than the change, and the deficit stops ranking for repair because the symptom moved. The two honest arms are a release and traversal whose depth does not grow with the data -- an explicit iterative teardown and an explicit worklist walk -- or a DECLARED depth bound enforced at construction, refusing a too-deep structure with a typed, located diagnostic naming the carrier and the level. **THE TRIGGER BELOW IS A DELIBERATE 1-TO-3 JUMP, STATED SO IT IS NOT MISREAD AS THE ADJACENT RUNG.** Deriving the risk from the declared carrier and failing compilation when an unbounded carrier lacks an iterative release or a declared bound is a STRUCTURAL GUARANTEE, rung 3 -- it skips rung 2 rather than passing through it. The rung-2 form, if the jump proves too large to take in one step, is a generated lens or an adversarial-depth witness that reliably exposes and blocks the class WHILE THE INVALID IMPLEMENTATION STAYS WRITABLE and safety depends on that mechanism executing and staying enrolled. **NEXT-RUNG TRIGGER, and it names a CAPABILITY rather than a repair to one carrier:** a mechanism that derives, from a declared carrier, whether any operation on it -- including generated ones -- can recurse without a bound, so that authoring an unbounded-depth carrier without an iterative release or a declared bound fails to compile. Repairing `Node` alone does not retire this row; it removes one instance, and `node_type_shape` would still stand, as would every carrier authored tomorrow. **BOUNDED AGAINST TWO NEIGHBOURS.** Section 5's absorbing fallback is a mechanism that WIDENS when it cannot answer precisely; here nothing widens and nothing is answered -- the process dies. And `right_censored_cost_read_as_exact` concerns a measurement truncated by a limit; the resemblance is superficial, because there the run completes and reports a bounded number, whereas here the limit destroys the run and its report together, which is precisely why the threshold instrument had to be built to get any number at all. **WHY THIS RECEIPT NAMES NO PRODUCER, STATED RATHER THAN LEFT AS AN OMISSION** (raised as a section 6 objection, review 61308). Section 6 requires a measurement to be cited by naming the producer that re-derives it, because A TRANSCRIBED NUMBER IS UNREACHABLE FROM THE THING THAT OWNS IT AND ROTS WITHOUT ANYONE TOUCHING EITHER END. **THE OWNER OF THESE FIGURES NO LONGER EXISTS.** Every number here was observed on a configuration requiring a resolver rule that is not on main and, by ruling, never lands in that shape; the specimen is extinct, so NO ENTRY POINT CAN RE-DERIVE IT AND NONE IS OWED -- which is the case section 6 names in its own final clause, a one-off rather than an instrument. What is recorded is therefore a HISTORICAL OBSERVATION OF AN EXTINCT CONDITION, not a standing measurement, and it cannot rot in section 6's sense because it asserts nothing about the current tree. Without this sentence a reader concludes the producer was omitted rather than impossible, which is why the absence is declared instead of left to be noticed. **THE POPULATION ON MAIN IS UNMEASURED AND THE ROW DOES NOT CLAIM OTHERWISE, WHICH IS A DENOMINATOR FACT AND NOT BOOKKEEPING** (established 2026-09-06, after this row was first written, by the three-cell probe that identified what builds the chain). THE SOLE SPECIMEN WAS PRODUCED BY A RESOLVER RULE THAT IS NOT PRESENT ON MAIN: admitting the one refused callable owner collapses the specimen from 1,800,008 to 85, with the deepest site moving to an unrelated module, and the same instrument reads 85 under main and under the merge base. **THE TWO LEVELS ARE CONDITIONAL OBSERVATIONS AND MUST NOT BE LIFTED OUT AS PROPERTIES OF "MAIN" AND "THE CUT".** They were measured under one fixed execution condition -- that corpus, that traversal order, that pool construction, that binary and instrumentation -- and the pool construction in force is itself order-dependent, so the absolute levels are traversal-order-contaminated. WHAT GENERALISES IS THE DISCRIMINATING EFFECT OF CHANGING THE PREDICATE, not the numbers: a defective traversal regime can move the absolute state presented to every cell alike, but it cannot manufacture a DIFFERENCE between cells when that regime was held fixed. So the collapse stands as causal evidence with its held condition attached, and 85 is not a fact about main. **WHETHER ORDINARY CORPUS GROWTH ALONE CAN REACH COMPARABLE DEPTH IS UNMEASURED, AND NOBODY HAS SHOWN THAT IT CAN.** THE PRODUCING DEFECT IS NAMED SO THE TWO ROWS CONNECT RATHER THAN LEAVING THE NEXT READER TO RE-DERIVE IT: the depth was reached because an UNRESOLVABLE CALL WAS ELABORATED RATHER THAN REFUSED, which is filed as the call-site form of `gunbc.recurring_failure_mode.state_space_conflation` -- the callable-lookup carrier collapses builtin, locally-bound and genuinely-missing into one constructor, and the consumer reads that collapsed absence as affirmative formal authority. **STATED IN BOTH DIRECTIONS, BECAUSE EACH HALF GUARDS AGAINST THE OPPOSITE MISREAD.** A reader must not conclude that corpus growth alone reaches this depth -- that is the unmeasured half above. A reader must ALSO not conclude that the structure was a harness artefact: NOTHING WAS FORCED IN THE RUN THAT PRODUCED IT. The 1,800,008 baseline is an ordinary compile under an ordinary resolver rule; the forced admissions were the two PROBES placed either side of that baseline to identify its cause. The structure was real, an accepted compile built it, and the class it exhibits is independent of whichever defect supplied the depth -- which is why this row stands on its own and does not wait on that defect's repair. **THE SECOND DEFECT THIS SPECIMEN EXPOSES IS NOT THIS ROW AND MUST NOT BE ABSORBED INTO IT:** nothing in two source files is 10^5 levels deep, so some pipeline stage BUILDS a structure far deeper than its input -- a chain where a flat structure was intended. That is a separate defect, for which this class is only the reporter, and it will be pathological for every consumer that walks the structure rather than only the one that releases it.) - **DESTRUCTIVE CLEANUP CORRECTNESS DEPENDING ON AN UNDECLARED EVALUATION-MODEL PROPERTY. THREE TRANSPORTS ARE CORRECT BY ACCIDENT OF REALIZATION, NOT BY CONSTRUCTION. INVALID STATE:** the interpreter implements `let` as eager/strict (eval_let evaluates the value expression unconditionally at the point of binding). Nothing declares this as a language guarantee — it is a property of the current interpreter realization, not a declared semantic axis. The companion divergence — that interpreter `&&` evaluates both operands while emitted Rust `&&` short-circuits — is already filed as realization_arms_diverge_on_whether_the_program_refuses. It does not affect these transports: their cleanups are let-bound, not &&-bound, and under eager let a short-circuit && cannot change any conjunction's value because it skips only the right operand when the left is false — a path where the answer is already determined and the skipped operand is a pure field read (.success) on a record whose effect already fired at its let binding. STRUCTURALLY, no transport in this class depends on && eagerness, and no addition to the corpus can create such a dependence as long as let stays eager. If `let` became lazy, cleanup would never run in the namespace_import_closure_behavioral_transport (where cleanup is bound but never consumed), and would be unobserved in the other two (where cleanup .success would not be consumed, though the eager `let` at binding point would still fire). The distinction matters: 'effect does not reach the filesystem' is a leak; 'effect reaches the filesystem but its success is unobserved' is an unobserved cleanup failure — different harms, different severities.POPULATION, BOUNDED AND ENUMERABLE — THE THREE TRANSPORTS: `tools.self_host_logic_behavioral_transport::slb_comparison_observation` (let-bound cleanup_src/cleanup_out consumed in the staged conjunction), `tools.emit_host_transport::run_rust_smoke` / `::run_python_smoke` / `::run_go_smoke` (let-bound cleanup consumed in a conjunction), `tools.namespace_import_closure_behavioral_transport::nic_emitted_crate_evidence` (let-bound cleanup_src/cleanup_out NEVER consumed — correct only because let is eager).CEILING: cleanup that does not depend on the evaluation model at all — an effect the transport sequences explicitly rather than one it gets from strictness. Every transport's destructive cleanup would be structurally sequenced (e.g. a `do cleanup after body` pattern) rather than relying on let-eagerness for its effect to reach the filesystem.NEXT-RUNG TRIGGER, A CAPABILITY — either (a) the evaluation model declared as a typed guarantee that a check can execute against — a property of the interpreter that is named, bounded, and verifiable, not prose — or (b) every transport whose correctness turns on eager evaluation sequences its destructive effects explicitly rather than inheriting them from let-strictness. Both capabilities are genuine transitions; neither is satisfied by documentation or by this row.CORPUS POPULATION MEASUREMENT — the && divergence has no live hazard in this corpus: zero sites place a non-total or effectful operation directly as the right operand of `&&` (the two found are read-only). Twenty sites let-bind destructive effects and then consume their .success in a `&&` chain; under the structural argument above, none depends on && eagerness. HOWEVER, A=0 is a SURVIVOR PROPERTY OF THE STRICTER ARM HAVING FILTERED THE CORPUS, not evidence the class is harmless. The interpreter evaluates both operands unconditionally, so any author who wrote `guard && dangerous` would have hit the refusal while authoring and rewritten before commit. Under a short-circuit `&&`, authors would write these patterns freely and they would compile — and the emitted arm would silently produce a wrong answer where the interpreter refuses. That is the existing row's subject.INSTRUMENT — DISCRIMINATING PROBES, THEIR NAMES AND INVOCATION, SO A FUTURE READER CAN RE-DERIVE THIS FINDING AND RE-RUN IT. Five executed probes in module `test.claim.eval_model_probe` (dag/test/claim/eval_model_probe_test.dag): `let_is_eager_when_unused` (let-binds a Remove without consuming its result; PASS means eager), `let_is_eager_when_consumed` (positive control), `and_demands_right_even_when_left_false` (let-binds false and Remove separately, && computes; PASS means Remove ran despite false), `and_short_circuit_direct_probe` (false && Remove directly; PASS means Remove ran), `and_with_true_left_removes_file` (positive control). Each was run as: `gunbc run --source-root dag --source-root src/v2 --entry dag/test/claim/eval_model_probe_test.dag --function --claim-run`. All five returned PASS (exit 0). Probes observed the filesystem side effect out-of-band (shell.Test.IsFile) rather than consuming a returned value, so no measurement-created dependency could confound the result. -- **generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. The refusal exits 2 with EMPTY STDOUT and interpolates the rendered value into the diagnostic on stderr: classify_exit's non-variant arm is type_name: ctx.format_value(other). Executed: wise-badger-902, EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown heading; quick-heron-85, proj3.err 1062137 bytes, same delimiters. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` forbids print-AND-SUCCEED, and holds. The content is therefore obtainable ONLY by scraping a half-megabyte stderr diagnostic between delimiters that are message prose (returned-open-quote and not-ProcessExit-close-quote), unversioned and enrolled by nothing. Extractable only by scraping an unversioned diagnostic — which is why the ProcessExit regen is the repair. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. RUNG FOUND AT: outside the ladder until a completing content agreement exists — silent wrongness, not a weak wall. After a ProcessExit projector and a required content compare execute, mechanically preventable: the stale file remains writable. CEILING: structurally guaranteed when membership of every committed generated `docs/` path in a content-agreement adjudicator is derived from `gunbc.generated_artifact` `committed_generated_artifacts`, so omitting the arm is a compile refusal. NEXT-RUNG TRIGGER, a capability: required-run dispatch consumes a content agreement — byte identity of generated vs committed — over every committed generated artifact whose location directory is `docs`, derived from the registry so adding a committed `docs/` path without that agreement will not compile. A trigger naming one command, one test file, or `docs_projection_gate.dag` is less than the capability and would fire while edit-only drift can still sit uncompared.) +- **generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. The refusal exits 2 with EMPTY STDOUT and interpolates the rendered value into the diagnostic on stderr: classify_exit's non-variant arm is type_name: ctx.format_value(other). Executed: wise-badger-902, EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown heading; quick-heron-85, proj3.err 1062137 bytes, same delimiters. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` forbids print-AND-SUCCEED, and holds. The content is therefore obtainable ONLY by scraping a half-megabyte stderr diagnostic between delimiters that are message prose (returned-open-quote and not-ProcessExit-close-quote), unversioned and enrolled by nothing. Extractable only by scraping an unversioned diagnostic — which is why the ProcessExit regen is the repair. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. INCIDENT, not a fixture and not a perturbation ordered for this PR, produced by quick-heron-85 without looking for this class, commit b1c6378c6d: their authority row unresolved_callee_conflated_with_dispatched_elsewhere was UNPARSEABLE for a full commit. An authored receipt contained a quoted phrase using bare double quotes inside a double-quoted string; the string terminated early and the parser refused undefined variable this and undefined variable leaf. THE AUTHORITY DID NOT COMPILE, so docs/design-failure-modes.md kept rendering the previous vintage while presenting as current. They found it only by running the projection; reading the row does not surface the break. This PR's gate would go red on that commit because an unparseable authority makes the projector refuse — from the committed markdown alone, STALE bytes and an UNBUILDABLE authority are indistinguishable. THIS ROW IS THE DETECTOR GAP. The quoting injury that silently disabled the row they were filing is a neighbouring class they are filing separately; its next-rung trigger is string escaping in the .dag lexer. This gate makes that injury loud; it does not make it unwritable. Do not read this row as closing that class. THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. RUNG FOUND AT: outside the ladder until a completing content agreement exists — silent wrongness, not a weak wall. After a ProcessExit projector and a required content compare execute, mechanically preventable: the stale file remains writable. CEILING: structurally guaranteed when membership of every committed generated `docs/` path in a content-agreement adjudicator is derived from `gunbc.generated_artifact` `committed_generated_artifacts`, so omitting the arm is a compile refusal. NEXT-RUNG TRIGGER, a capability: required-run dispatch consumes a content agreement — byte identity of generated vs committed — over every committed generated artifact whose location directory is `docs`, derived from the registry so adding a committed `docs/` path without that agreement will not compile. A trigger naming one command, one test file, or `docs_projection_gate.dag` is less than the capability and would fire while edit-only drift can still sit uncompared.) - **a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. SPECIMEN, 2026-09-06, executed both ways. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs NotProcessExit with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the interpolation site and ruled the same. quick-heron-85 held proj3.err at 1062137 bytes. The manager then RAN the projector: EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown. The field name said type; the field held format_value. WHY THE FORK SURVIVED, and it is a section 4b evidence gap not a naming smell. `non_process_exit_refuses_rather_than_printing_and_succeeding` hand-constructs ExitClass::NotProcessExit with type_name the literal Bool and starts from an already-classified value. It never calls `classify_exit`. Nothing executing joins the field's name to its contents on the non-variant path — the only enrolled evidence feeds the field a literal that matches its name, which is exactly the input that cannot discriminate. The control's RED is unauthorable for this defect, so it was permanently green over the case that mattered and got cited as coverage. A field carrying two facts is ordinary; a field carrying two facts with a control that only ever sees one of them is why three of us read adjacent code correctly and reached three answers. THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. RUNG FOUND AT: mitigatable — the wrong inference is a human reading; the runtime still refuses with exit 2 and does not print-and-succeed. CEILING: structurally impossible when `NotProcessExit` cannot be constructed with a rendered value under a field named for a type — two variants, or a field named for what it actually holds. NEXT-RUNG TRIGGER, a capability: `ExitClass` distinguishes a type-name refusal from a rendered-value refusal in the type, so interpolating one as the other will not compile.) From 2f22545321b7db6601f9f3f8c9d6b0634557b523 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 01:28:21 +0000 Subject: [PATCH 13/22] Retract the heal-dies mechanism from the docs-projection row. heal main_wet completed on another branch; the 137 was claim_executor. Comments now mark tools.ci_gates.exit_ok as deleted. Co-authored-by: Cursor --- dag/gunbc/instruments/dag_compile_clean_gate.dag | 4 ++-- dag/gunbc/instruments/generated_artifact_gate.dag | 4 ++-- .../docs_markdown_projections_have_no_completing_actuator.dag | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dag/gunbc/instruments/dag_compile_clean_gate.dag b/dag/gunbc/instruments/dag_compile_clean_gate.dag index 394512890cc..8515be9b76c 100644 --- a/dag/gunbc/instruments/dag_compile_clean_gate.dag +++ b/dag/gunbc/instruments/dag_compile_clean_gate.dag @@ -4,8 +4,8 @@ import std.process { ProcessExit, ExitSuccess, exit_failure } import gunbc.ci_gate { gate_receipt_exit, gate_receipt_failure_detail } import tools.dag_compile_clean_transport { run_clean_tree_compile } -// The loudness half: the floor witness collapses this gate's ProcessExit to Bool via -// tools.ci_gates.exit_ok, so compile-clean reds reported only returned Bool(false). The gate +// The loudness half: the floor witness used to collapse this gate's ProcessExit to Bool via +// tools.ci_gates.exit_ok (deleted; call sites now match ProcessExit), so compile-clean reds reported only returned Bool(false). The gate // consumes the executor's one in-run compile receipt; the failure-receipt companion projects the // same receipt's located diagnostic without a second compile. BOTH HALVES NOW READ ONE RECEIPT. // Before this retype the body branched on a Bool and, on the false side, INVENTED its reason -- diff --git a/dag/gunbc/instruments/generated_artifact_gate.dag b/dag/gunbc/instruments/generated_artifact_gate.dag index 3fed36fde3f..1e4cc67020a 100644 --- a/dag/gunbc/instruments/generated_artifact_gate.dag +++ b/dag/gunbc/instruments/generated_artifact_gate.dag @@ -94,8 +94,8 @@ func run_generated_artifact_drift_gate() -> ProcessExit { run_generated_artifact_drift_gate_body() } -// The loudness half: the floor witness collapses this gate's ProcessExit to Bool via -// tools.ci_gates.exit_ok, so a generated-artifact drift red reported only returned Bool(false) +// The loudness half: the floor witness used to collapse this gate's ProcessExit to Bool via +// tools.ci_gates.exit_ok (deleted; call sites now match ProcessExit), so a generated-artifact drift red reported only returned Bool(false) // without the located path. The gate body records the reason from its single // observe_generated_artifact_population pass; the failure-receipt companion consumes that retained // detail instead of re-observing. THE BODY NOW RECORDS THE CLEAN SIDE TOO, and that is the whole diff --git a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag index baaa05f3127..428845559be 100644 --- a/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag +++ b/dag/gunbc/recurring_failure_mode/docs_markdown_projections_have_no_completing_actuator.dag @@ -18,7 +18,7 @@ data docs_markdown_projections_have_no_completing_actuator: RecurringFailureMode "THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. ", - "THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. Heal-generated-artifacts already stages docs/design-failure-modes.md by name after generated_artifact_gate main_wet; that is not an absent gate. The path is registered, and the actuator dies on a fold whose cost is the whole registry rather than the two files that changed. Registration without a completing actuator is worse than absence, because it looks covered. ", + "THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. ", "RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. ", From 9384292b3e763ca276e43480941273f6eeef65da Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 01:59:11 +0000 Subject: [PATCH 14/22] Drop false main_wet-cannot-complete claims; untranscribe the registry fold. required-regen ownership motivates the docs actuator. Ordering still defends against an observed whole-registry stall; the fold is not a 35. Co-authored-by: Cursor --- dag/gunbc/instruments/docs_projection_gate.dag | 8 +++----- src/v1/stage0/src/bin/claim_executor.rs | 2 +- src/v1/stage0/src/generated_artifact_boundary_host.rs | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/dag/gunbc/instruments/docs_projection_gate.dag b/dag/gunbc/instruments/docs_projection_gate.dag index 9f9ece60279..6457046b0b4 100644 --- a/dag/gunbc/instruments/docs_projection_gate.dag +++ b/dag/gunbc/instruments/docs_projection_gate.dag @@ -24,11 +24,9 @@ fn write_docs_projection(path: String, content: String) -> ProcessExit { // gunbc.generated_artifact_gate main_wet folds the WHOLE committed registry, and resolving // gunbc.generated_artifact_emit pulls every generator (plans, workflows, CAD, stage0 mirrors). // This entry still imports gunbc.generated_artifact for artifact_path only — the location -// authority — not generated_artifact_emit. Authors filing a recurring-failure-mode row then -// ran claim_executor --required-regen (which never owns these files) or main_wet (which does -// not return a ProcessExit on the runners that SIGKILL it), so three rostered rows sat in the -// authority while docs/design-failure-modes.md stayed stale and nothing that completes could -// say so. +// authority — not generated_artifact_emit. claim_executor --required-regen never owns these +// files: is_compared_generated_basename is ends_with .rs. That ownership gap is the motive; +// this entry returns ProcessExit over the two ledger paths. func regen() -> ProcessExit { let first = write_docs_projection( path: artifact_path(a: DesignFailureModesArtifact), diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index 9b9e40aca00..589cf7c915f 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -846,7 +846,7 @@ fn run() -> Result { // DOCS LEDGER HALF. A dedicated ProcessExit entry whose closure is the two ledger // renderers, not gunbc.generated_artifact_emit. Run even after a mirror refusal, and // before the whole-registry resolve, so a stale docs/design-failure-modes.md cannot - // hide behind a SIGKILL or MemoryStall of the 35-artifact fold. + // hide behind a SIGKILL or MemoryStall of the whole-registry fold. match v1_compiler::cli_run::run_docs_projection_agreement(&source_roots) { v1_compiler::cli_run::DocsProjectionAgreement::Clean => { eprintln!("required-ci: generated-artifact population=docs-projections OK"); diff --git a/src/v1/stage0/src/generated_artifact_boundary_host.rs b/src/v1/stage0/src/generated_artifact_boundary_host.rs index 2ffffaaae16..49ca6bae800 100644 --- a/src/v1/stage0/src/generated_artifact_boundary_host.rs +++ b/src/v1/stage0/src/generated_artifact_boundary_host.rs @@ -46,7 +46,7 @@ pub const GENERATED_ARTIFACT_PRODUCING_COMMAND: &str = /// Regenerates only the two `docs/*.md` ledger projections and returns `ProcessExit`. /// Distinct from `GENERATED_ARTIFACT_PRODUCING_COMMAND`: that entry resolves the whole -/// registry emit graph and does not complete on the runners that SIGKILL `main_wet`. +/// registry emit graph. This one is the two ledger paths only. /// The operator-facing recipe is `tools.docs_projection_agreement` `docs_projection_regen_command` /// and is already inside the gate's `ProcessExit` reason; this host does not mint a second copy. const DOCS_PROJECTION_GATE_ENTRY: &str = "dag/gunbc/instruments/docs_projection_gate.dag"; From 8750b04b89afea1ff651add3003121218cd8b110 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 02:35:12 +0000 Subject: [PATCH 15/22] Restore exhaustive std.process exit_ok and drop stale residue citations. Claim Bool collapse has one home; sequencers still match ProcessExit. Residue rows named the deleted instrument copies. Co-authored-by: Cursor --- .../instruments/dag_compile_clean_gate.dag | 4 +- .../instruments/floor_effect_gate_witness.dag | 37 ++++--------------- dag/gunbc/non_fold_residue.dag | 10 ----- dag/std/process.dag | 8 ++++ 4 files changed, 18 insertions(+), 41 deletions(-) diff --git a/dag/gunbc/instruments/dag_compile_clean_gate.dag b/dag/gunbc/instruments/dag_compile_clean_gate.dag index 8515be9b76c..6c6415766df 100644 --- a/dag/gunbc/instruments/dag_compile_clean_gate.dag +++ b/dag/gunbc/instruments/dag_compile_clean_gate.dag @@ -4,8 +4,8 @@ import std.process { ProcessExit, ExitSuccess, exit_failure } import gunbc.ci_gate { gate_receipt_exit, gate_receipt_failure_detail } import tools.dag_compile_clean_transport { run_clean_tree_compile } -// The loudness half: the floor witness used to collapse this gate's ProcessExit to Bool via -// tools.ci_gates.exit_ok (deleted; call sites now match ProcessExit), so compile-clean reds reported only returned Bool(false). The gate +// The loudness half: the floor witness collapses this gate's ProcessExit to Bool via +// std.process.exit_ok, so compile-clean reds reported only returned Bool(false). The gate // consumes the executor's one in-run compile receipt; the failure-receipt companion projects the // same receipt's located diagnostic without a second compile. BOTH HALVES NOW READ ONE RECEIPT. // Before this retype the body branched on a Bool and, on the false side, INVENTED its reason -- diff --git a/dag/gunbc/instruments/floor_effect_gate_witness.dag b/dag/gunbc/instruments/floor_effect_gate_witness.dag index 2f19a55e540..412536883e1 100644 --- a/dag/gunbc/instruments/floor_effect_gate_witness.dag +++ b/dag/gunbc/instruments/floor_effect_gate_witness.dag @@ -5,7 +5,7 @@ import gunbc.ci_spec { ExtdepsExternalAuthorityGate, DagCompileCleanGate, GeneratedArtifactDriftGate, ExtdepsScopePlacementGate, ProseRowIntroductionGate } -import std.process { ExitSuccess, ExitFailure } +import std.process { exit_ok } import tools.ci_gates { run_spec_gate } import tools.extdeps_scope_placement_gate { scope_placement_failure_receipt } import tools.prose_row_introduction_gate { prose_row_gate_failure_receipt } @@ -38,45 +38,27 @@ import tools.generated_artifact_gate { generated_artifact_drift_failure_receipt // projection from this entry's declarations (see loudness_witness_note sequencing note). func emit_host_gate_passes() -> Bool { - match run_spec_gate(g: EmitHostGate) { - ExitSuccess => true - ExitFailure { code, reason } => false - } + exit_ok(e: run_spec_gate(g: EmitHostGate)) } func cheap_claim_pool_gate_passes() -> Bool { - match run_spec_gate(g: CheapClaimPoolGate) { - ExitSuccess => true - ExitFailure { code, reason } => false - } + exit_ok(e: run_spec_gate(g: CheapClaimPoolGate)) } func extdeps_external_authority_gate_passes() -> Bool { - match run_spec_gate(g: ExtdepsExternalAuthorityGate) { - ExitSuccess => true - ExitFailure { code, reason } => false - } + exit_ok(e: run_spec_gate(g: ExtdepsExternalAuthorityGate)) } func dag_compile_clean_gate_passes() -> Bool { - match run_spec_gate(g: DagCompileCleanGate) { - ExitSuccess => true - ExitFailure { code, reason } => false - } + exit_ok(e: run_spec_gate(g: DagCompileCleanGate)) } func generated_artifact_drift_gate_passes() -> Bool { - match run_spec_gate(g: GeneratedArtifactDriftGate) { - ExitSuccess => true - ExitFailure { code, reason } => false - } + exit_ok(e: run_spec_gate(g: GeneratedArtifactDriftGate)) } func extdeps_scope_placement_gate_passes() -> Bool { - match run_spec_gate(g: ExtdepsScopePlacementGate) { - ExitSuccess => true - ExitFailure { code, reason } => false - } + exit_ok(e: run_spec_gate(g: ExtdepsScopePlacementGate)) } func extdeps_scope_placement_gate_failure_receipt() -> String { @@ -84,10 +66,7 @@ func extdeps_scope_placement_gate_failure_receipt() -> String { } func prose_row_introduction_gate_passes() -> Bool { - match run_spec_gate(g: ProseRowIntroductionGate) { - ExitSuccess => true - ExitFailure { code, reason } => false - } + exit_ok(e: run_spec_gate(g: ProseRowIntroductionGate)) } func prose_row_introduction_gate_failure_receipt() -> String { diff --git a/dag/gunbc/non_fold_residue.dag b/dag/gunbc/non_fold_residue.dag index 624ded783eb..08d41862a60 100644 --- a/dag/gunbc/non_fold_residue.dag +++ b/dag/gunbc/non_fold_residue.dag @@ -597,16 +597,6 @@ data non_fold_residue_frontier: List = [ reason: nfr_reason_mint_era, dissolution: nfr_dissolve_wildcard_total, }, - FrontierRow { - subject: PathSubject { path: "dag/gunbc/instruments/ci_gates.dag::exit_ok" }, - reason: nfr_reason_mint_era, - dissolution: nfr_dissolve_wildcard_total, - }, - FrontierRow { - subject: PathSubject { path: "dag/gunbc/instruments/generated_artifact_gate.dag::exit_ok" }, - reason: nfr_reason_mint_era, - dissolution: nfr_dissolve_wildcard_total, - }, FrontierRow { subject: PathSubject { path: "src/v2/compiler/01_tokenize.dag::lex_try_rules_prefer_longer" }, reason: nfr_reason_mint_era, diff --git a/dag/std/process.dag b/dag/std/process.dag index b7f1f55550a..dec64ddd38c 100644 --- a/dag/std/process.dag +++ b/dag/std/process.dag @@ -14,3 +14,11 @@ type ProcessExit fn exit_failure(reason: String) -> ProcessExit { ExitFailure { code: exit_code_general_error, reason: reason } } + +// Claim-surface inhabitance of ExitSuccess. Sequencers match ProcessExit; they do not ask this for a Bool. +fn exit_ok(e: ProcessExit) -> Bool { + match e { + ExitSuccess => true + ExitFailure { code, reason } => false + } +} From 0b152712c703b475e92af68e90087d0f3f124878 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 02:35:36 +0000 Subject: [PATCH 16/22] Point the generated-artifact loudness comment at std.process.exit_ok. Co-authored-by: Cursor --- dag/gunbc/instruments/generated_artifact_gate.dag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dag/gunbc/instruments/generated_artifact_gate.dag b/dag/gunbc/instruments/generated_artifact_gate.dag index 1e4cc67020a..f903715036c 100644 --- a/dag/gunbc/instruments/generated_artifact_gate.dag +++ b/dag/gunbc/instruments/generated_artifact_gate.dag @@ -94,8 +94,8 @@ func run_generated_artifact_drift_gate() -> ProcessExit { run_generated_artifact_drift_gate_body() } -// The loudness half: the floor witness used to collapse this gate's ProcessExit to Bool via -// tools.ci_gates.exit_ok (deleted; call sites now match ProcessExit), so a generated-artifact drift red reported only returned Bool(false) +// The loudness half: the floor witness collapses this gate's ProcessExit to Bool via +// std.process.exit_ok, so a generated-artifact drift red reported only returned Bool(false) // without the located path. The gate body records the reason from its single // observe_generated_artifact_population pass; the failure-receipt companion consumes that retained // detail instead of re-observing. THE BODY NOW RECORDS THE CLEAN SIDE TOO, and that is the whole From 79687a993fd6717a74512d71a34ce6ab1121e043 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 03:33:59 +0000 Subject: [PATCH 17/22] chore: regenerate drifted generated artifacts (ci auto-heal) Ledger-Repair-Judged: docs/design-failure-modes.md Ledger-Rows-Repaired: docs/design-failure-modes.md excluded_witness_outlives_the_subject_it_names Ledger-Rows-Repaired: docs/design-failure-modes.md docs_markdown_projections_have_no_completing_actuator Ledger-Rows-Repaired: docs/design-failure-modes.md type_name_field_carries_rendered_value Ledger-Repair-Judged: docs/design-rung-drops.md --- docs/design-failure-modes.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/design-failure-modes.md b/docs/design-failure-modes.md index 7722e1ccdf5..3f6e5aa0fd8 100644 --- a/docs/design-failure-modes.md +++ b/docs/design-failure-modes.md @@ -165,8 +165,6 @@ The exact `RecurringFailureMode.identity` population, which no other projection - `permissiveness_promoted_into_a_higher_safety_rung` - `native_stack_recursion_unbounded_over_corpus_shaped_structure` - `evaluation_model_cleanup_undeclared` -- `docs_markdown_projections_have_no_completing_actuator` -- `type_name_field_carries_rendered_value` --- @@ -347,5 +345,3 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a refusal that is the FINAL policy is written up as a rung below a more capable one, and a next-rung trigger is filed promising to admit the case it deliberately refuses** (INVALID STATE: a class refuses some input; an author reasons that a future mechanism could HANDLE that input rather than refuse it, concludes the refusal is therefore below its ceiling, and files a DESIGN 4b(2) next-rung trigger naming the more capable mechanism. HARM: the trigger becomes a standing obligation to BREAK the semantics the refusal was chosen to protect, and it is discharged by making the system MORE PERMISSIVE, which moves the class DOWN the ladder while the roster records a climb. The row then reads as a known gap on the way to being closed, so the deliberate boundary loses its author and the next person to touch it satisfies the trigger in good faith. **THE RECOGNITION RULE, SCOPED TO WHAT IT CAN ACTUALLY CARRY: EXPANDING A DELIBERATELY CHOSEN REFUSAL BOUNDARY IS NOT A SAFETY CLIMB MERELY BECAUSE IT ADMITS MORE CASES.** Read the proposed trigger and ask whether discharging it would make the system ACCEPT AN INPUT THE DESIGN DELIBERATELY REFUSES. If so, the trigger is a capability request wearing a safety rung's clothes and belongs in a product decision rather than on the ladder. THE LADDER MEASURES HOW A DECISION IS ENFORCED, NOT HOW MANY CASES IT ADMITS -- but only the DECISION's enforcement, which is why the boundary must be a chosen one for this row to apply. **THE RULE WAS STATED TOO WIDELY IN THIS ROW'S FIRST WRITING AND IS CORRECTED HERE RATHER THAN SOFTENED.** It said a genuine next-rung trigger ALWAYS preserves which inputs are admitted and which refused, changing only whether the refusal is checked or unwritable. THAT IS FALSE ACROSS THE LADDER, and the counterexample is an ordinary rung-1 to rung-2 climb: a failure that previously OCCURRED and was contained -- by a total operation, a rollback, an isolation boundary -- begins being BLOCKED once a gate exposes it, so the admitted set genuinely shrinks and behaviour genuinely changes. A universal rule that forbids that would refuse most real climbs, and a row whose recognition rule fires on legitimate work is worse than no row: it teaches reviewers to discount it. So the class is scoped to a DELIBERATELY CHOSEN refusal boundary, which is the population both receipts below actually inhabit. **THE ORDER OF THE TWO QUESTIONS IS WHAT ACTUALLY GOES WRONG, AND IT IS AN ORDER ERROR RATHER THAN AN IGNORANCE ERROR.** 4b(2) says a class below its ceiling must name its next-rung trigger. That obligation is CONDITIONAL, and the condition is the part that gets skipped: the class must first be ESTABLISHED to sit below a ceiling. An author who feels the obligation before performing the derivation will always find a trigger, because a more capable mechanism can be imagined for any refusal. So the diligent-seeming move -- 'the rule says name a trigger, so I will name one' -- is exactly the move that manufactures the defect, and reviewers reward it because a row with a trigger looks more complete than a row without one. **BOUNDARY AGAINST ceiling_never_exercised_for_a_population_the_census_cannot_plan AND AGAINST AN UNDERIVED CEILING, which are the two nearest neighbours and arrive from the opposite direction.** Asserting a ceiling NUMBER without deriving it claims the class cannot climb; this class assumes without deriving that the class CAN climb. Both skip the same derivation and they fail in opposite directions, which is why they are separate rows rather than one: the repair for an underived ceiling is to derive it and possibly climb, and the repair here is to derive it and possibly DELETE THE TRIGGER. A single row would make one of those two repairs invisible. **RECEIPT, gunbc.scm.merge_base, 2026-09-06.** The squash merge refuses divergent per-path content at MANIFEST GRAIN rather than merging file contents line by line. This author wrote that absent line-level merging was 'a stated scope with a next-rung trigger: a text-difference capability'. The side-chat reviewer refused it: refusing divergent file contents can be the FINAL safety policy of the operation, and a text-difference capability increases ADMITTED MERGE POWER rather than safety. If the semantics are deliberately 'never synthesize file content neither author supplied', then text-level auto-merge VIOLATES those semantics instead of climbing toward them, and the trigger would have been an obligation to break the design. The trigger was not written; what was written instead is that manifest-grain conflict is a deliberate semantic boundary, with whether line-level merging is wanted AT ALL left open as a product question rather than presumed. **A SECOND RECEIPT ON THE SAME LANE AND THE SAME DAY, WHICH IS WHY THIS IS A CLASS AND NOT AN ANECDOTE.** gunbc.scm.integration records the consumed source as a commit reference, which stops being a durable anchor if source commit records are ever PHYSICALLY DELETED rather than merely kept out of target ancestry. The reflex was to file that as a next-rung trigger. It is not one: retirement is a capability nobody has asked for, nothing establishes that this cut sits below a ceiling, and a trigger would have committed a future author to build durable provenance for a deletion feature that may never exist. It is recorded in that module as a FUTURE BOUNDARY -- what would have to be designed IF the capability is ever wanted -- which carries the same information and creates no obligation. THE REPAIR IS USUALLY A RESTATEMENT, NOT A DELETION: the knowledge is worth keeping, and only its status as a debt is false. **CEILING: UNDERIVED, AND STATED SO RATHER THAN GUESSED, on this row's own rule.** Deciding mechanically whether a proposed trigger widens the admitted set requires comparing the semantics before and after a mechanism that does not exist yet, and this author has not established that comparison is decidable from modeled facts. Asserting a rung here would be the underived-ceiling error in a row about underived ceilings. What is honest today: the class is caught by review, it has been caught twice by one reviewer on one lane, and the instrument that found both was a question a person asked rather than anything executed. NEXT TRIGGER: none is named, because naming one would require knowing the ceiling. - **a walk whose native-stack depth grows with the depth of corpus-shaped data, with no declared bound** (the compiler recurses once per level of a structure whose depth is an input, not a constant. Past some corpus the process aborts on stack exhaustion. HARM: the abort is not a diagnostic -- it is a SIGSEGV turned into `thread ... has overflowed its stack` and an abort, so it carries no span, no diagnostic identity, and no partial result. The run neither succeeds nor refuses, and every downstream reader sees a crashed process rather than a located refusal. Worse, the abort is silent about its own cause -- it kills the process before the summary line, so the very output that would characterise the input is the output you lose. **THE DISTINGUISHING FACT IS THAT THE RECURSION NEED NOT APPEAR IN OUR SOURCE AT ALL.** The class has two instances and they differ in exactly this, which is why one row covers both and why looking only for authored recursion finds half of it. AUTHORED INSTANCE: `v1.compiler.infer_types` `node_type_shape` and `node_type_shape_argument_list` are mutually recursive over type structure with no depth bound, rendering `Primitive(...)`, `Named(...)`, `Product(...)`, `Coproduct(...)` by descending into children. A reader can find this one, and grep for `fn` names finds it. COMPILER-GENERATED INSTANCE: `v1.std.core` `Node` carries `children: Rc>>` (and `params`, `uses`, `properties`, `body`, `type_annotation` likewise) with no hand-written `Drop`, so the DESTRUCTOR rustc synthesises recurses once per level on every release of a deep graph. **No reading of our own source can find it, because it is not in our source.** Grepping for recursion, auditing every `fn`, and eliminating every in-window candidate by inspection all report health -- and did, on the specimen below, where three code candidates in the failing window were correctly eliminated by reading while the actual recursion was in glue no one had written. **RECOGNITION RULE, and it is about the DATA, not about the code: any type whose values are trees or chains of unbounded depth carries this class in every operation that touches a whole value -- including the ones you did not write.** Construction, traversal, rendering, cloning, comparison, hashing, serialisation and RELEASE are all depth-recursive by default. The question to ask of a recursive carrier is not `which of my functions recurse` but `what happens to a value of this type at 10^6 levels`, and the honest answer is usually that nobody knows because nobody has one. **RECEIPT, EXECUTED, WITH A PAIRED CONTROL AND ONE CHANGED LINE** (2026-09-05, quick-heron-85, during the NS-N containment cut). A two-file subject -- `std.measure` plus `test.claim.long.reference_closure_equivalence_test`, compiled with `src/v2` and `src/v1` as name-pool roots -- aborts with `stack overflow` after four pipeline phases and before its diagnostic summary prints. THE STACK-THRESHOLD INSTRUMENT TURNS THE BINARY INTO A SCALAR: bisecting `ulimit -s` gives the minimum stack at which each configuration completes. Each file ALONE completes at 1024 KiB; the PAIR completes at 147456 KiB and overflows at 139264 KiB. A factor of at least 144, closed on both sides, which is what establishes the recursion as FINITE AND DEEP rather than unbounded-in-the-limit -- an unlimited stack completes. THE DISCRIMINATOR IS THE ONE-LINE PROBE, not the threshold: `std::mem::forget(resolved.clone())` at the head of `v1.compiler.compile` `emit_resolved_for_target_selected`, holding files, binary invocation, source roots, stack size and diagnostics fixed and changing ONLY whether the tree is ever released. Paired at the same stock 8192 KiB: unpatched OVERFLOW, leaked binary exit 1, zero `stack overflow`, and the summary line prints (496 blocking, 2643 advisory). The destructor was the recursion. **THE PROBE MUST FORGET A CLONE, NOT THE VALUE**, and this is the part worth carrying to the next investigator: `resolved` is an `Rc`, so forgetting IT merely drops one handle and moves the destructor to whoever holds the last one -- reading as a false PERSIST and killing a true hypothesis. Forgetting a CLONE pins the outer refcount above zero, so the contents can never be released no matter who else holds a handle. That converts `check the precondition` into `remove the failure mode`, which is the difference between a probe you have to trust and one that cannot lie. **WHAT DID NOT DISCRIMINATE, recorded because each looked decisive and cost a cycle:** diagnostic COUNT is blind -- a probe rendering 8,000 diagnostics does not overflow while this configuration's 3,139 does; per-diagnostic SHAPE is blind unless the shape is in the graph; and a 2,000-level-deep authored expression chain completes. The depth that matters is the depth of the structure RELEASED, which no probe varying the diagnostic list can reach. **RUNG FOUND AT: 1, MITIGATABLE, by fail-stop and process isolation -- AND SECTION 5's FAIL-CLOSED INTERFACE IS STILL VIOLATED. Both halves belong in the sentence.** The mitigation is real and is what earns the rung: the abort is a fail-stop at a process boundary, and that boundary PREVENTS AN ACCEPTED RESULT OR ANY OTHER SUCCESS PUBLICATION -- the run dies before its summary line, so no consumer receives a partial or fabricated answer. Section 4b names isolation among rung-1 mechanisms, so the absence of a typed result does not deny rung 1. **BUT RUNG 1 DOES NOT REHABILITATE THE SIGSEGV**: section 5 independently requires every path to succeed fully or fail with a TYPED, LOCATED diagnostic, and an abort carrying no subject, no span and no diagnostic identity fails that badly. The deficit is a rung-1 class with a standing section 5 interface violation, which is what makes it rank for repair. **AN EARLIER REVISION OF THIS ROW PLACED THE CLASS OUTSIDE THE LADDER, AND THAT WAS WRONG IN A WAY WORTH KEEPING VISIBLE** (corrected 2026-09-06, native review 5124409497). Section 4b's only exclusions are silent wrongness below the floor and the adjacent column of external reality, undecidable properties and undeclared intent; this class is none of those, and the row REFUTED ITS OWN EXCLUSION two sentences later by stating that the depth property is decidable, that nothing waits on external reality, and that both a declared bound and an iterative implementation are authorable. **EXCUSING A CLASS FROM HAVING A RUNG HAS THE SAME PRACTICAL EFFECT AS INFLATING ONE -- IT NEVER RANKS**, which is the failure this ledger exists to prevent, arriving from the low side rather than the high one. **CEILING: 3, STRUCTURALLY GUARANTEED -- NOT 4, BECAUSE DECIDABLE DOES NOT IMPLY RUNG 4.** Depth is decidable and nothing waits on external reality, but decidability establishes only that a structural decision procedure is POSSIBLE. If an unbounded recursive carrier remains WRITABLE while the compiler derives a proof or a refusal for it, that is rung 3. **RUNG 4 REQUIRES THAT THE INVALID STATE HAVE NO CONSTRUCTOR IN THE CANONICAL MODEL, AND THIS ROW CANNOT NAME THE CONSTRUCTOR-ELIMINATION PATH**: no canonical carrier model is in hand in which data-dependent native-stack recursion is inexpressible, and a recursive carrier is a thing the substrate is meant to be able to declare. The ceiling is therefore raised to 4 only by a future row that NAMES that model; until then, claiming 4 here would be the inflation this ledger refuses. **THE REPAIR MAY NOT BE A BIGGER STACK, A FATTER THREAD, OR A RAISED BUDGET, AND THE LEAK IS AN INSTRUMENT RATHER THAN AN ARM.** All four leave the abort exactly where it was and move the corpus at which it fires, which is section 5's absorbing fallback in its purest form: the failure arm WIDENS instead of refusing, the widening is denominated in the corpus rather than the change, and the deficit stops ranking for repair because the symptom moved. The two honest arms are a release and traversal whose depth does not grow with the data -- an explicit iterative teardown and an explicit worklist walk -- or a DECLARED depth bound enforced at construction, refusing a too-deep structure with a typed, located diagnostic naming the carrier and the level. **THE TRIGGER BELOW IS A DELIBERATE 1-TO-3 JUMP, STATED SO IT IS NOT MISREAD AS THE ADJACENT RUNG.** Deriving the risk from the declared carrier and failing compilation when an unbounded carrier lacks an iterative release or a declared bound is a STRUCTURAL GUARANTEE, rung 3 -- it skips rung 2 rather than passing through it. The rung-2 form, if the jump proves too large to take in one step, is a generated lens or an adversarial-depth witness that reliably exposes and blocks the class WHILE THE INVALID IMPLEMENTATION STAYS WRITABLE and safety depends on that mechanism executing and staying enrolled. **NEXT-RUNG TRIGGER, and it names a CAPABILITY rather than a repair to one carrier:** a mechanism that derives, from a declared carrier, whether any operation on it -- including generated ones -- can recurse without a bound, so that authoring an unbounded-depth carrier without an iterative release or a declared bound fails to compile. Repairing `Node` alone does not retire this row; it removes one instance, and `node_type_shape` would still stand, as would every carrier authored tomorrow. **BOUNDED AGAINST TWO NEIGHBOURS.** Section 5's absorbing fallback is a mechanism that WIDENS when it cannot answer precisely; here nothing widens and nothing is answered -- the process dies. And `right_censored_cost_read_as_exact` concerns a measurement truncated by a limit; the resemblance is superficial, because there the run completes and reports a bounded number, whereas here the limit destroys the run and its report together, which is precisely why the threshold instrument had to be built to get any number at all. **WHY THIS RECEIPT NAMES NO PRODUCER, STATED RATHER THAN LEFT AS AN OMISSION** (raised as a section 6 objection, review 61308). Section 6 requires a measurement to be cited by naming the producer that re-derives it, because A TRANSCRIBED NUMBER IS UNREACHABLE FROM THE THING THAT OWNS IT AND ROTS WITHOUT ANYONE TOUCHING EITHER END. **THE OWNER OF THESE FIGURES NO LONGER EXISTS.** Every number here was observed on a configuration requiring a resolver rule that is not on main and, by ruling, never lands in that shape; the specimen is extinct, so NO ENTRY POINT CAN RE-DERIVE IT AND NONE IS OWED -- which is the case section 6 names in its own final clause, a one-off rather than an instrument. What is recorded is therefore a HISTORICAL OBSERVATION OF AN EXTINCT CONDITION, not a standing measurement, and it cannot rot in section 6's sense because it asserts nothing about the current tree. Without this sentence a reader concludes the producer was omitted rather than impossible, which is why the absence is declared instead of left to be noticed. **THE POPULATION ON MAIN IS UNMEASURED AND THE ROW DOES NOT CLAIM OTHERWISE, WHICH IS A DENOMINATOR FACT AND NOT BOOKKEEPING** (established 2026-09-06, after this row was first written, by the three-cell probe that identified what builds the chain). THE SOLE SPECIMEN WAS PRODUCED BY A RESOLVER RULE THAT IS NOT PRESENT ON MAIN: admitting the one refused callable owner collapses the specimen from 1,800,008 to 85, with the deepest site moving to an unrelated module, and the same instrument reads 85 under main and under the merge base. **THE TWO LEVELS ARE CONDITIONAL OBSERVATIONS AND MUST NOT BE LIFTED OUT AS PROPERTIES OF "MAIN" AND "THE CUT".** They were measured under one fixed execution condition -- that corpus, that traversal order, that pool construction, that binary and instrumentation -- and the pool construction in force is itself order-dependent, so the absolute levels are traversal-order-contaminated. WHAT GENERALISES IS THE DISCRIMINATING EFFECT OF CHANGING THE PREDICATE, not the numbers: a defective traversal regime can move the absolute state presented to every cell alike, but it cannot manufacture a DIFFERENCE between cells when that regime was held fixed. So the collapse stands as causal evidence with its held condition attached, and 85 is not a fact about main. **WHETHER ORDINARY CORPUS GROWTH ALONE CAN REACH COMPARABLE DEPTH IS UNMEASURED, AND NOBODY HAS SHOWN THAT IT CAN.** THE PRODUCING DEFECT IS NAMED SO THE TWO ROWS CONNECT RATHER THAN LEAVING THE NEXT READER TO RE-DERIVE IT: the depth was reached because an UNRESOLVABLE CALL WAS ELABORATED RATHER THAN REFUSED, which is filed as the call-site form of `gunbc.recurring_failure_mode.state_space_conflation` -- the callable-lookup carrier collapses builtin, locally-bound and genuinely-missing into one constructor, and the consumer reads that collapsed absence as affirmative formal authority. **STATED IN BOTH DIRECTIONS, BECAUSE EACH HALF GUARDS AGAINST THE OPPOSITE MISREAD.** A reader must not conclude that corpus growth alone reaches this depth -- that is the unmeasured half above. A reader must ALSO not conclude that the structure was a harness artefact: NOTHING WAS FORCED IN THE RUN THAT PRODUCED IT. The 1,800,008 baseline is an ordinary compile under an ordinary resolver rule; the forced admissions were the two PROBES placed either side of that baseline to identify its cause. The structure was real, an accepted compile built it, and the class it exhibits is independent of whichever defect supplied the depth -- which is why this row stands on its own and does not wait on that defect's repair. **THE SECOND DEFECT THIS SPECIMEN EXPOSES IS NOT THIS ROW AND MUST NOT BE ABSORBED INTO IT:** nothing in two source files is 10^5 levels deep, so some pipeline stage BUILDS a structure far deeper than its input -- a chain where a flat structure was intended. That is a separate defect, for which this class is only the reporter, and it will be pathological for every consumer that walks the structure rather than only the one that releases it.) - **DESTRUCTIVE CLEANUP CORRECTNESS DEPENDING ON AN UNDECLARED EVALUATION-MODEL PROPERTY. THREE TRANSPORTS ARE CORRECT BY ACCIDENT OF REALIZATION, NOT BY CONSTRUCTION. INVALID STATE:** the interpreter implements `let` as eager/strict (eval_let evaluates the value expression unconditionally at the point of binding). Nothing declares this as a language guarantee — it is a property of the current interpreter realization, not a declared semantic axis. The companion divergence — that interpreter `&&` evaluates both operands while emitted Rust `&&` short-circuits — is already filed as realization_arms_diverge_on_whether_the_program_refuses. It does not affect these transports: their cleanups are let-bound, not &&-bound, and under eager let a short-circuit && cannot change any conjunction's value because it skips only the right operand when the left is false — a path where the answer is already determined and the skipped operand is a pure field read (.success) on a record whose effect already fired at its let binding. STRUCTURALLY, no transport in this class depends on && eagerness, and no addition to the corpus can create such a dependence as long as let stays eager. If `let` became lazy, cleanup would never run in the namespace_import_closure_behavioral_transport (where cleanup is bound but never consumed), and would be unobserved in the other two (where cleanup .success would not be consumed, though the eager `let` at binding point would still fire). The distinction matters: 'effect does not reach the filesystem' is a leak; 'effect reaches the filesystem but its success is unobserved' is an unobserved cleanup failure — different harms, different severities.POPULATION, BOUNDED AND ENUMERABLE — THE THREE TRANSPORTS: `tools.self_host_logic_behavioral_transport::slb_comparison_observation` (let-bound cleanup_src/cleanup_out consumed in the staged conjunction), `tools.emit_host_transport::run_rust_smoke` / `::run_python_smoke` / `::run_go_smoke` (let-bound cleanup consumed in a conjunction), `tools.namespace_import_closure_behavioral_transport::nic_emitted_crate_evidence` (let-bound cleanup_src/cleanup_out NEVER consumed — correct only because let is eager).CEILING: cleanup that does not depend on the evaluation model at all — an effect the transport sequences explicitly rather than one it gets from strictness. Every transport's destructive cleanup would be structurally sequenced (e.g. a `do cleanup after body` pattern) rather than relying on let-eagerness for its effect to reach the filesystem.NEXT-RUNG TRIGGER, A CAPABILITY — either (a) the evaluation model declared as a typed guarantee that a check can execute against — a property of the interpreter that is named, bounded, and verifiable, not prose — or (b) every transport whose correctness turns on eager evaluation sequences its destructive effects explicitly rather than inheriting them from let-strictness. Both capabilities are genuine transitions; neither is satisfied by documentation or by this row.CORPUS POPULATION MEASUREMENT — the && divergence has no live hazard in this corpus: zero sites place a non-total or effectful operation directly as the right operand of `&&` (the two found are read-only). Twenty sites let-bind destructive effects and then consume their .success in a `&&` chain; under the structural argument above, none depends on && eagerness. HOWEVER, A=0 is a SURVIVOR PROPERTY OF THE STRICTER ARM HAVING FILTERED THE CORPUS, not evidence the class is harmless. The interpreter evaluates both operands unconditionally, so any author who wrote `guard && dangerous` would have hit the refusal while authoring and rewritten before commit. Under a short-circuit `&&`, authors would write these patterns freely and they would compile — and the emitted arm would silently produce a wrong answer where the interpreter refuses. That is the existing row's subject.INSTRUMENT — DISCRIMINATING PROBES, THEIR NAMES AND INVOCATION, SO A FUTURE READER CAN RE-DERIVE THIS FINDING AND RE-RUN IT. Five executed probes in module `test.claim.eval_model_probe` (dag/test/claim/eval_model_probe_test.dag): `let_is_eager_when_unused` (let-binds a Remove without consuming its result; PASS means eager), `let_is_eager_when_consumed` (positive control), `and_demands_right_even_when_left_false` (let-binds false and Remove separately, && computes; PASS means Remove ran despite false), `and_short_circuit_direct_probe` (false && Remove directly; PASS means Remove ran), `and_with_true_left_removes_file` (positive control). Each was run as: `gunbc run --source-root dag --source-root src/v2 --entry dag/test/claim/eval_model_probe_test.dag --function --claim-run`. All five returned PASS (exit 0). Probes observed the filesystem side effect out-of-band (shell.Test.IsFile) rather than consuming a returned value, so no measurement-created dependency could confound the result. -- **generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. The refusal exits 2 with EMPTY STDOUT and interpolates the rendered value into the diagnostic on stderr: classify_exit's non-variant arm is type_name: ctx.format_value(other). Executed: wise-badger-902, EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown heading; quick-heron-85, proj3.err 1062137 bytes, same delimiters. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` forbids print-AND-SUCCEED, and holds. The content is therefore obtainable ONLY by scraping a half-megabyte stderr diagnostic between delimiters that are message prose (returned-open-quote and not-ProcessExit-close-quote), unversioned and enrolled by nothing. Extractable only by scraping an unversioned diagnostic — which is why the ProcessExit regen is the repair. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. INCIDENT, not a fixture and not a perturbation ordered for this PR, produced by quick-heron-85 without looking for this class, commit b1c6378c6d: their authority row unresolved_callee_conflated_with_dispatched_elsewhere was UNPARSEABLE for a full commit. An authored receipt contained a quoted phrase using bare double quotes inside a double-quoted string; the string terminated early and the parser refused undefined variable this and undefined variable leaf. THE AUTHORITY DID NOT COMPILE, so docs/design-failure-modes.md kept rendering the previous vintage while presenting as current. They found it only by running the projection; reading the row does not surface the break. This PR's gate would go red on that commit because an unparseable authority makes the projector refuse — from the committed markdown alone, STALE bytes and an UNBUILDABLE authority are indistinguishable. THIS ROW IS THE DETECTOR GAP. The quoting injury that silently disabled the row they were filing is a neighbouring class they are filing separately; its next-rung trigger is string escaping in the .dag lexer. This gate makes that injury loud; it does not make it unwritable. Do not read this row as closing that class. THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. RUNG FOUND AT: outside the ladder until a completing content agreement exists — silent wrongness, not a weak wall. After a ProcessExit projector and a required content compare execute, mechanically preventable: the stale file remains writable. CEILING: structurally guaranteed when membership of every committed generated `docs/` path in a content-agreement adjudicator is derived from `gunbc.generated_artifact` `committed_generated_artifacts`, so omitting the arm is a compile refusal. NEXT-RUNG TRIGGER, a capability: required-run dispatch consumes a content agreement — byte identity of generated vs committed — over every committed generated artifact whose location directory is `docs`, derived from the registry so adding a committed `docs/` path without that agreement will not compile. A trigger naming one command, one test file, or `docs_projection_gate.dag` is less than the capability and would fire while edit-only drift can still sit uncompared.) -- **a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. SPECIMEN, 2026-09-06, executed both ways. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs NotProcessExit with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the interpolation site and ruled the same. quick-heron-85 held proj3.err at 1062137 bytes. The manager then RAN the projector: EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown. The field name said type; the field held format_value. WHY THE FORK SURVIVED, and it is a section 4b evidence gap not a naming smell. `non_process_exit_refuses_rather_than_printing_and_succeeding` hand-constructs ExitClass::NotProcessExit with type_name the literal Bool and starts from an already-classified value. It never calls `classify_exit`. Nothing executing joins the field's name to its contents on the non-variant path — the only enrolled evidence feeds the field a literal that matches its name, which is exactly the input that cannot discriminate. The control's RED is unauthorable for this defect, so it was permanently green over the case that mattered and got cited as coverage. A field carrying two facts is ordinary; a field carrying two facts with a control that only ever sees one of them is why three of us read adjacent code correctly and reached three answers. THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. RUNG FOUND AT: mitigatable — the wrong inference is a human reading; the runtime still refuses with exit 2 and does not print-and-succeed. CEILING: structurally impossible when `NotProcessExit` cannot be constructed with a rendered value under a field named for a type — two variants, or a field named for what it actually holds. NEXT-RUNG TRIGGER, a capability: `ExitClass` distinguishes a type-name refusal from a rendered-value refusal in the type, so interpolating one as the other will not compile.) From b892807596702647e7041b56bc9af6c8a842952e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 03:52:04 +0000 Subject: [PATCH 18/22] Cite the existing claim-surface stall as exit_ok's standing disposition. The Bool collapse is not an untracked residue: sequencers already match ProcessExit, and tools.emit_host_gate emit_host_verdict_narration_dissolution_trigger names when the claim path can carry typed reasons. Co-authored-by: Cursor --- dag/std/process.dag | 1 + 1 file changed, 1 insertion(+) diff --git a/dag/std/process.dag b/dag/std/process.dag index dec64ddd38c..342c682bebc 100644 --- a/dag/std/process.dag +++ b/dag/std/process.dag @@ -16,6 +16,7 @@ fn exit_failure(reason: String) -> ProcessExit { } // Claim-surface inhabitance of ExitSuccess. Sequencers match ProcessExit; they do not ask this for a Bool. +// Standing disposition: tools.emit_host_gate emit_host_verdict_narration_dissolution_trigger. fn exit_ok(e: ProcessExit) -> Bool { match e { ExitSuccess => true From 26abf96a55a57cc466709190acfabc93c2ef8499 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 03:53:43 +0000 Subject: [PATCH 19/22] Say exit_ok is not rostered; the Bool claim gap is analysed on the narration trigger. That trigger deletes the scaffold, not this predicate. Both retire when the claim path carries typed reasons. Co-authored-by: Cursor --- dag/std/process.dag | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dag/std/process.dag b/dag/std/process.dag index 342c682bebc..637b8a2378e 100644 --- a/dag/std/process.dag +++ b/dag/std/process.dag @@ -16,7 +16,9 @@ fn exit_failure(reason: String) -> ProcessExit { } // Claim-surface inhabitance of ExitSuccess. Sequencers match ProcessExit; they do not ask this for a Bool. -// Standing disposition: tools.emit_host_gate emit_host_verdict_narration_dissolution_trigger. +// This and tools.emit_host_gate emit_host_verdict_narration_scaffold are two consequences of one gap: +// the claim surface is Bool-typed. That gap's capability is stated at tools.emit_host_gate +// emit_host_verdict_narration_dissolution_trigger, and retires both; this predicate is not separately rostered. fn exit_ok(e: ProcessExit) -> Bool { match e { ExitSuccess => true From 658f75833dd765796660466507976500ddcc01df Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 04:16:45 +0000 Subject: [PATCH 20/22] Admit the seven exit_ok TargetChanged bindings and drop consumed #10676 rows. The floor_effect_gate_witness _passes functions now bind exit_ok to std.process; the nine scm builder-rehome admissions were consumed on main and came due on this roster touch. Co-authored-by: Cursor --- src/v1/stage0/src/namespace_wave_admission.rs | 150 +++++++----------- 1 file changed, 61 insertions(+), 89 deletions(-) diff --git a/src/v1/stage0/src/namespace_wave_admission.rs b/src/v1/stage0/src/namespace_wave_admission.rs index 044caef5d10..f231967eca5 100644 --- a/src/v1/stage0/src/namespace_wave_admission.rs +++ b/src/v1/stage0/src/namespace_wave_admission.rs @@ -1561,36 +1561,6 @@ pub struct TransitionAdmission { /// and all four report CONSUMED, coming due on this roster's next touch. Adjudicate that deletion /// by joining each row against main's tree on its own tuple, not by trusting this sentence. -/// THE SCM REPOSITORY BUILDER MOVES TO A SHARED FIXTURE (2026-09-06, gunbc#10676). No ordinal is -/// claimed, for the reason the entries above give. -/// -/// `test.claim.scm_merge_base_witness` authored a repository builder -- `MbBuild` with its two arms -/// `MbBuilt` and `MbSetupFailed`, and the six operations `mb_start`, `mb_stage`, `mb_commit`, -/// `mb_at`, `mb_head`, `mb_root_of` -- to construct scenes through the real store and mint rather -/// than by forging rows. The squash-merge verb's witness needs that construction VERBATIM. Copying -/// it would put one construction rule in two files, and the drift would be INVISIBLE: each copy -/// would keep passing its own claims while the two fixtures quietly stopped describing the same -/// repository (DESIGN.md §2). So the builder is rehomed to `test.fixture.scm_repository_builder`, -/// which is where this repository already puts fixtures shared across claims, and both witnesses -/// import it. -/// -/// NINE BINDINGS IN ONE MODULE resolve to the new declarer, which is `TargetChanged` and is not -/// auto-admitted: seven inside `mb_scene` and two inside -/// `scm_mb_the_scene_holds_the_root_relations_the_controls_depend_on`. Every spelling is identical -/// on both sides; only the declaring module differs, which is the membership motion this roster -/// exists to adjudicate. -/// -/// ONE CHANGE CLASS, AND NOTHING IS REQUALIFIED. No spelling changes, no behaviour changes, and the -/// evidence that the move is behaviour-preserving is executed rather than asserted: all twelve -/// `scm_merge_base_witness` claims pass unchanged against the shared fixture. That is the positive -/// control for a rehome -- a builder that had silently changed would show up as a claim that -/// stopped discriminating, not as a compile error. -/// -/// TRIGGER, AND IT IS THESE ROWS' OWN DEATH: they go when gunbc#10676 merges. Main then declares -/// the builder in `test.fixture.scm_repository_builder`, base and head bind each spelling -/// identically, and all nine report CONSUMED, coming due on this roster's next touch. Adjudicate -/// that deletion by joining each row against main's tree on its own -/// (module, in_declaration, spelling, target) tuple, not by trusting this sentence. /// THE gunbc#10671 ROWS DISSOLVED HERE (2026-09-06), BY THEIR OWN TRIGGER AND ON THE ROSTER TOUCH /// THEY NAMED. gunbc#10671 merged, so the four cable-leg rows reported CONSUMED and came due on the /// next roster-touching change, which is this one. @@ -1604,99 +1574,101 @@ pub struct TransitionAdmission { /// `test.claim.cable_order_admission_witness`, import the spelling from the new declarer. So base and /// head bind it identically, no run can produce those four deltas, and CONSUMED is the correct /// reading rather than an author error. -const SCM_REPOSITORY_BUILDER_REHOME_LABEL: &str = - "gunbc#10676 scm fixture extraction: the repository builder moves from \ - test.claim.scm_merge_base_witness to test.fixture.scm_repository_builder, so the merge_base \ - and squash_merge witnesses read one construction rule instead of two copies"; +/// +/// THE gunbc#10676 ROWS DISSOLVE HERE (2026-09-07), BY THEIR OWN TRIGGER AND ON THIS ROSTER TOUCH. +/// gunbc#10676 merged; the nine builder-rehome rows reported CONSUMED. Joined on origin/main against +/// each row's (module, in_declaration, spelling, target), not the trigger sentence: +/// +/// `test.fixture.scm_repository_builder` DECLARES `MbBuild` with arms `MbBuilt` and +/// `MbSetupFailed`, and `fn mb_start`, `mb_stage`, `mb_commit`, `mb_at`, `mb_head`, `mb_root_of`. +/// `test.claim.scm_merge_base_witness` DECLARES none of those nine spellings -- they appear only +/// as an import from the fixture (and as uses inside `mb_scene` and +/// `scm_mb_the_scene_holds_the_root_relations_the_controls_depend_on`). Both consumers -- +/// `test.claim.scm_merge_base_witness` and `test.claim.scm_squash_merge_witness` -- import every +/// one of those spellings from the new declarer. So base and head bind identically; CONSUMED is +/// the correct reading for all nine. +/// +/// EXIT_OK RELOCATES TO std.process. Seven `TargetChanged` bindings in +/// `tools.floor_effect_gate_witness` (`_passes` for emit_host_gate, cheap_claim_pool_gate, +/// extdeps_external_authority_gate, dag_compile_clean_gate, generated_artifact_drift_gate, +/// extdeps_scope_placement_gate, prose_row_introduction_gate) resolve `exit_ok` at base to +/// `tools.ci_gates` and at head to `std.process`. Spelling unchanged; predicate exhaustive with no +/// wildcard arm; behaviour unchanged; the witnesses that consume it are unchanged. TRIGGER: they +/// go when this relocation is on main, then report CONSUMED and come due on the roster's next touch. +/// Adjudicate that deletion by joining each row against main on its own tuple, not this sentence. +const EXIT_OK_REHOME_LABEL: &str = + "exit_ok relocates from tools.ci_gates to std.process: spelling unchanged, behaviour \ + unchanged, the predicate is exhaustive with no wildcard arm, and the floor witnesses \ + that consume it are unchanged"; pub const NAMESPACE_TRANSITION_ADMISSIONS: &[TransitionAdmission] = &[ TransitionAdmission { - label: SCM_REPOSITORY_BUILDER_REHOME_LABEL, - subject: AdmissionSubject::Binding { - module: "test.claim.scm_merge_base_witness", - in_declaration: "mb_scene", - spelling: "MbBuilt", - target: "test.fixture.scm_repository_builder", - }, - disposition: NamespaceDeltaDisposition::TargetChanged, - }, - TransitionAdmission { - label: SCM_REPOSITORY_BUILDER_REHOME_LABEL, - subject: AdmissionSubject::Binding { - module: "test.claim.scm_merge_base_witness", - in_declaration: "mb_scene", - spelling: "MbSetupFailed", - target: "test.fixture.scm_repository_builder", - }, - disposition: NamespaceDeltaDisposition::TargetChanged, - }, - TransitionAdmission { - label: SCM_REPOSITORY_BUILDER_REHOME_LABEL, + label: EXIT_OK_REHOME_LABEL, subject: AdmissionSubject::Binding { - module: "test.claim.scm_merge_base_witness", - in_declaration: "mb_scene", - spelling: "mb_start", - target: "test.fixture.scm_repository_builder", + module: "tools.floor_effect_gate_witness", + in_declaration: "emit_host_gate_passes", + spelling: "exit_ok", + target: "std.process", }, disposition: NamespaceDeltaDisposition::TargetChanged, }, TransitionAdmission { - label: SCM_REPOSITORY_BUILDER_REHOME_LABEL, + label: EXIT_OK_REHOME_LABEL, subject: AdmissionSubject::Binding { - module: "test.claim.scm_merge_base_witness", - in_declaration: "mb_scene", - spelling: "mb_stage", - target: "test.fixture.scm_repository_builder", + module: "tools.floor_effect_gate_witness", + in_declaration: "cheap_claim_pool_gate_passes", + spelling: "exit_ok", + target: "std.process", }, disposition: NamespaceDeltaDisposition::TargetChanged, }, TransitionAdmission { - label: SCM_REPOSITORY_BUILDER_REHOME_LABEL, + label: EXIT_OK_REHOME_LABEL, subject: AdmissionSubject::Binding { - module: "test.claim.scm_merge_base_witness", - in_declaration: "mb_scene", - spelling: "mb_commit", - target: "test.fixture.scm_repository_builder", + module: "tools.floor_effect_gate_witness", + in_declaration: "extdeps_external_authority_gate_passes", + spelling: "exit_ok", + target: "std.process", }, disposition: NamespaceDeltaDisposition::TargetChanged, }, TransitionAdmission { - label: SCM_REPOSITORY_BUILDER_REHOME_LABEL, + label: EXIT_OK_REHOME_LABEL, subject: AdmissionSubject::Binding { - module: "test.claim.scm_merge_base_witness", - in_declaration: "mb_scene", - spelling: "mb_at", - target: "test.fixture.scm_repository_builder", + module: "tools.floor_effect_gate_witness", + in_declaration: "dag_compile_clean_gate_passes", + spelling: "exit_ok", + target: "std.process", }, disposition: NamespaceDeltaDisposition::TargetChanged, }, TransitionAdmission { - label: SCM_REPOSITORY_BUILDER_REHOME_LABEL, + label: EXIT_OK_REHOME_LABEL, subject: AdmissionSubject::Binding { - module: "test.claim.scm_merge_base_witness", - in_declaration: "mb_scene", - spelling: "mb_head", - target: "test.fixture.scm_repository_builder", + module: "tools.floor_effect_gate_witness", + in_declaration: "generated_artifact_drift_gate_passes", + spelling: "exit_ok", + target: "std.process", }, disposition: NamespaceDeltaDisposition::TargetChanged, }, TransitionAdmission { - label: SCM_REPOSITORY_BUILDER_REHOME_LABEL, + label: EXIT_OK_REHOME_LABEL, subject: AdmissionSubject::Binding { - module: "test.claim.scm_merge_base_witness", - in_declaration: "scm_mb_the_scene_holds_the_root_relations_the_controls_depend_on", - spelling: "MbBuilt", - target: "test.fixture.scm_repository_builder", + module: "tools.floor_effect_gate_witness", + in_declaration: "extdeps_scope_placement_gate_passes", + spelling: "exit_ok", + target: "std.process", }, disposition: NamespaceDeltaDisposition::TargetChanged, }, TransitionAdmission { - label: SCM_REPOSITORY_BUILDER_REHOME_LABEL, + label: EXIT_OK_REHOME_LABEL, subject: AdmissionSubject::Binding { - module: "test.claim.scm_merge_base_witness", - in_declaration: "scm_mb_the_scene_holds_the_root_relations_the_controls_depend_on", - spelling: "mb_root_of", - target: "test.fixture.scm_repository_builder", + module: "tools.floor_effect_gate_witness", + in_declaration: "prose_row_introduction_gate_passes", + spelling: "exit_ok", + target: "std.process", }, disposition: NamespaceDeltaDisposition::TargetChanged, }, From d2ae6ee55e1e6dc813504a9ed7e7928203fac28c Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 05:26:55 +0000 Subject: [PATCH 21/22] chore: regenerate drifted generated artifacts (ci auto-heal) Ledger-Repair-Judged: docs/design-failure-modes.md Ledger-Rows-Repaired: docs/design-failure-modes.md excluded_witness_outlives_the_subject_it_names Ledger-Rows-Repaired: docs/design-failure-modes.md docs_markdown_projections_have_no_completing_actuator Ledger-Rows-Repaired: docs/design-failure-modes.md type_name_field_carries_rendered_value Ledger-Repair-Judged: docs/design-rung-drops.md --- docs/design-failure-modes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/design-failure-modes.md b/docs/design-failure-modes.md index 3f6e5aa0fd8..d1153b59c01 100644 --- a/docs/design-failure-modes.md +++ b/docs/design-failure-modes.md @@ -128,6 +128,7 @@ The exact `RecurringFailureMode.identity` population, which no other projection - `fail_closed_gate_refuses_its_own_repair` - `a_live_authority_name_carries_a_superseded_claim` - `witness_that_fails_to_compile_is_absent_rather_than_red` +- `excluded_witness_outlives_the_subject_it_names` - `instruction_and_subject_resolved_from_different_revisions` - `reported_required_refusal_does_not_precondition_landing` - `corroboration_without_an_independent_derivation_axis` @@ -165,6 +166,8 @@ The exact `RecurringFailureMode.identity` population, which no other projection - `permissiveness_promoted_into_a_higher_safety_rung` - `native_stack_recursion_unbounded_over_corpus_shaped_structure` - `evaluation_model_cleanup_undeclared` +- `docs_markdown_projections_have_no_completing_actuator` +- `type_name_field_carries_rendered_value` --- @@ -308,6 +311,7 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a fail-closed gate predicates evaluation on base-revision health and therefore refuses the change that restores that health.** INVALID STATE: the gate correctly declines to compare an unreadable base, but has no representable arm for `the base defect is exactly the subject this change removes`. Its safety property becomes self-sealing: every ordinary change is rightly refused while the repair is refused for the same reason, so no gate-admitted transition can restore the precondition the gate requires. MEASURED SPECIMEN (2026-09-04): gunbc#10390 left an unattached `//` block at the tail of `dag/test/claim/emit_copy_qualification_witness_test.dag`; the base revision then failed parsing with 16 diagnostics. On gunbc#10428, which moved that block onto a declaration and made the file parse again, the namespace-wave-admission phase reported `NotEvaluated -- dag/test/claim/emit_copy_qualification_witness_test.dag does not parse at the base revision (16 diagnostic(s)), so its base-side declarations cannot be read`. The refusal was correct, and #10428 could repair main only because it landed despite that refusal. Its next run was green. DISTINGUISHING FACTS: nothing widened, defaulted, or fabricated, so this is NOT `absorbing_fallback`; the gate preserved ignorance as NotEvaluated exactly as fail-closed requires. The defect is transition reachability, not verdict truth. Recognition rule: for every gate that requires a healthy base, construct a base violating that precondition and a head differing only by removal of the violation; if the same refusal covers both that repair and an unrelated change, the gate has no repair discriminator and its invariant is self-sealing. HARM: a below-floor parse defect on main made its own direct repair unmergeable by the required admission mechanism, and every other pull request inherited the refusal until an operator used a route outside that mechanism. A gate intended to bound change thereby extended the outage and required an unmodeled exception at the moment its guarantee mattered most. RUNG FOUND AT: mitigatable by operator inspection and exceptional landing, with the invalid transition still writable and no mechanism distinguishing it. CEILING: mechanically preventable, not structural today: admitting a purported repair from the head alone is the obvious fail-open trap, because an ordinary change can conceal, relocate, or replace the unreadable base subject while claiming repair. A safe arm must establish both a bounded defect identity on the base and monotone removal of only that defect without pretending to compare base semantics the parser could not produce. NEXT-RUNG TRIGGER: a revision-scoped recovery-parse and change-classification capability sufficient to locate a bounded base defect in raw source, prove that the head removes that same defect, and prove that no outside-scope semantic change rides with it; the gate may then admit only that typed Repair transition while continuing to refuse every unclassified NotEvaluated change. - a live authority name carries a superseded claim. A rename or authority replacement sweeps prose for the NAME, every occurrence is updated, every updated occurrence resolves -- and the PROPOSITION each name was embedded in is never re-read. The citation is now current and the sentence around it is false. **THIS IS NOT A STALE CITATION AND THE DIFFERENCE IS THE WHOLE CLASS.** A stale name is visibly stale: it fails to resolve, or it names something a reader can see is gone. A REFRESHED name is worse, because the refresh is itself an attestation -- it tells the reader that someone recently looked at this sentence and found it correct. The sweep supplies that signal without having supplied the reading. Neighbouring classes differ at the referent, not here: `positional_citation` and `stable_citation_mutable_referent` are about the pointer decaying underneath a claim that stayed true, and this is the exact inverse -- the pointer is repaired and the claim is what died. **RECEIPT (eager-pike-541, 2026-09-04, gunbc#10358).** The PR renamed `gunbc.model.choice` to `gunbc.model.ollama_choice` and updated the citation in `docs/plans/spark-fleet-hand-edit-gap-analysis.md` section 7 accordingly. The updated paragraph continued to state that serving configuration identity derives from the runner's argv -- a mechanism `ObservedOllamaLaunchConfiguration` had ALREADY REPLACED before the rename was proposed. The rename was correct, the new name was correct, and the sentence carrying it had been false since a prior change. Found by external review as REQUEST_CHANGES (`review 59866`), not by any mechanism in the tree. **WHY NO EXISTING WALL SEES IT, stated as a boundary rather than a gap to be closed by the neighbour.** gunbc's namespace-wave admission (`gunbc.namespace_wave_admission`) enumerates AUTHORED NAME OCCURRENCES resolved per module and adjudicates every changed binding -- on this same PR it correctly demanded 255 `TargetChanged` rows. It does not inspect prose, and it is not supposed to: DESIGN section 4c makes annotations invisible to every semantic pass, and a `.md` plan is further out still. So a fully green namespace wave is positive evidence about the executable binding surface and NO evidence about whether the prose describing that authority remains true. The two are complementary walls, not a strong and a weak version of one wall. **RECEIPT (warm-ant-80, 2026-09-05, source 06a30d90376d5fb1fef91d6963604e3b053b9df1).** The annotation on `gunbc.instruments.opaque_realization_census.read_emitted_tree` says `shell.Find.FilesByNameSorted` reports sort's success even when find fails, and says a partial walk cannot be detected until that transport changes. The named operation already captures find's exit status, sorts only after find succeeds, and returns the captured failure; its adjacent `find_pipe_shell_emit_dissolution_trigger` describes that repair. The pointer still resolves, but the proposition it accompanies has been superseded. While investigating an artifact-digest difference, a manager relied on the annotation and directed this lane to file the repaired transport defect as newly live. Reading the operation stopped that false filing; no new transport defect was established. The source comparison and the withdrawn instruction are the evidence here, not an execution of a broken listing. **SECOND RECEIPT (warm-ant-80, 2026-09-05, same source revision).** The header of `tools.emission_entry_instrument` says a scoped compile fits on the runner that killed a whole-corpus compile, citing `gunbc.whole_corpus_compile_admission`. That authority's scope note explicitly withdraws the fit claim, citing clever-tern-899's 2026-08-28 scoped control produced by `gunbc compile` with entry `src/v2/std/node.dag`: the observed demand exceeded that runner's reported budget. A manager relied on the surviving header to redirect this lane to a route as though its fit were established, then withdrew the instruction after reading the cited authority. The grain claim survives: one entry's closure differs from the whole-corpus population. The runner-specific fit claim does not. The instrument restated a proposition rather than deriving it, so the authority's withdrawal did not reach its restatement. **THE SAME FAILURE WITHOUT A NAME-REFRESH HUNK.** A repair or a withdrawn measurement can invalidate a sibling's prose while leaving every cited name unchanged. The repair's own dissolution trigger does not enumerate or adjudicate those descriptions. Here the surviving citations lent authority to claims that caused wrong instructions. This extends the specimen boundary beyond rename sweeps: a resolving pointer is no attestation that its surrounding proposition was rechecked after the referenced behavior changed. **RUNG: MITIGATABLE.** External review caught this instance; nothing in the tree can. The ceiling is not structural impossibility: the truth of an arbitrary proposition about a mechanism is not decidable from the Node tree, so the honest ceiling is MECHANICALLY PREVENTABLE. **NEXT-RUNG TRIGGER: a claim census** -- an instrument that takes the prose citations a rename or authority replacement TOUCHED and surfaces, for adjudication, the proposition each one sits inside, refusing the change until each is dispositioned. That is a real instrument and it does not exist; naming it here is the trigger, not a claim of coverage. **RECOGNITION RULE: any diff that updates a name inside prose it did not otherwise read.** The tell is a citation-only hunk -- one identifier changed, the surrounding sentence untouched. The obligation is that a sweep which refreshes a name has adopted the claim: re-read the proposition, and either confirm it against the current mechanism or mark it superseded. Updating the name alone is not the cheap half of the job, it is the half that manufactures the false attestation. - **a witness that fails to COMPILE is ABSENT rather than red, and running its SUBJECT proves nothing about it** (a claim file is committed, reviewed and merged while it cannot compile, so none of its assertions ever execute and the guarantee it was written to carry does not exist; the class it polices reports as walled while nothing walls it, which is worse than an absent witness because the file is cited as coverage by its own author and by its reviewers). WHY IT IS A CLASS AND NOT A SLIP: every ordinary signal is compatible with the dead file and NONE OF THEM IS MALFUNCTIONING. It emits no advisories, because a file that does not compile contributes none. It fails no assertions, because none run. It is ABSENT from the floor disposition rather than failing in it, so a disposition read shows nothing to investigate. `cargo fmt` is green because the file is well-formed text. The push is green. And a reviewer can APPROVE on a wholly correct reading of the source, because READING DOES NOT COMPILE. THE CORE IS A TWO-CLAIM COLLAPSE: `the compiler behaves correctly` and `the witness that enrols that behaviour as executing evidence works` are DIFFERENT CLAIMS, and running the subject as a direct fixture establishes only the first. The mechanism of the misread is that holding the stronger claim's output makes the weaker one feel already answered — the author has the passing fixtures in hand, which is exactly why the file meant to carry them never gets checked. RECOGNITION RULE: ask what would have caught it, and IF THE ONLY ANSWER IS A RUN AIMED AT A DIFFERENT QUESTION, the class has no dedicated detector. The specimen surfaced from a whole-tree advisory census re-derivation chasing an unrelated hypothesis about a denominator, as blocking error number one, after the arms were green, the PR was open and an APPROVE was already recorded. THE ONE INSTRUMENT THAT SEES IT is a whole-tree compile INCLUDING the test roots; compiling the subject, or entry-scoping to the witness itself, does not. Rung found at 1, mitigatable, held only by an unrelated run happening to range over the file, which is not a mechanism. CEILING 3, structurally guaranteed: the population is decidable and closed — every file under the claim roots — so a required step that compiles them and refuses a non-compiling claim file makes the state unwritable in an `Accepted` tree. NEXT TRIGGER, capability and not artifact: the required floor must COMPILE every claim-root file and refuse a claim file that does not compile, rather than only executing the claim files that compile — AN EXECUTION ROSTER KEYED TO COMPILING FILES CANNOT SEE A FILE THAT FELL OUT OF IT, the same shape as a check whose population IS its own roster. Specimen gunbc#10337: `brand_nominal_identity_witness_test` named a host builtin in its import list; four direct fixtures had already proven the compiler refuses, and the witness enrolling them had never run. +- **an excluded witness outlives the subject it names, and reads as coverage of a subject it can no longer see** (INVALID STATE: a witness compiles, is committed, and is kept off every executing cadence by a declared exclusion or freeze; the SUBJECT it names is then moved or deleted by a correct edit elsewhere; and because nothing executes the witness, nothing observes that it now names nothing. The roster still lists it, the exclusion row still explains why it is offline, and both statements are true -- while the assertion itself has become false and unfalsifiable at the same moment. HARM: it is cited as coverage, which is the decoration case DESIGN section 4b names, with the aggravating property that its offline standing is DOCUMENTED and therefore looks deliberate rather than neglected. A reader auditing what guards a class finds the witness, finds the exclusion row with its dissolution trigger, and concludes the coverage exists and is merely deferred. What actually exists is a row naming a path the tree does not contain. Every ordinary signal is compatible with it and none is malfunctioning: it compiles, the floor does not report it because the floor never admits it, and the exclusion roster counts it as an excluded member rather than as a broken one. THE SPECIMEN (2026-09-06, complexity-lens lane, live on main at the time of filing): v2.test.claim.complexity.accumulator_copy_roster_gate roster_glob_discovery_zero_suspects_within_ratchet names src/v2/workflow/glob_discovery_law.dag. That file moved to src/v2/test/workflow/ in the #9637 directory reorganisation and the row was not repointed. The entry is frozen in gunbc.witness_deferral_freeze AND discovery-excluded by the gunbc.ci_layer_roots pattern claim/complexity/accumulator_copy_roster_gate, so nothing has executed it since. Before the typed-read repair the row would have ABORTED the process on the missing path; after it, the row would return FALSE. Neither has ever been observed, because the witness does not run. AND THE COST GATE MAKES THE FALSE ROW CHEAPER TO LEAVE THAN TO FIX, WHICH IS THE HALF WORTH RECORDING. The repair is a one-line repoint. But the required floor admits a CHANGED witness regardless of any exclusion -- correctly, since an exclusion a witness could opt into on the commit that touches it would be an escape hatch -- and this entry's witnesses cost several times the per-witness CPU line v2.workflow.required_floor required_floor_claim_cpu_safety_limit_ms declares. So editing the row to make it true ADMITS it, and it is then preempted and BLOCKS the lane; leaving it false costs nothing. The lane that found this repointed the row, measured the block, and reverted the repair -- which is the correct local decision and the reason the class persists. A cost gate that prices repair above neglect converts a fixable staleness into a standing one. WHY IT IS NOT THE ROWS BESIDE IT, checked at identity grain over gunbc.recurring_failure_mode.roster. It is not `witness_that_fails_to_compile_is_absent_rather_than_red`: there the file cannot compile, so it is ABSENT from every roster and the misread is that nobody notices its absence. Here the witness compiles, is PRESENT in the rosters, and its offline standing is declared -- the misread is that a documented exclusion is taken for deferred coverage rather than for no coverage. The repair differs accordingly: that class is closed by compiling every claim-root file, which this witness passes. It is not `stale_claim_survives_its_own_correct_edit`: that class is PROSE that was true when written and became false through a correct edit elsewhere. This is an EXECUTABLE assertion in the same situation, and the difference is not academic -- a prose claim has no mechanism that could ever have caught it, while this one has a mechanism that is deliberately switched off. The staleness is therefore not merely undetected, it is undetectABLE while the exclusion stands, and the exclusion is the thing presented as the reason it is safe. RUNG FOUND AT 1, mitigatable: the harm is bounded only by the fact that an excluded witness makes no false GREEN claim in any run -- it makes no claim at all -- so what is misled is a reader, not a gate. Calling it higher would be inflation; nothing counts these rows or checks their subjects. CEILING 4, structurally impossible, and the reason it is that high rather than a validator is that the bad state is a DANGLING REFERENCE and nothing else. A witness names its subject by a path string today, which is the positional naming DESIGN section 3 already refuses for citations: any edit above or around the file silently invalidates it. If a subject were named by a symbol the containment tree resolves, a moved file could not leave a witness naming nothing -- the reference would fail to resolve and the entry would refuse at compile, whether or not anything ever executes it. NEXT-RUNG TRIGGER, NAMING THE CAPABILITY: witness subjects are named by a resolvable reference rather than by a path literal, so that a subject that moves breaks the naming rather than the meaning. SUFFICIENT FOR the excluded population specifically -- an executing witness already fails loudly when its subject moves, so a trigger satisfied only for witnesses that run restores nothing here; the whole population at risk is the one nothing runs. A roster-completeness check that merely verified every named path EXISTS would reach rung 2 and is worth having as the interim step, but it must not be reported as the ceiling: it leaves the path literal writable and re-checks it from outside rather than making the dangling state unconstructible. RECOGNITION RULE: for any witness that does not execute, ask whether its SUBJECT still exists, and ask what it would return today. If the answer to the second question is unknown to everyone including its author, the row is not deferred coverage; it is an unfalsifiable assertion with a documented reason for being unfalsifiable.) - **a step's INSTRUCTION and the TREE it operates on are resolved from different revisions** (INVALID STATE: the command a step runs -- which entry point, which function, which path -- is taken from revision A, while the working tree that command is pointed at is checked out at revision B, and nothing in the step binds the two or reports which is which. HARM: the step's refusal is REAL, typed and located, and it names a subject that never carried the instruction. Section 5's located diagnostic is intact and points at the wrong tree, so the repair it invites -- edit the module the error names -- is correct-looking and cannot work, because that module is not what disagreed. THIS IS NOT A STALE-BASE PROBLEM AND CALLING IT ONE SENDS THE FIX TO THE WRONG PLACE. Age of the base is neither necessary nor sufficient: a branch whose instruction and tree both come from the OLD revision is perfectly consistent and refuses nothing, and a branch that integrates the new revision is consistent again. What produces the failure is the SPLIT, and a step can hold it open at any base age. The reason it presents as staleness is that the split is invisible while the two revisions happen to agree, which is most of the time. **SPECIMEN, gunbc#10479, run 33926616205 job 101196425514 (2026-09-04).** The heal-generated-artifacts job refused with NoSuchFunction { name: heal_repair_declaration } while evaluating dag/gunbc/instruments/generated_artifact_gate.dag. The function and the invocation of it landed together in ONE commit, 865fddf03d (#10373) -- .dag definition and workflow argv in the same diff -- so the pair is atomic everywhere and no asymmetric merge resolution is involved. The split is the job's own construction: for a pull_request event GitHub supplies the WORKFLOW FILE from the merge ref, and this job declares ref: github.head_ref because it PUSHES the regeneration back to the branch and therefore cannot run detached on refs/pull/N/merge. Its checkout line reads git checkout --progress --force -B refs/remotes/origin/, not a merge ref. Measured at three revisions: branch head a86d7ed225 had definition 0 and invocation 0, a consistent OLD pair; origin/main had 1 and 1, a consistent NEW pair; what EXECUTED was the invocation from the merge ref against the definition from the branch head. git merge-base --is-ancestor 865fddf03d a86d7ed225 is false. Every other required job runs the merge ref and stayed green, which is why exactly one job reddened and why integrating main cleared it with nothing resolved by hand. **AND THE FIRST ACCOUNT OF THIS SPECIMEN WAS WRONG IN THE INSTRUCTIVE DIRECTION, which is why the population statement is written twice here.** It was first filed as any branch based before 865fddf03d will red this way -- a rule keyed on the BASE. A reviewer then established that both halves landed atomically and concluded the opposite, that a consistent old pair cannot refuse and so the cause must be an asymmetric merge resolution that took one side. Both readings check one ref and infer the other. The measurement that decides it is the checkout line in the job log, and neither account had read it. The population is: the branch HEAD predates the commit that added the invocation, AND the entry point is reached by a step that checks out the head rather than the merge ref -- narrower than base age in one direction, and broader than a bad merge resolution in the other. **SECOND SPECIMEN, gunbc#10490, AND IT IS A MATCHED PAIR RATHER THAN A SECOND SIGHTING.** Reached independently by another lane on a case neither party to the first diagnosis chose, and diagnosed correctly there before this row existed -- which is the evidence the roster is for: the class is recognisable in the wild, not only in retrospect. Re-measured here rather than accepted as reported. RED ARM, head c30c049f68: the definition is absent (git merge-base --is-ancestor 865fddf03d c30c049f68 is false), and on run 33930364431 heal-generated-artifacts failed while required-witnesses-build, required-witnesses-floor and the aggregator all succeeded -- one job red, and it is the one job whose ref differs from the workflow file it is running. GREEN ARM, head 5888fb0106 after integrating main: the definition is present. The property flips with the remedy, which makes the pair discriminating rather than illustrative, and per section 4b(4) it stays enrolled as the arm that would catch a regression in the eventual fix. ONE HONEST GAP IN THAT PAIR: at the time of writing, #10490 green run 33932538855 had not concluded, so the executed green arm is gunbc#10479 at 245416762b, where heal passed in 18m56s after exactly this remedy. Naming #10490 as an executed positive control before its run reports would be the unbacked-execution-claim failure inside the row that files a class about misattributed evidence. **SCOPE, STATED BECAUSE THE CLASS IS WRITTEN WIDER THAN ITS EVIDENCE.** The general shape is a step whose checkout ref differs from the ref that supplied the workflow file, and it is written at that grain deliberately: the next instance will be whichever step next needs to write back to the branch. But BOTH executed specimens are heal-generated-artifacts, which is the only such step in the required workflow today, so the evidence covers one job and the generalisation is reasoning. A reader deciding whether their own red is this class should check their step's ref: declaration, not assume the shape has been observed anywhere else. **DISTINCT FROM ITS THREE NEAREST NEIGHBOURS, and the repair law is what separates them.** upstream_carrier_substituted_for_the_consumer_selected_subject is about deriving a CLAIM about a consumer from a carrier the consumer transforms; there the defect is in a measurement and the repair is to measure the composed subject. Here nothing is measured -- the EXECUTOR pairs the two revisions itself, and the repair is in the step. verification_bound_to_the_revision_it_started_on is one tree MOVING across an interval; here the two revisions are fixed for the whole run and differ by construction, so waiting or re-running changes nothing. receipt_names_a_property_not_the_tree_it_holds_of is an expired GREEN that produces no signal at all; this class produces a loud, correctly typed RED whose subject attribution is false, which is a different failure to catch and a different one to trust. **RUNG: 1 (mitigatable).** The line stops, the diagnostic is typed and located, and the harm is bounded to a misattributed red plus the investigation it misdirects -- there is no silent wrongness here. **CEILING: 3 (structurally guaranteed), and not 4, for a stated reason:** which ref supplies a workflow file is GitHub's decision and sits outside the modeled guarantee, so no constructor of ours can make the two-ref pairing unwritable. What IS reachable is that a step cannot emit an unattributed refusal: the invocation is resolved against the tree the step checked out, and an entry that tree does not define refuses by naming BOTH revisions and classifying the cause as an instruction/subject split rather than as a defect in the module it names. **NEXT-RUNG TRIGGER, a capability and not an artifact: a step whose entry point is resolved from the revision it checked out, with the refusal carrying both revision identities.** What does NOT satisfy it: making this one job check out the merge ref, which it cannot do and still push; renaming or restoring the function; or adding a note telling readers to merge main, which is guidance rather than a mechanism. **RECOGNITION RULE: when a job refuses naming an entry point, function or path that is absent from the branch, ask which revision supplied the COMMAND and which supplied the TREE, and demand both as immutable shas.** The tell is that the refusal's own subject has no history on the branch at all -- git log -S on the branch is empty -- while git show : contains the invocation. Two refs, one of them never named in the error. - **a required check reaches a terminal refusal on the head and the same head is then landed by hand, so the check detects the invalid state without being a precondition of landing.** INVALID STATE: a committed emitted mirror differs from what its authority emits; the required comparator enumerates that mirror, observes the differing bytes, and reports failure before the merge, yet the merge completes. This is an admission failure after detection, not an inert or mis-scoped detector. HARM: hand-maintained projections reach main in either direction -- bytes the authority emits can disappear and bytes it does not emit can appear -- while every later branch inherits a red required lane. A separate actuator may repeatedly report green over its own population, making the repository look converged without touching the drifted mirrors. MEASURED SPECIMEN (gunbc#10273, merged as 5a12090e7a, 2026-09-04). The pull request hand-maintained two files in `v1_compiler.emitted_population`: it removed the 47-line rustc-judged `nested_refinement_cast_fixture_closure_discrimination` pair from `compiler_tests.rs` while emission still produced it, and added 39 lines to `std_measure.rs` that emission did not produce. The required build run 33908874519 executed `claim_executor --required-ci --required-lane build`, reported `planned=156 executed=156 adjudicated=156`, and at 19:17:52Z printed `required-ci: regen FAIL generated surface drift: compiler_tests.rs, std_measure.rs`; its aggregate required `witnesses` check concluded FAILURE at 19:30:00Z. The human merge occurred at 19:59:28Z. Thus a terminal refusal existed for twenty-nine minutes before landing. A second pull-request run was created at 19:59:46Z, after the merge, and is not the evidence this receipt rests on. THE SPECIMEN IS AN INSTANCE OF A MEASURED RATE, NOT AN OUTLIER. Reproduced on 2026-09-04 over the 25 most recently merged pull requests, all merged that same day: for each pull request, join its merged head SHA and `mergedAt` to that commit's check runs, retain `name == witnesses` with `completed_at < mergedAt`, and take the latest by completion time. The closed tally was 13 success, 9 failure, 3 undetermined, total 25. Thus 9 of 25 landed after a terminal red on that exact head, and 12 of 25 landed without an established green from this instrument. THE THREE ARE `undetermined`, NOT VIOLATIONS: no check named `witnesses` completed before their merge, but check names date a receipt and this census did not establish what an earlier roster called the executing check. The population is ONE DAY of merges and supports no extrapolation beyond those 25. The query was controlled first on gunbc#10273 and returned its known `failure` at 19:30:00Z; this matters because an earlier instrument passed an unsupported second `--arg` to `gh api --jq`, suppressed the resulting errors, and rendered every failure as an empty result. FOLLOW-UP RECEIPT, INDEPENDENTLY MEASURED 2026-09-05T03:58:38Z (gunbc#10500). This is the `undetermined` bucket, not another terminal refusal crossed and not an addition to the earlier day's census. `gh pr view 10500 --repo gunb-ai/gunbc --json mergedAt,headRefOid,state` identifies the merged head as `5c55457118814759467fe21c35419746ee920ba7` and the merge instant as 2026-09-05T03:49:17Z. Querying `repos/gunb-ai/gunbc/commits/5c55457118814759467fe21c35419746ee920ba7/check-runs?per_page=100&filter=all` returned `total_count=3`, checked nonzero and equal to the enumerated population before counting conclusions: `heal-generated-artifacts` (101243152637) started at 03:46:49Z, `required-witnesses-build` (101243152749) at 03:47:26Z, and `required-witnesses-floor` (101243152786) at 03:47:37Z, all on 2026-09-05 and all before the merge. At this later observation every `completed_at` and `conclusion` was null and every status was `in_progress`; no aggregate check-run named `witnesses` existed. The exact-head actions-runs query returned the `witnesses` pull_request workflow run 33942442212, created at 03:39:06Z and still in_progress. Thus the observed checks had begun before landing and remained nonterminal after it; no terminal verdict was available at the merge instant. A subsequent reading found `heal-generated-artifacts` completed successfully at 2026-09-05T04:04:43Z, strictly after `mergedAt`; the build and floor checks still had null completion fields. For those unfinished checks this receipt retains the dated null-completion observation rather than inventing a later verdict; when completion is reported, compare `completed_at` to `mergedAt`, not the current status label. Use the full head SHA and require a successful API response with a nonempty, fully enumerated population: an error or an empty result is not evidence of no refusal. Independently reading #10469's `mergedAt` gives 2026-09-05T02:18:23Z, placing this landing 1 hour 30 minutes 54 seconds after the row landed. The program that filed the row then populated its predicted undetermined bucket with its own PR while the row was live on main. Documentation had not made a terminal verdict a precondition of that landing path. This establishes neither a terminal-refusal violation of this row's primary subject nor that the merge was wrong, and attributes no actor or admission mechanism. DISTINGUISHING FACTS. `compiler_tests.rs` and `std_measure.rs` are members of `v1_compiler.emitted_population`, whose required regen phase compared all 156 planned mirrors and named both paths. They are not members of the distinct `gunbc.generated_artifact` `generated_artifact_registry`: the heal run on the same head reported `HealNoChange`, while the required generated-artifact phase reported `rostered=38 adjudicated=38 matches=38 drifted=0`. Heal runs `tools.generated_artifact_gate` `main_wet` and stages that 38-artifact registry; it does not run or install the 156-mirror required-regen candidate. Its green was true of its own population and could not converge either file. THE CONTENT WITNESS IS NOT THE MIRROR GATE. `nested_refinement_cast_fixture_closure_discrimination` is `#[ignore]`, and cargo test is on no CI step, so no executing merge-path gate covered what the deleted test asserts. The required regen phase nevertheless covered the containing mirror byte-for-byte and went red. Saying the test did not execute therefore explains why its semantics were not independently exercised; it does not explain or weaken the detected mirror drift. DISTINCT FROM `head_landed_by_hand_before_its_own_verification_reported`: there the landing precedes the head's terminal verdict, so improving detector speed cannot help and the record cannot say whether a reported refusal would have blocked. Here the required verdict was terminal and red twenty-nine minutes before the landing. Waiting longer was impossible as a repair because the answer had already arrived. Distinct from `green_reported_over_a_population_the_instrument_does_not_own`: that row concerns a green being read beyond its denominator. Heal exhibits that neighbouring shape here, but the landing defect is established by the red comparator that did own both files. RECOGNITION RULE: join the required check's subject SHA, conclusion time and conclusion to the landing's subject SHA and merge time. If the same head has a terminal required refusal before `merged_at`, yet lands, the detector is neither absent, inert nor late; the landing path did not make its refusal a precondition. Then enumerate every actuator separately before asking why a later green did not repair the state -- shared words such as generated and regen do not make two populations one. RUNG FOUND AT: mitigatable for admission. The comparator mechanically exposed the mirror drift at rung 2, but this row's subject is the consumer of that refusal: admission did not prevent the landing, and repair depended on a later author noticing the red residual and installing a full candidate. A correct detector whose refusal has no accountable consumer is observation, not rung 2 prevention on the landing path. This subsumes the otherwise-separate class `gate_refusal_has_no_accountable_consumer`: the unconsumed refusal is the mechanism established here, so a second row would give one class two authorities. CEILING: outside the modeled guarantee on the human hosting-platform path, and structurally guaranteed on a landing path this repository constructs; the class takes the weaker boundary honestly. The repository can model a landing whose accepted constructor requires a terminal success for the exact ref, but a person pressing the hosting platform's merge control is an external boundary until that path is removed or made to consume the modeled decision. Calling the existing required context a structural guarantee would contradict the specimen: its refusal was present and the external action still occurred. NEXT-RUNG TRIGGER, the same capability already named by `gunbc.guarantee_stall` `merge_admission_terminal_verdict_stall`: a landing path in which terminal SUCCESS for the exact ref that lands is a precondition, sufficient to refuse both absent evidence and an observed FAILURE, human actor included; until that exists, a landing observation that joins every merged head to the terminal verdict present at its merge instant and reports this class explicitly. Adding another comparator, widening heal, or speeding the existing run does not discharge it: the owned comparator already returned the right answer in time.) - **corroboration without an independent derivation axis** (a second reader reaches the same conclusion as the first, and the agreement is counted as confirmation -- but both readings ran the same query over the same lexical projection of the same subject, so the second signature carries no information the first did not. TWO SIGNATURES, ONE EVIDENCE LINEAGE. What makes it dangerous is that it looks exactly like independent replication from the outside, and it is REPORTED as replication: the second reader honestly says they checked. Specimen, measured 2026-09-04 on gunbc: two sessions independently concluded that HTTP listing pagination was modelled in this tree. Both read `extdeps.github.github` `default_per_page` and the `per_page` fields that consume it, and both substituted PAGE SIZE for CONTINUATION CLOSURE. The tree in fact carried no page parameter, no cursor advance, no Link-header handling and no page fold anywhere, so every listing read in the repository was capped at its first page -- and `extdeps.github.github` declared a `Pagination` type with `per_page`, `cursor` and `has_next` fields that NOTHING consumed, which is a shape with no execution behind it. **THE THREE CLAIMS THE SUBJECT ACTUALLY CARRIED, and conflating them is the mechanism rather than an incidental error: page SIZE (how many items one request asks for), cursor SHAPE (how the next request is named), and continuation CLOSURE (whether the sequence of requests was consumed until the authority said there was no next).** Only the first appears lexically in a request declaration. The third appears nowhere unless a fold exists, so a lexical reading can only ever return evidence about the first, and both readings returned it. **Recognition rule: when a second reading agrees, ask which DERIVATION AXIS it changed.** If both readings are the same query over the same projection, the agreement is one observation reported twice. A reading corroborates only when it moves an axis -- operation-graph reachability rather than token presence, an EXECUTING transition rather than a declaration, or a controlled multi-page mutation rather than a read of the shape that would serve one. Any one of those was available and cheap here, and any one would have returned the truth on first contact. **Distinct from `authority_substitution`, where prose invents an A-to-B arrow that no carrier states:** here every sentence either reader wrote about `per_page` was TRUE, and the defect is that a true fact about claim one was accepted as an answer about claim three. Distinct from `bound_shaped_closure`, which is about a CONSUMER concluding a requirement is discharged because a real number satisfied it; this is about two readers' agreement being read as evidence when it is one reading counted twice. Distinct from `reachability_read_as_occupancy`: that class is one substitution made once, this class is the reason such a substitution SURVIVES review. **DISTINCT FROM `population_selector_that_cannot_admit_a_counterexample`, and the boundary is stated rather than assumed because the two rows were filed the same night off the same specimen.** That row is about how a POPULATION is enumerated: a lexical proxy for the predicate makes the verdict unfalsifiable by construction, and this corroboration failure appears there as a receipt explaining why nobody caught it. THIS row is about when a SECOND READING is evidence at all, and it fires with a perfectly well-formed population -- two readers can share one lineage while reading a log, a dashboard or a diff, where no enumeration is involved and no proxy was adopted. **THE SUBJECT SPLIT IS THE SHARPEST TELL THAT THEY ARE DIFFERENT OBJECTS RATHER THAN TWO NAMES FOR ONE, and an earlier draft of this paragraph drew the line in the wrong place.** It claimed the discriminator was DECIDABILITY -- that the sibling row was statically decidable with ceiling 3 and a constructor for a trigger, against this row being undecidable at ceiling 1. That reading is refuted on the sibling row itself, which withdrew both the rung and the ceiling: whether a selector can admit a counterexample is a SEMANTIC relation between two expressions, not a shared-symbol question a lens can read, so that row is a ratchet at ceiling 1 for the same reason this one is. Two rows that are both undecidable cannot be told apart by decidability. **THE DISCRIMINATOR IS THE SUBJECT.** That row is about how a POPULATION WAS CONSTRUCTED -- a lexical proxy for the predicate makes the verdict unfalsifiable by construction, and this corroboration failure appears there as a receipt explaining why nobody caught it. THIS row is a property of the RELATIONSHIP BETWEEN TWO READERS, and it fires with a perfectly well-formed population: two readers can share one lineage while reading a log, a dashboard or a diff, where no enumeration exists and no proxy was adopted. A repair that closes either one leaves the other exactly where it stood. **RUNG FOUND AT: 1 (mitigatable)** -- the error was caught by a third reader who changed the axis, which is a person and not a mechanism. **CEILING: 1, and the reason is decidability rather than unbuilt work.** Whether two readings share an unstated premise is a property of how two agents derived a conclusion, not of any artifact in the tree; DESIGN section 5 names this the ratchet-forever case, and calling it mechanically preventable would be the `never` trap. What IS decidable, and was therefore repaired rather than rostered, is the specimen's own class: a truncated listing is unconstructible FOR ANY CONSUMER THAT READS THROUGH THE FOLD, because `std.page_fold` `PageFoldOutcome` carries the items only on the arm reached when the serving authority itself declared the listing closed, every other arm is a typed, located refusal carrying no partial result, and `PageFoldState` is `sole_constructor` so a closed-looking state cannot be forged to reach that arm without an authority that said the listing ended. **THE SCOPE OF THAT SENTENCE IS THE FOLD, NOT THE TREE, AND THE DIFFERENCE IS THE WHOLE REASON THIS ROW EXISTS.** No production listing operation consumes `std.page_fold` yet: the GitHub listing surfaces still expose only a page-size parameter, nothing imports `extdeps.github.pagination`, and `page_fold_live` is reached today only by its own witnesses. So every real listing read in this repository is STILL capped at its first page, and the repair to date is that the capability now exists and is executable rather than that any caller has been moved onto it. Writing this row as though the class were closed would be the same substitution the row is about -- a shape that exists read as a capability that runs -- committed by the very entry that names it. **THE REMAINING INTEGRATION OBLIGATION, stated so it can be checked rather than remembered:** one real paginated operation -- `extdeps.github.workflow_runs` `ListForRef` is the intended first -- must demand pages through `page_fold_live` and close on the authority's own Link-header end, with its own live-upstream witness. Until a production caller reads through the fold, the tree's truncation is mitigated by nothing at all; it is merely now REPAIRABLE without further modeling. That work is deliberately scoped out of the PR that landed this row, and this sentence is what makes its absence countable instead of forgotten. The `Pagination` type with no consumers was deleted in the same motion, since a declared shape with no execution behind it is the surface this class reads as capability. **NEXT-RUNG TRIGGER: none, and stating that honestly is the point** -- this row is a review-time recognition rule, and enumerating agreements more carefully does not retire it. What retires an INSTANCE is the axis change: name, before looking, what a second reading would have to execute for its agreement to mean anything.) @@ -345,3 +349,5 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a refusal that is the FINAL policy is written up as a rung below a more capable one, and a next-rung trigger is filed promising to admit the case it deliberately refuses** (INVALID STATE: a class refuses some input; an author reasons that a future mechanism could HANDLE that input rather than refuse it, concludes the refusal is therefore below its ceiling, and files a DESIGN 4b(2) next-rung trigger naming the more capable mechanism. HARM: the trigger becomes a standing obligation to BREAK the semantics the refusal was chosen to protect, and it is discharged by making the system MORE PERMISSIVE, which moves the class DOWN the ladder while the roster records a climb. The row then reads as a known gap on the way to being closed, so the deliberate boundary loses its author and the next person to touch it satisfies the trigger in good faith. **THE RECOGNITION RULE, SCOPED TO WHAT IT CAN ACTUALLY CARRY: EXPANDING A DELIBERATELY CHOSEN REFUSAL BOUNDARY IS NOT A SAFETY CLIMB MERELY BECAUSE IT ADMITS MORE CASES.** Read the proposed trigger and ask whether discharging it would make the system ACCEPT AN INPUT THE DESIGN DELIBERATELY REFUSES. If so, the trigger is a capability request wearing a safety rung's clothes and belongs in a product decision rather than on the ladder. THE LADDER MEASURES HOW A DECISION IS ENFORCED, NOT HOW MANY CASES IT ADMITS -- but only the DECISION's enforcement, which is why the boundary must be a chosen one for this row to apply. **THE RULE WAS STATED TOO WIDELY IN THIS ROW'S FIRST WRITING AND IS CORRECTED HERE RATHER THAN SOFTENED.** It said a genuine next-rung trigger ALWAYS preserves which inputs are admitted and which refused, changing only whether the refusal is checked or unwritable. THAT IS FALSE ACROSS THE LADDER, and the counterexample is an ordinary rung-1 to rung-2 climb: a failure that previously OCCURRED and was contained -- by a total operation, a rollback, an isolation boundary -- begins being BLOCKED once a gate exposes it, so the admitted set genuinely shrinks and behaviour genuinely changes. A universal rule that forbids that would refuse most real climbs, and a row whose recognition rule fires on legitimate work is worse than no row: it teaches reviewers to discount it. So the class is scoped to a DELIBERATELY CHOSEN refusal boundary, which is the population both receipts below actually inhabit. **THE ORDER OF THE TWO QUESTIONS IS WHAT ACTUALLY GOES WRONG, AND IT IS AN ORDER ERROR RATHER THAN AN IGNORANCE ERROR.** 4b(2) says a class below its ceiling must name its next-rung trigger. That obligation is CONDITIONAL, and the condition is the part that gets skipped: the class must first be ESTABLISHED to sit below a ceiling. An author who feels the obligation before performing the derivation will always find a trigger, because a more capable mechanism can be imagined for any refusal. So the diligent-seeming move -- 'the rule says name a trigger, so I will name one' -- is exactly the move that manufactures the defect, and reviewers reward it because a row with a trigger looks more complete than a row without one. **BOUNDARY AGAINST ceiling_never_exercised_for_a_population_the_census_cannot_plan AND AGAINST AN UNDERIVED CEILING, which are the two nearest neighbours and arrive from the opposite direction.** Asserting a ceiling NUMBER without deriving it claims the class cannot climb; this class assumes without deriving that the class CAN climb. Both skip the same derivation and they fail in opposite directions, which is why they are separate rows rather than one: the repair for an underived ceiling is to derive it and possibly climb, and the repair here is to derive it and possibly DELETE THE TRIGGER. A single row would make one of those two repairs invisible. **RECEIPT, gunbc.scm.merge_base, 2026-09-06.** The squash merge refuses divergent per-path content at MANIFEST GRAIN rather than merging file contents line by line. This author wrote that absent line-level merging was 'a stated scope with a next-rung trigger: a text-difference capability'. The side-chat reviewer refused it: refusing divergent file contents can be the FINAL safety policy of the operation, and a text-difference capability increases ADMITTED MERGE POWER rather than safety. If the semantics are deliberately 'never synthesize file content neither author supplied', then text-level auto-merge VIOLATES those semantics instead of climbing toward them, and the trigger would have been an obligation to break the design. The trigger was not written; what was written instead is that manifest-grain conflict is a deliberate semantic boundary, with whether line-level merging is wanted AT ALL left open as a product question rather than presumed. **A SECOND RECEIPT ON THE SAME LANE AND THE SAME DAY, WHICH IS WHY THIS IS A CLASS AND NOT AN ANECDOTE.** gunbc.scm.integration records the consumed source as a commit reference, which stops being a durable anchor if source commit records are ever PHYSICALLY DELETED rather than merely kept out of target ancestry. The reflex was to file that as a next-rung trigger. It is not one: retirement is a capability nobody has asked for, nothing establishes that this cut sits below a ceiling, and a trigger would have committed a future author to build durable provenance for a deletion feature that may never exist. It is recorded in that module as a FUTURE BOUNDARY -- what would have to be designed IF the capability is ever wanted -- which carries the same information and creates no obligation. THE REPAIR IS USUALLY A RESTATEMENT, NOT A DELETION: the knowledge is worth keeping, and only its status as a debt is false. **CEILING: UNDERIVED, AND STATED SO RATHER THAN GUESSED, on this row's own rule.** Deciding mechanically whether a proposed trigger widens the admitted set requires comparing the semantics before and after a mechanism that does not exist yet, and this author has not established that comparison is decidable from modeled facts. Asserting a rung here would be the underived-ceiling error in a row about underived ceilings. What is honest today: the class is caught by review, it has been caught twice by one reviewer on one lane, and the instrument that found both was a question a person asked rather than anything executed. NEXT TRIGGER: none is named, because naming one would require knowing the ceiling. - **a walk whose native-stack depth grows with the depth of corpus-shaped data, with no declared bound** (the compiler recurses once per level of a structure whose depth is an input, not a constant. Past some corpus the process aborts on stack exhaustion. HARM: the abort is not a diagnostic -- it is a SIGSEGV turned into `thread ... has overflowed its stack` and an abort, so it carries no span, no diagnostic identity, and no partial result. The run neither succeeds nor refuses, and every downstream reader sees a crashed process rather than a located refusal. Worse, the abort is silent about its own cause -- it kills the process before the summary line, so the very output that would characterise the input is the output you lose. **THE DISTINGUISHING FACT IS THAT THE RECURSION NEED NOT APPEAR IN OUR SOURCE AT ALL.** The class has two instances and they differ in exactly this, which is why one row covers both and why looking only for authored recursion finds half of it. AUTHORED INSTANCE: `v1.compiler.infer_types` `node_type_shape` and `node_type_shape_argument_list` are mutually recursive over type structure with no depth bound, rendering `Primitive(...)`, `Named(...)`, `Product(...)`, `Coproduct(...)` by descending into children. A reader can find this one, and grep for `fn` names finds it. COMPILER-GENERATED INSTANCE: `v1.std.core` `Node` carries `children: Rc>>` (and `params`, `uses`, `properties`, `body`, `type_annotation` likewise) with no hand-written `Drop`, so the DESTRUCTOR rustc synthesises recurses once per level on every release of a deep graph. **No reading of our own source can find it, because it is not in our source.** Grepping for recursion, auditing every `fn`, and eliminating every in-window candidate by inspection all report health -- and did, on the specimen below, where three code candidates in the failing window were correctly eliminated by reading while the actual recursion was in glue no one had written. **RECOGNITION RULE, and it is about the DATA, not about the code: any type whose values are trees or chains of unbounded depth carries this class in every operation that touches a whole value -- including the ones you did not write.** Construction, traversal, rendering, cloning, comparison, hashing, serialisation and RELEASE are all depth-recursive by default. The question to ask of a recursive carrier is not `which of my functions recurse` but `what happens to a value of this type at 10^6 levels`, and the honest answer is usually that nobody knows because nobody has one. **RECEIPT, EXECUTED, WITH A PAIRED CONTROL AND ONE CHANGED LINE** (2026-09-05, quick-heron-85, during the NS-N containment cut). A two-file subject -- `std.measure` plus `test.claim.long.reference_closure_equivalence_test`, compiled with `src/v2` and `src/v1` as name-pool roots -- aborts with `stack overflow` after four pipeline phases and before its diagnostic summary prints. THE STACK-THRESHOLD INSTRUMENT TURNS THE BINARY INTO A SCALAR: bisecting `ulimit -s` gives the minimum stack at which each configuration completes. Each file ALONE completes at 1024 KiB; the PAIR completes at 147456 KiB and overflows at 139264 KiB. A factor of at least 144, closed on both sides, which is what establishes the recursion as FINITE AND DEEP rather than unbounded-in-the-limit -- an unlimited stack completes. THE DISCRIMINATOR IS THE ONE-LINE PROBE, not the threshold: `std::mem::forget(resolved.clone())` at the head of `v1.compiler.compile` `emit_resolved_for_target_selected`, holding files, binary invocation, source roots, stack size and diagnostics fixed and changing ONLY whether the tree is ever released. Paired at the same stock 8192 KiB: unpatched OVERFLOW, leaked binary exit 1, zero `stack overflow`, and the summary line prints (496 blocking, 2643 advisory). The destructor was the recursion. **THE PROBE MUST FORGET A CLONE, NOT THE VALUE**, and this is the part worth carrying to the next investigator: `resolved` is an `Rc`, so forgetting IT merely drops one handle and moves the destructor to whoever holds the last one -- reading as a false PERSIST and killing a true hypothesis. Forgetting a CLONE pins the outer refcount above zero, so the contents can never be released no matter who else holds a handle. That converts `check the precondition` into `remove the failure mode`, which is the difference between a probe you have to trust and one that cannot lie. **WHAT DID NOT DISCRIMINATE, recorded because each looked decisive and cost a cycle:** diagnostic COUNT is blind -- a probe rendering 8,000 diagnostics does not overflow while this configuration's 3,139 does; per-diagnostic SHAPE is blind unless the shape is in the graph; and a 2,000-level-deep authored expression chain completes. The depth that matters is the depth of the structure RELEASED, which no probe varying the diagnostic list can reach. **RUNG FOUND AT: 1, MITIGATABLE, by fail-stop and process isolation -- AND SECTION 5's FAIL-CLOSED INTERFACE IS STILL VIOLATED. Both halves belong in the sentence.** The mitigation is real and is what earns the rung: the abort is a fail-stop at a process boundary, and that boundary PREVENTS AN ACCEPTED RESULT OR ANY OTHER SUCCESS PUBLICATION -- the run dies before its summary line, so no consumer receives a partial or fabricated answer. Section 4b names isolation among rung-1 mechanisms, so the absence of a typed result does not deny rung 1. **BUT RUNG 1 DOES NOT REHABILITATE THE SIGSEGV**: section 5 independently requires every path to succeed fully or fail with a TYPED, LOCATED diagnostic, and an abort carrying no subject, no span and no diagnostic identity fails that badly. The deficit is a rung-1 class with a standing section 5 interface violation, which is what makes it rank for repair. **AN EARLIER REVISION OF THIS ROW PLACED THE CLASS OUTSIDE THE LADDER, AND THAT WAS WRONG IN A WAY WORTH KEEPING VISIBLE** (corrected 2026-09-06, native review 5124409497). Section 4b's only exclusions are silent wrongness below the floor and the adjacent column of external reality, undecidable properties and undeclared intent; this class is none of those, and the row REFUTED ITS OWN EXCLUSION two sentences later by stating that the depth property is decidable, that nothing waits on external reality, and that both a declared bound and an iterative implementation are authorable. **EXCUSING A CLASS FROM HAVING A RUNG HAS THE SAME PRACTICAL EFFECT AS INFLATING ONE -- IT NEVER RANKS**, which is the failure this ledger exists to prevent, arriving from the low side rather than the high one. **CEILING: 3, STRUCTURALLY GUARANTEED -- NOT 4, BECAUSE DECIDABLE DOES NOT IMPLY RUNG 4.** Depth is decidable and nothing waits on external reality, but decidability establishes only that a structural decision procedure is POSSIBLE. If an unbounded recursive carrier remains WRITABLE while the compiler derives a proof or a refusal for it, that is rung 3. **RUNG 4 REQUIRES THAT THE INVALID STATE HAVE NO CONSTRUCTOR IN THE CANONICAL MODEL, AND THIS ROW CANNOT NAME THE CONSTRUCTOR-ELIMINATION PATH**: no canonical carrier model is in hand in which data-dependent native-stack recursion is inexpressible, and a recursive carrier is a thing the substrate is meant to be able to declare. The ceiling is therefore raised to 4 only by a future row that NAMES that model; until then, claiming 4 here would be the inflation this ledger refuses. **THE REPAIR MAY NOT BE A BIGGER STACK, A FATTER THREAD, OR A RAISED BUDGET, AND THE LEAK IS AN INSTRUMENT RATHER THAN AN ARM.** All four leave the abort exactly where it was and move the corpus at which it fires, which is section 5's absorbing fallback in its purest form: the failure arm WIDENS instead of refusing, the widening is denominated in the corpus rather than the change, and the deficit stops ranking for repair because the symptom moved. The two honest arms are a release and traversal whose depth does not grow with the data -- an explicit iterative teardown and an explicit worklist walk -- or a DECLARED depth bound enforced at construction, refusing a too-deep structure with a typed, located diagnostic naming the carrier and the level. **THE TRIGGER BELOW IS A DELIBERATE 1-TO-3 JUMP, STATED SO IT IS NOT MISREAD AS THE ADJACENT RUNG.** Deriving the risk from the declared carrier and failing compilation when an unbounded carrier lacks an iterative release or a declared bound is a STRUCTURAL GUARANTEE, rung 3 -- it skips rung 2 rather than passing through it. The rung-2 form, if the jump proves too large to take in one step, is a generated lens or an adversarial-depth witness that reliably exposes and blocks the class WHILE THE INVALID IMPLEMENTATION STAYS WRITABLE and safety depends on that mechanism executing and staying enrolled. **NEXT-RUNG TRIGGER, and it names a CAPABILITY rather than a repair to one carrier:** a mechanism that derives, from a declared carrier, whether any operation on it -- including generated ones -- can recurse without a bound, so that authoring an unbounded-depth carrier without an iterative release or a declared bound fails to compile. Repairing `Node` alone does not retire this row; it removes one instance, and `node_type_shape` would still stand, as would every carrier authored tomorrow. **BOUNDED AGAINST TWO NEIGHBOURS.** Section 5's absorbing fallback is a mechanism that WIDENS when it cannot answer precisely; here nothing widens and nothing is answered -- the process dies. And `right_censored_cost_read_as_exact` concerns a measurement truncated by a limit; the resemblance is superficial, because there the run completes and reports a bounded number, whereas here the limit destroys the run and its report together, which is precisely why the threshold instrument had to be built to get any number at all. **WHY THIS RECEIPT NAMES NO PRODUCER, STATED RATHER THAN LEFT AS AN OMISSION** (raised as a section 6 objection, review 61308). Section 6 requires a measurement to be cited by naming the producer that re-derives it, because A TRANSCRIBED NUMBER IS UNREACHABLE FROM THE THING THAT OWNS IT AND ROTS WITHOUT ANYONE TOUCHING EITHER END. **THE OWNER OF THESE FIGURES NO LONGER EXISTS.** Every number here was observed on a configuration requiring a resolver rule that is not on main and, by ruling, never lands in that shape; the specimen is extinct, so NO ENTRY POINT CAN RE-DERIVE IT AND NONE IS OWED -- which is the case section 6 names in its own final clause, a one-off rather than an instrument. What is recorded is therefore a HISTORICAL OBSERVATION OF AN EXTINCT CONDITION, not a standing measurement, and it cannot rot in section 6's sense because it asserts nothing about the current tree. Without this sentence a reader concludes the producer was omitted rather than impossible, which is why the absence is declared instead of left to be noticed. **THE POPULATION ON MAIN IS UNMEASURED AND THE ROW DOES NOT CLAIM OTHERWISE, WHICH IS A DENOMINATOR FACT AND NOT BOOKKEEPING** (established 2026-09-06, after this row was first written, by the three-cell probe that identified what builds the chain). THE SOLE SPECIMEN WAS PRODUCED BY A RESOLVER RULE THAT IS NOT PRESENT ON MAIN: admitting the one refused callable owner collapses the specimen from 1,800,008 to 85, with the deepest site moving to an unrelated module, and the same instrument reads 85 under main and under the merge base. **THE TWO LEVELS ARE CONDITIONAL OBSERVATIONS AND MUST NOT BE LIFTED OUT AS PROPERTIES OF "MAIN" AND "THE CUT".** They were measured under one fixed execution condition -- that corpus, that traversal order, that pool construction, that binary and instrumentation -- and the pool construction in force is itself order-dependent, so the absolute levels are traversal-order-contaminated. WHAT GENERALISES IS THE DISCRIMINATING EFFECT OF CHANGING THE PREDICATE, not the numbers: a defective traversal regime can move the absolute state presented to every cell alike, but it cannot manufacture a DIFFERENCE between cells when that regime was held fixed. So the collapse stands as causal evidence with its held condition attached, and 85 is not a fact about main. **WHETHER ORDINARY CORPUS GROWTH ALONE CAN REACH COMPARABLE DEPTH IS UNMEASURED, AND NOBODY HAS SHOWN THAT IT CAN.** THE PRODUCING DEFECT IS NAMED SO THE TWO ROWS CONNECT RATHER THAN LEAVING THE NEXT READER TO RE-DERIVE IT: the depth was reached because an UNRESOLVABLE CALL WAS ELABORATED RATHER THAN REFUSED, which is filed as the call-site form of `gunbc.recurring_failure_mode.state_space_conflation` -- the callable-lookup carrier collapses builtin, locally-bound and genuinely-missing into one constructor, and the consumer reads that collapsed absence as affirmative formal authority. **STATED IN BOTH DIRECTIONS, BECAUSE EACH HALF GUARDS AGAINST THE OPPOSITE MISREAD.** A reader must not conclude that corpus growth alone reaches this depth -- that is the unmeasured half above. A reader must ALSO not conclude that the structure was a harness artefact: NOTHING WAS FORCED IN THE RUN THAT PRODUCED IT. The 1,800,008 baseline is an ordinary compile under an ordinary resolver rule; the forced admissions were the two PROBES placed either side of that baseline to identify its cause. The structure was real, an accepted compile built it, and the class it exhibits is independent of whichever defect supplied the depth -- which is why this row stands on its own and does not wait on that defect's repair. **THE SECOND DEFECT THIS SPECIMEN EXPOSES IS NOT THIS ROW AND MUST NOT BE ABSORBED INTO IT:** nothing in two source files is 10^5 levels deep, so some pipeline stage BUILDS a structure far deeper than its input -- a chain where a flat structure was intended. That is a separate defect, for which this class is only the reporter, and it will be pathological for every consumer that walks the structure rather than only the one that releases it.) - **DESTRUCTIVE CLEANUP CORRECTNESS DEPENDING ON AN UNDECLARED EVALUATION-MODEL PROPERTY. THREE TRANSPORTS ARE CORRECT BY ACCIDENT OF REALIZATION, NOT BY CONSTRUCTION. INVALID STATE:** the interpreter implements `let` as eager/strict (eval_let evaluates the value expression unconditionally at the point of binding). Nothing declares this as a language guarantee — it is a property of the current interpreter realization, not a declared semantic axis. The companion divergence — that interpreter `&&` evaluates both operands while emitted Rust `&&` short-circuits — is already filed as realization_arms_diverge_on_whether_the_program_refuses. It does not affect these transports: their cleanups are let-bound, not &&-bound, and under eager let a short-circuit && cannot change any conjunction's value because it skips only the right operand when the left is false — a path where the answer is already determined and the skipped operand is a pure field read (.success) on a record whose effect already fired at its let binding. STRUCTURALLY, no transport in this class depends on && eagerness, and no addition to the corpus can create such a dependence as long as let stays eager. If `let` became lazy, cleanup would never run in the namespace_import_closure_behavioral_transport (where cleanup is bound but never consumed), and would be unobserved in the other two (where cleanup .success would not be consumed, though the eager `let` at binding point would still fire). The distinction matters: 'effect does not reach the filesystem' is a leak; 'effect reaches the filesystem but its success is unobserved' is an unobserved cleanup failure — different harms, different severities.POPULATION, BOUNDED AND ENUMERABLE — THE THREE TRANSPORTS: `tools.self_host_logic_behavioral_transport::slb_comparison_observation` (let-bound cleanup_src/cleanup_out consumed in the staged conjunction), `tools.emit_host_transport::run_rust_smoke` / `::run_python_smoke` / `::run_go_smoke` (let-bound cleanup consumed in a conjunction), `tools.namespace_import_closure_behavioral_transport::nic_emitted_crate_evidence` (let-bound cleanup_src/cleanup_out NEVER consumed — correct only because let is eager).CEILING: cleanup that does not depend on the evaluation model at all — an effect the transport sequences explicitly rather than one it gets from strictness. Every transport's destructive cleanup would be structurally sequenced (e.g. a `do cleanup after body` pattern) rather than relying on let-eagerness for its effect to reach the filesystem.NEXT-RUNG TRIGGER, A CAPABILITY — either (a) the evaluation model declared as a typed guarantee that a check can execute against — a property of the interpreter that is named, bounded, and verifiable, not prose — or (b) every transport whose correctness turns on eager evaluation sequences its destructive effects explicitly rather than inheriting them from let-strictness. Both capabilities are genuine transitions; neither is satisfied by documentation or by this row.CORPUS POPULATION MEASUREMENT — the && divergence has no live hazard in this corpus: zero sites place a non-total or effectful operation directly as the right operand of `&&` (the two found are read-only). Twenty sites let-bind destructive effects and then consume their .success in a `&&` chain; under the structural argument above, none depends on && eagerness. HOWEVER, A=0 is a SURVIVOR PROPERTY OF THE STRICTER ARM HAVING FILTERED THE CORPUS, not evidence the class is harmless. The interpreter evaluates both operands unconditionally, so any author who wrote `guard && dangerous` would have hit the refusal while authoring and rewritten before commit. Under a short-circuit `&&`, authors would write these patterns freely and they would compile — and the emitted arm would silently produce a wrong answer where the interpreter refuses. That is the existing row's subject.INSTRUMENT — DISCRIMINATING PROBES, THEIR NAMES AND INVOCATION, SO A FUTURE READER CAN RE-DERIVE THIS FINDING AND RE-RUN IT. Five executed probes in module `test.claim.eval_model_probe` (dag/test/claim/eval_model_probe_test.dag): `let_is_eager_when_unused` (let-binds a Remove without consuming its result; PASS means eager), `let_is_eager_when_consumed` (positive control), `and_demands_right_even_when_left_false` (let-binds false and Remove separately, && computes; PASS means Remove ran despite false), `and_short_circuit_direct_probe` (false && Remove directly; PASS means Remove ran), `and_with_true_left_removes_file` (positive control). Each was run as: `gunbc run --source-root dag --source-root src/v2 --entry dag/test/claim/eval_model_probe_test.dag --function --claim-run`. All five returned PASS (exit 0). Probes observed the filesystem side effect out-of-band (shell.Test.IsFile) rather than consuming a returned value, so no measurement-created dependency could confound the result. +- **generated `docs/*.md` ledger projections have no ProcessExit actuator and no regen coverage, so an EDIT to a still-present row can sit in the authority, absent from the projection, with every completing instrument green** (INVALID STATE: `gunbc.recurring_failure_mode` is the authority; `docs/design-failure-modes.md` is the projection DESIGN.md points readers at. `expected_design_failure_modes_md` returns `String`. The refusal exits 2 with EMPTY STDOUT and interpolates the rendered value into the diagnostic on stderr: classify_exit's non-variant arm is type_name: ctx.format_value(other). Executed: wise-badger-902, EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown heading; quick-heron-85, proj3.err 1062137 bytes, same delimiters. The enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` forbids print-AND-SUCCEED, and holds. The content is therefore obtainable ONLY by scraping a half-megabyte stderr diagnostic between delimiters that are message prose (returned-open-quote and not-ProcessExit-close-quote), unversioned and enrolled by nothing. Extractable only by scraping an unversioned diagnostic — which is why the ProcessExit regen is the repair. `claim_executor --required-regen` compares only `is_compared_generated_basename` = ends_with .rs into `target/stage0-regen-candidate`. HARM: three rows can be edited in `.dag` after the last projection and nothing that exits 0 will say the markdown is stale. The meaning-fork in the field named type_name is a separate row, `type_name_field_carries_rendered_value`, not this class. SPECIMEN, measured by another lane while regenerating the projection, 2026-09-06: `instrument_answers_outside_its_domain`, `stale_claim_survives_its_own_correct_edit`, and `native_stack_recursion_unbounded_over_corpus_shaped_structure` had been edited in their authorities after the doc was last projected and had grown by 2349, 5519 and 1152 characters. Regenerating carried the repairs through. On origin/main as of 3531d30bb82 those three `.dag` files are older than `docs/design-failure-modes.md` (`81686f7bea8`), so CURRENT main is not claimed still stale; the class is that nothing completing could have said so while they were. SPECIMEN, review 58567 on gunbc#9864, on the host comment above `exit_status_for_class`: a relocation dropped the `status: refused — printing the value and exiting 0 would report success for a run whose outcome is unknown.` sentence while the comment claimed byte-for-byte equivalence. The doc lied, not the code. The sentence was restored rather than the claim weakened. That is this class on a different pair: an authority and its projection disagreeing with nothing completing to say so, and a real specimen rather than an injected literal. INCIDENT, not a fixture and not a perturbation ordered for this PR, produced by quick-heron-85 without looking for this class, commit b1c6378c6d: their authority row unresolved_callee_conflated_with_dispatched_elsewhere was UNPARSEABLE for a full commit. An authored receipt contained a quoted phrase using bare double quotes inside a double-quoted string; the string terminated early and the parser refused undefined variable this and undefined variable leaf. THE AUTHORITY DID NOT COMPILE, so docs/design-failure-modes.md kept rendering the previous vintage while presenting as current. They found it only by running the projection; reading the row does not surface the break. This PR's gate would go red on that commit because an unparseable authority makes the projector refuse — from the committed markdown alone, STALE bytes and an UNBUILDABLE authority are indistinguishable. THIS ROW IS THE DETECTOR GAP. The quoting injury that silently disabled the row they were filing is a neighbouring class they are filing separately; its next-rung trigger is string escaping in the .dag lexer. This gate makes that injury loud; it does not make it unwritable. Do not read this row as closing that class. THE IDENTITY-JOIN CORRECTION, which is the transferable part and which corrects a rule this project holds. Completeness is an identity join, not a count equality — and AN IDENTITY JOIN OVER ROW NAMES WOULD HAVE REPORTED THAT FILE COMPLETE AND CORRECT: every identity present, none missing, none extra. The drift was pure EDIT. Only a regenerated byte diff sees an edit. Worse: a first pass comparing line SETS read the three as REORDERINGS, because the changed rows shared an 80-character prefix with their old text, so the cheap check does not merely miss the drift, it manufactures a benign explanation for it. An identity join proves MEMBERSHIP, not CONTENT. THE TWO NEIGHBOURS THIS IS NOT. `authority_merges_unprotected_while_its_projection_is_guarded` is MERGE POLARITY: the generated projection is driver-bound while the hand-authored authority is not. Adjacent files, distinct defect — this row is the absence of a completing actuator or content gate, reachable with no merge in sight. `green_reported_over_a_population_the_instrument_does_not_own` is a split of actuators whose GREEN is read as covering the half it does not enumerate; that climb joined registry-projections to stage0-mirrors inside one required phase, and still does not give `gunbc run` a ProcessExit projector for the markdown, nor put `.md` in `--required-regen`. RECOGNITION RULE: when a generated file is what readers are pointed at, ask (1) whether any `ProcessExit` entry writes those exact bytes and exits 0, (2) whether regen's compared population includes that path, and (3) whether the gate compares CONTENT, not row-name membership. A complete identity set of names is not a content agreement. RUNG FOUND AT: outside the ladder until a completing content agreement exists — silent wrongness, not a weak wall. After a ProcessExit projector and a required content compare execute, mechanically preventable: the stale file remains writable. CEILING: structurally guaranteed when membership of every committed generated `docs/` path in a content-agreement adjudicator is derived from `gunbc.generated_artifact` `committed_generated_artifacts`, so omitting the arm is a compile refusal. NEXT-RUNG TRIGGER, a capability: required-run dispatch consumes a content agreement — byte identity of generated vs committed — over every committed generated artifact whose location directory is `docs`, derived from the registry so adding a committed `docs/` path without that agreement will not compile. A trigger naming one command, one test file, or `docs_projection_gate.dag` is less than the capability and would fire while edit-only drift can still sit uncompared.) +- **a field named type_name holds a type name on three arms and a fully rendered value on the fourth** (INVALID STATE: ExitClass::NotProcessExit is one spelling whose payload field is called type_name. `classify_exit` Variant arms that are not ProcessExit put ctx.resolve of the variant type symbol, or ProcessExit plus the unknown variant, into it — an actual type or variant name. The final other arm puts ctx.format_value of the non-variant value — the rendered value — into the same field. `exit_status_for_class` interpolates that field into the returned-quote slot with no second match, so every refusal path reads one name for two facts. HARM: a reader of the interpolation site infers the field's contents from its NAME and concludes a String projector yields the word String; a reader of the producer and a live stderr file concludes the markdown body is in the diagnostic. Both readings of adjacent code are correct; the name is a third surface and it lied. SPECIMEN, 2026-09-06, executed both ways. clever-otter-298 read `exit_status_for_class` and the enrolled control `non_process_exit_refuses_rather_than_printing_and_succeeding` (which constructs NotProcessExit with a fixture type_name Bool and never goes through `classify_exit`) and asserted the diagnostic names the type. wise-badger-902 read the interpolation site and ruled the same. quick-heron-85 held proj3.err at 1062137 bytes. The manager then RAN the projector: EXIT=2, stdout 0 bytes, stderr 578588 bytes opening with the markdown. The field name said type; the field held format_value. WHY THE FORK SURVIVED, and it is a section 4b evidence gap not a naming smell. `non_process_exit_refuses_rather_than_printing_and_succeeding` hand-constructs ExitClass::NotProcessExit with type_name the literal Bool and starts from an already-classified value. It never calls `classify_exit`. Nothing executing joins the field's name to its contents on the non-variant path — the only enrolled evidence feeds the field a literal that matches its name, which is exactly the input that cannot discriminate. The control's RED is unauthorable for this defect, so it was permanently green over the case that mattered and got cited as coverage. A field carrying two facts is ordinary; a field carrying two facts with a control that only ever sees one of them is why three of us read adjacent code correctly and reached three answers. THIS IS NOT `docs_markdown_projections_have_no_completing_actuator`. That row is the absence of a ProcessExit actuator and a content gate over generated `docs/*.md`. This row is a meaning fork inside `ExitClass`, reachable with no docs file in sight. Neighbour `meaning_fork` is the generic class; this instance is filed because the fork sat in the struct every refusal path reads and produced the three-way contradiction. Neighbour `diagnostic_name_mechanism_silent` is a diagnostic NAME silent about its predicate; here the FIELD name is accurate for three arms and false for the fourth. RECOGNITION RULE: when a field is named for a kind of contents, read every constructor, not the interpolation site. If one constructor writes a different kind, the name has forked. RUNG FOUND AT: mitigatable — the wrong inference is a human reading; the runtime still refuses with exit 2 and does not print-and-succeed. CEILING: structurally impossible when `NotProcessExit` cannot be constructed with a rendered value under a field named for a type — two variants, or a field named for what it actually holds. NEXT-RUNG TRIGGER, a capability: `ExitClass` distinguishes a type-name refusal from a rendered-value refusal in the type, so interpolating one as the other will not compile.) From bce2d1cb2482120523ffe816f5e36153e463155d Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 05:45:33 +0000 Subject: [PATCH 22/22] Cite std.process.exit_ok in the scope-placement loudness comment. The other two sequencers already named the rehomed predicate; this copy still pointed at tools.ci_gates after that symbol was deleted. Co-authored-by: Cursor --- dag/gunbc/instruments/extdeps_scope_placement_gate.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dag/gunbc/instruments/extdeps_scope_placement_gate.dag b/dag/gunbc/instruments/extdeps_scope_placement_gate.dag index 28b568d089e..22611ccbc79 100644 --- a/dag/gunbc/instruments/extdeps_scope_placement_gate.dag +++ b/dag/gunbc/instruments/extdeps_scope_placement_gate.dag @@ -251,7 +251,7 @@ func run_extdeps_scope_placement_gate_body() -> ProcessExit { } // The loudness half (gunbc#7644 diagnosis): the floor witness collapses this gate's ProcessExit to -// Bool via tools.ci_gates.exit_ok, so ten consecutive main runs reported the bare string 'returned +// Bool via std.process.exit_ok, so ten consecutive main runs reported the bare string 'returned // Bool(false)' and named neither the refusing arm nor the offending path — the located diagnostic // scope_placement_gate_verdict already builds was discarded one frame above it. This projects the // SAME verdict (one authority, never a second decision path) into the claim_executor