From bfd48a40f5485b76aa23633c54ac853637e1b645 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 31 Aug 2026 02:30:32 +0000 Subject: [PATCH 01/28] Census the first() interpreter/emitted divergence, and repair the half that has an authority `dag/std/algebra.dag` declares `first`/`last`/`get`/`lookup`/`map_get` with `return_type: OptionalOf { inner: ReceiverElement }`. The Rust emit arm realizes that row (`{recv}.first().cloned()` -> `Option`); the interpreter answered the same question by hand and answered it differently -- `items.front().cloned().unwrap_or(Value::Null)`, the RAW element. Two realizations of one declared signature that disagree are DESIGN.md section 5 silent wrongness, outside the guarantee ladder rather than low on it. CENSUS (docs/plans/first-optional-divergence-census.md). 186 terminal `|> first` sites over 81 files in dag/ + src/v2 on main, rostered at identity grain in three shapes: 143 eliminated by `match` (the population the interpreter's compensating raw-unwrap arms already made agree, and the one the repair must not break), 37 returned onward as the enclosing function's `T?`, 6 flowing into a value position. The count is for reconciliation with the parent lane's 187/82 only; the roster is the deliverable. The census did not stop at the pipeline spelling, and that is where it earned its keep. The METHOD form `.first()` is a separate population of 646 occurrences over 180 files whose dominant idiom is the value position -- `parse_int(s: fields.first())`, `trim(tokens.first())`, `percent(scalars.first())`. Those work today because the emitted arm inserts `rust_call_arg_fail_closed_unwrap`'s `.expect(..)` while the interpreter needs no coercion at all, having never wrapped in the first place. So the raw-element arm is not one bad handler: it is the compensation the interpreter's MISSING argument coercion has been leaning on corpus-wide, and repairing `first` alone converts a silent agreement into a silent disagreement. MEASURED, not argued. A five-case probe run through `gunbc run` fixes the divergence (`[Absent] |> first` read as an empty list; `(["x"] |> filter(..) |> first) == Present { value: "x" }` false interpreted and true emitted). Those five rows are enrolled in dag/test/claim/first_optional_construction_witness_test.dag, 7/7 green with this change and 4 red against the unmodified arm, with three green positive controls separating "constructs the Optional" from "refuses everything". branded_list_first_optional_witness stays 8/8 green. REPAIRED HERE: `first`/`last`/`get`/`lookup` construct the Optional their roster row declares, decided by call site rather than value shape (the rule `map_lookup_as_optional` already states); `eval_algebra_method_inner` refuses when an arm's result does not inhabit the optionality `all_algebra_field_templates()` declares for it; `.value` on an absent Optional refuses instead of returning `Value::Null`; and `call_function_inner` gains the optional-into-required-parameter coercion the Rust emitter already had, unwrapping `Present` and stopping the line on `Absent`. NOT CLOSED, and the census says why: a builtin call never reaches `call_function_inner`, and `builtin_function_registry` maps a builtin to a RETURN TYPE only, so argument cardinality cannot be derived for one. Deciding it from the argument's value shape is validation standing where construction was available. The grounding this class waits on is builtin PARAMETER signatures; the doc names it as the blocker rather than working around it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK --- ...rst_optional_construction_witness_test.dag | 85 ++++++ .../plans/first-optional-divergence-census.md | 214 ++++++++++++++ src/v1/stage0/src/v1_interpreter.rs | 261 ++++++++++++++++-- 3 files changed, 544 insertions(+), 16 deletions(-) create mode 100644 dag/test/claim/first_optional_construction_witness_test.dag create mode 100644 docs/plans/first-optional-divergence-census.md diff --git a/dag/test/claim/first_optional_construction_witness_test.dag b/dag/test/claim/first_optional_construction_witness_test.dag new file mode 100644 index 00000000000..a9944d2dabf --- /dev/null +++ b/dag/test/claim/first_optional_construction_witness_test.dag @@ -0,0 +1,85 @@ +module test.claim.first_optional_construction_witness + +// WHAT THIS PINS. `dag/std/algebra.dag` declares `first`, `last`, `get`, `lookup` and `map_get` +// with `return_type: OptionalOf { inner: ReceiverElement }`. That one row is what makes the Rust +// emit arm produce `Option`; the interpreter used to answer the same question by hand and +// answer it differently -- `first` returned `items.front().cloned().unwrap_or(Null)`, i.e. the RAW +// ELEMENT. Two realizations of one declared signature that disagree are DESIGN.md section 5 silent +// wrongness: `(["x"] |> filter(n => true) |> first) == Present { value: "x" }` typechecked and +// returned FALSE interpreted while returning true emitted, with no diagnostic anywhere. +// +// WHY THE PRE-EXISTING first WITNESSES DID NOT CATCH IT. `branded_list_first_optional_witness` +// eliminates `first` with `match { Present { value: v } => .. Absent => .. }` over a NON-optional +// element type, and the interpreter carried compensating raw-unwrap arms in `match_pattern` that +// made exactly that shape agree. So the whole match-scrutinee population -- the large majority of +// the corpus's terminal `|> first` sites -- was green on a divergence it could not observe. The +// two shapes below are the ones the compensation cannot cover, and they are the discriminating +// RED: each one goes red against the raw-element arm and green against the constructed Optional. +// +// SHAPE 1 -- THE ELEMENT TYPE IS ITSELF AN Optional. `[Absent] |> first` must be +// `Present { value: Absent }`, one head that is an absent optional. Under the raw arm the head +// came back bare, so the outer `Present` pattern saw an `Absent` variant and the whole match took +// the OUTER Absent arm: a one-element list read as an empty one. +// +// SHAPE 2 -- THE RESULT IS COMPARED RATHER THAN MATCHED. `==` against `Present { value: .. }` has +// no pattern for a compensation arm to intercept, so it reads the representation directly. + +fn classify(xs: List>) -> String { + match xs |> first { + Present { value: inner } => + match inner { + Present { value: s } => s + Absent => "OUTER-PRESENT-INNER-ABSENT" + } + Absent => "OUTER-ABSENT" + } +} + +// Positive control: the empty list is the one case the raw arm already got right, so a green here +// with the two reds below is what separates "the Optional is constructed" from "the arm refuses". +test fn first_of_an_empty_list_is_absent() -> Bool { + classify(xs: []) == "OUTER-ABSENT" +} + +// SHAPE 1, discriminating: red against `unwrap_or(Value::Null)`, which reported "OUTER-ABSENT". +test fn first_of_a_list_whose_head_is_an_absent_optional_is_present_of_absent() -> Bool { + classify(xs: [Absent]) == "OUTER-PRESENT-INNER-ABSENT" +} + +// Positive control for the same shape: a present head must still reach its payload, so the fix is +// not "wrap everything and lose the value". +test fn first_of_a_list_whose_head_is_a_present_optional_reaches_the_payload() -> Bool { + classify(xs: [Present { value: "x" }]) == "x" +} + +// SHAPE 2, discriminating: this is the exact comparison the BT-0 lane found returning false +// interpreted and true emitted. +test fn first_result_compares_equal_to_the_optional_the_roster_declares() -> Bool { + (["x"] |> filter(n => true) |> first) == Present { value: "x" } +} + +// SHAPE 2, the other direction. The raw arm made this TRUE -- the bare element compared equal to +// itself -- which is the same divergence read from the side where the interpreter was permissive +// rather than wrong-answered. +test fn first_result_does_not_compare_equal_to_the_bare_element() -> Bool { + ((["x"] |> filter(n => true) |> first) == Present { value: "x" }) + && !(match ["x"] |> filter(n => true) |> first { Present { value: v } => v == "y" Absent => true }) +} + +// `last` carries the identical roster row and had the identical raw arm. +test fn last_of_a_list_whose_tail_is_an_absent_optional_is_present_of_absent() -> Bool { + match [Present { value: "a" }, Absent] |> last { + Present { value: inner } => + match inner { + Present { value: _ } => false + Absent => true + } + Absent => false + } +} + +// `get` carries it too, and its absent case was the same or-Null read. +test fn get_past_the_end_is_absent_and_get_in_bounds_is_present() -> Bool { + (match ["a"] |> get(1) { Present { value: _ } => false Absent => true }) + && (match ["a"] |> get(0) { Present { value: s } => s == "a" Absent => false }) +} diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md new file mode 100644 index 00000000000..be8e261207f --- /dev/null +++ b/docs/plans/first-optional-divergence-census.md @@ -0,0 +1,214 @@ +# The `first` interpreter/emitted divergence: census, root cause, and why the repair is two-sided + +## The claim under census + +`dag/std/algebra.dag` declares five methods with `return_type: OptionalOf { inner: ReceiverElement }` +— `first`, `last`, `get`, `lookup`, `map_get`. That row is the single authority for their result +type, and the Rust emit arm realizes it: `extdeps/languages/rust/emit.dag`'s method template row for +`first` is `{recv}.first().cloned()`, an `Option`. + +The interpreter arm did not. `v1_interpreter`'s `method_call.first` computed +`items.front().cloned().unwrap_or(Value::Null)` — the RAW ELEMENT, or `Value::Null` when the +collection was empty. `method_call.last` and `method_call.get` and `method_call.lookup` were the +same shape; only `map_get` already constructed the Optional (through `map_lookup_as_optional`, whose +doc comment states the construction-not-validation rule this census re-derives from the other side). + +Two realizations of one declared signature that disagree are not a low rung on the §4b ladder. They +are DESIGN.md §5 silent wrongness — outside it. + +## What is measured, and by what + +Executed, not reasoned. A four-case probe run through `gunbc run` against `dag` + `src/v2` +source roots, on the seed as it stands on `main`: + +| probe | interpreted | what `dag/std/algebra.dag` declares | | +|---|---|---|---| +| `[] \|> first`, outer/inner match | `OUTER-ABSENT` | `OUTER-ABSENT` | agrees | +| `[Absent] \|> first`, outer/inner match | `OUTER-ABSENT` | `OUTER-PRESENT-INNER-ABSENT` | **diverges** | +| `[Present { value: "x" }] \|> first` | `x` | `x` | agrees (positive control) | +| `(["x"] \|> filter(n => true) \|> first) == Present { value: "x" }` | `false` | `true` | **diverges** | +| `(["x"] \|> filter(n => true) \|> first) == "x"` | `true` | not well-typed | **diverges** | + +Those five rows are now enrolled as executing evidence in +`dag/test/claim/first_optional_construction_witness_test.dag`, which is red against the raw-element +arm and green against the constructed Optional. The pre-existing +`dag/test/claim/branded_list_first_optional_witness_test.dag` is the regression control: it was +green BEFORE the repair and must stay green after. + +## Why the pre-existing witnesses were green on a real divergence + +`branded_list_first_optional_witness` eliminates `first` with +`match { Present { value: v } => .. Absent => .. }` over a NON-optional element type. The interpreter +carried compensating raw-unwrap arms in `match_pattern` — a `Present` pattern against a value that is +neither `Null` nor a `Variant` matches and binds the value itself — precisely so that shape would +agree. So the entire match-scrutinee population was green on a divergence it is structurally unable +to observe. **A witness whose RED is not authorable is a decoration**; for this class the +match-scrutinee shape is exactly that, and the two shapes in the table are the ones that are not. + +## The census — 186 terminal `|> first` sites, 81 files, at identity grain + +Population: every terminal `|> first` occurrence in `dag/**.dag` and `src/v2/**.dag` on `main` +(`git ls-files`, pipeline-terminal form). The parent lane reported 187 across 82 files; the extra +row is not on `main` and is most plausibly the known-red claim added on PR #9775's branch. The +count is reported for reconciliation only — **the roster below, not the count, is the deliverable.** + +The three shapes, and what each does with the result: + +- **S1 — eliminated by `match` (143 sites).** `match xs |> first { Present { value: v } => .. }`, + including the `let x = .. |> first` then `match x` spelling. The compensating `match_pattern` arms + make interpreter and emitted AGREE here, for every element type that is not itself an `Optional`. + These sites are not victims; they are the regression population the repair must not break. +- **S2 — returned directly as the enclosing function's `T?` (37 sites).** The divergence is + propagated, not resolved: the raw element leaves the function wearing the declared `Optional` + type. Each of these is a victim exactly when one of its callers is S3-shaped. +- **S3 — flows into a value position (6 sites).** Compared, passed as an argument, or returned + where a NON-optional type is declared. These are the sites where the two realizations produce + different answers on inputs the corpus can actually reach. + +The S2 and S3 rosters are below. S1 is not rostered individually: it is the complement, and its +membership test is mechanical (the occurrence is a `match` scrutinee). + +## The finding that changes the shape of the repair + +The census does not stop at `|> first`. The METHOD-CALL spelling `.first()` / `.last()` is a +separate and much larger population — 646 occurrences across 180 files in the same two trees — and +its dominant idiom is the value position, not the match: + + parse_int(s: fields.first()) + trim(tokens.skip(n: 1).first()) + percent(scalars.first()) + OpenBmcCollectionOne { value: values.first() } + fn cache_facts_for_id(..) -> CacheInterfaceFacts { catalog |> filter(..) |> first() } + +Every one of those passes an `Optional` into a position declared `T`. They work TODAY in both +realizations, for two different reasons: + +- **emitted**: `05_emit_rust`'s `rust_call_arg_fail_closed_unwrap` sees a `CardOptional` argument + meeting a required parameter and emits `.expect("fail-closed: an optional value flowed into + non-optional parameter N of F (empty Optional at runtime)")`. Typed, located, fail-closed. +- **interpreted**: nothing. There is no optional→required coercion in `v1_interpreter`'s argument + binding. It works only because `first` handed back the raw element in the first place. + +So the raw-element arm is not an isolated defect in one handler. **It is the compensation that the +interpreter's MISSING argument coercion has been leaning on**, corpus-wide. Repairing `first` alone +— making the interpreter construct the Optional its roster row declares — removes the compensation +without supplying what it was compensating for, and hundreds of value-position sites begin handing a +`Present { .. }` variant to `parse_int`, `trim`, `percent` and to record fields. That is a larger +silent wrongness than the one being fixed, in the same direction. + +That is measured, not argued. With only the `first`/`last`/`get`/`lookup` construction in place, +`bmc_capability_solve_witness_test`'s `firmware_wire_version_is_parsed_before_track_matching` — an +ordinary corpus witness that names nothing about optionals — flips from PASS to +`FAIL (runtime error [type-error]: type error: parse_int expects a string argument, got Variant)`, +while the same witness is green on the unmodified seed. It reaches +`parse_int(s: fields.first())` in `extdeps/bmc/capability.dag`. + +**The repair is therefore two-sided, and both sides derive from authorities that already exist:** + +1. `v1_interpreter` constructs the `Optional` for every algebra row that declares `OptionalOf`, + decided by call site rather than by value shape (the `RawMapLookup` rule, applied to the ordered + collections). One authority: the `AlgebraFieldTemplate` rows. +2. `v1_interpreter` gains the optional→required argument coercion the Rust emit arm already has, + decided by the callee's declared parameter cardinality — the same fact + `rust_call_arg_fail_closed_unwrap` reads — and refusing, typed and located, where the emitted arm + `.expect`s. One authority: the callee's parameter cardinality. + +Neither half is landable without the other, and that is executed rather than predicted: the +capability-solve witness above is the discriminating control, red on half the repair and green on +both halves and on neither. Landing (1) alone is the absorbing-fallback shape read +backwards: it converts a silent agreement into a silent disagreement across a population the +change does not name. + +## What blocks the second half, and why this lane stopped rather than improvised + +Side (2) is implemented in this branch for `.dag`-declared callees: `call_function_inner` binds an +argument, reads the callee parameter's declared type node, and — where that parameter is a required +value parameter — unwraps `Present` and REFUSES on `Absent`, typed and located, exactly where the +emitted arm `.expect`s. Measured: the new witness is 7/7 green, and +`branded_list_first_optional_witness` (the regression population) stays 8/8 green. + +It does not close the class, and the reason is a missing authority rather than a missing edit. +`parse_int(s: fields.first())` never reaches `call_function_inner`: `parse_int` is a BUILTIN, and +`04_method`'s `builtin_function_registry` maps a builtin name to a RETURN TYPE only — 04_sigs says +so in as many words. There is no declared parameter cardinality for a builtin anywhere the +interpreter (or anything else) can read, so the coercion cannot be DERIVED for a builtin call. The +capability-solve witness above stays red for that reason and no other. + +Deciding it by the argument's value shape instead — "if it looks like an `Optional`, unwrap it" — +is available and is the wrong answer twice: it is validation standing where construction was +available (§5), and it is precisely the value-shape inference `map_lookup_as_optional`'s own doc +comment refuses, because a stored `V = Optional` payload is then indistinguishable from a wrapped +result. So this lane stops here rather than shipping it. + +**The grounding this class is waiting on**: builtin PARAMETER signatures, modelled beside the +return type the registry already carries, so that argument cardinality is a fact the substrate +holds rather than a fact only the Rust emitter's static types happen to know. That is +model-before-implement work in `std/` ahead of any further pipeline edit, and it is a routing +decision, not something to improvise inside this repair. + +## Rung, ceiling, trigger + +- **Class**: `first_optional_representation_divergence` — a collection projection declared + `Optional` realized as a raw element in one arm and as `Option` in the other. +- **Rung found at**: below the ladder (silent wrongness). Both arms typecheck; neither warns. +- **Rung after part (1) + (2)**: mechanically preventable. The interpreter refuses when an algebra + arm's result does not inhabit the optionality its roster row declares, and refuses when an absent + optional reaches a required position. The invalid state stays writable — a hand-written arm in the + seed can still compute the wrong thing — so this is rung 2, not 3. +- **Attainable ceiling**: structurally impossible (rung 4), reached when the arm BODIES stop being + hand-written Rust in the seed and are projected from the same rows the emit arm reads. That is the + §7 self-host frontier for `v1_interpreter`, not a local edit. +- **Next-rung trigger**: the capability that lets an interpreter primitive's body be derived from + its `AlgebraFieldTemplate` row rather than authored beside it — the same `v1_interpreter` pure-eval + dissolution named on the `method_call.map_keys` arm. Not an artifact; the capability. + +## Roster + +### S2-returned-as-optional (37 sites) + +- `dag/extdeps/filesystem/linux.dag` · `linux_proc_mount_row_for_target` +- `dag/extdeps/git/object_store.dag` · `git_find_stored_object` +- `dag/extdeps/git/object_store.dag` · `git_find_unavailable` +- `dag/extdeps/languages/rust/emit.dag` · `rust_pair_completion_spelling_for` +- `dag/extdeps/languages/rust/representation.dag` · `rust_representation_realization_for` +- `dag/extdeps/llm/codex_auth.dag` · `codex_default_organization` +- `dag/extdeps/mercurial.dag` · `mercurial_first_cycle_node` +- `dag/extdeps/pijul.dag` · `pijul_delivery_dependency_issue` +- `dag/extdeps/pijul.dag` · `pijul_delivery_unavailable_issue` +- `dag/extdeps/pijul.dag` · `pijul_find_channel` +- `dag/extdeps/pijul.dag` · `pijul_missing_channel_member_issue` +- `dag/extdeps/pijul.dag` · `pijul_missing_conflict_change_issue` +- `dag/extdeps/pijul.dag` · `pijul_missing_context_issue` +- `dag/extdeps/pijul.dag` · `pijul_missing_dependency_issue` +- `dag/extdeps/pijul.dag` · `pijul_missing_tree_change_issue` +- `dag/extdeps/pijul.dag` · `pijul_present_change_declared_missing` +- `dag/extdeps/pijul.dag` · `pijul_present_path_declared_missing` +- `dag/extdeps/pijul.dag` · `pijul_present_vertex_declared_missing` +- `dag/extdeps/pijul.dag` · `pijul_self_dependency` +- `dag/extdeps/pijul.dag` · `pijul_state_identity_issue` +- `dag/gunbc/design/interaction.dag` · `detent_named` +- `dag/gunbc/design/material.dag` · `carrier_named` +- `dag/gunbc/design/state_response.dag` · `rest_member` +- `dag/gunbc/host/host_standup.dag` · `assimilation_obligation_for_input` +- `dag/gunbc/instruments/e0599_emitter_decision_census.dag` · `e0599_row_for_operation` +- `dag/gunbc/live_deploy/repository_convergence.dag` · `convergence_ref_at` +- `dag/gunbc/live_deploy/repository_convergence.dag` · `convergence_worktree_at` +- `dag/gunbc/namespace/namespace_clause_e_projection_law.dag` · `` +- `dag/gunbc/roadmap/roadmap_belt_actuate.dag` · `belt_dispatch_result_for_label` +- `dag/gunbc/roadmap/roadmap_closing_contract_authoring.dag` · `closing_contract_target_node` +- `dag/gunbc/roadmap/roadmap_execution_contract.dag` · `dispatch_host_realization` +- `dag/gunbc/roadmap/roadmap_style.dag` · `swatch_named` +- `dag/gunbc/roadmap/roadmap_validation_oracle.dag` · `validation_oracle_first_incomplete` +- `dag/std/target_representation.dag` · `checkpoint_row_migration_for` +- `dag/std/target_representation.dag` · `representation_spelling_for` +- `dag/test/claim/algebra_carrier_roster_witness_test.dag` · `ascii_least` +- `dag/test/claim/roadmap/roadmap_program_view_witness_test.dag` · `fx_line_view` + +### S3-value-position (6 sites) + +- `dag/gunbc/generated_artifact_observation.dag` · `observe_generated_artifact_with` +- `dag/std/cache_interface.dag` · `cache_facts_for_id` +- `dag/std/orthogonal_geometry.dag` · `any_nonadjacent_edges_touch` +- `dag/test/claim/build_latency_actions_collect_witness_test.dag` · `witness_population_fold_groups_by_host_and_filters_job_name` +- `dag/test/claim/build_latency_actions_collect_witness_test.dag` · `witness_population_fold_groups_by_host_and_filters_job_name` +- `dag/test/claim/guarantee_probe_corpus_witness_test.dag` · `witness_harness_revision` \ No newline at end of file diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 8621fbf2466..4e138d06421 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -687,6 +687,144 @@ fn optional_absent(ctx: &InterpContext) -> Value { } } +/// Whether a value already carries the `Optional` contract, i.e. is one of the two constructors +/// `optional_present`/`optional_absent` build. Used only to REFUSE a disagreeing arm — never to +/// decide whether to wrap, which is a call-site fact (see `RawMapLookup`). +fn is_optional_value(v: &Value, ctx: &InterpContext) -> bool { + match v { + Value::Variant { + type_name, + variant_name, + .. + } => { + *type_name == ctx.sym("Optional") + && (*variant_name == ctx.sym("Present") || *variant_name == ctx.sym("Absent")) + } + _ => false, + } +} + +/// ONE AUTHORITY FOR `Optional`-VALUEDNESS: the `AlgebraFieldTemplate` rows projected from +/// `dag/std/algebra.dag`. `first`/`last`/`get`/`lookup`/`map_get` each declare +/// `return_type: OptionalOf { inner: ReceiverElement }` there, and that row is what makes the Rust +/// emit arm produce `Option`. Before this function existed the interpreter arms answered the +/// same question by hand and answered it differently — `first` returned the RAW element (or +/// `Value::Null`), so `xs |> first == Present { value: x }` was false interpreted and true emitted: +/// DESIGN.md §5 silent wrongness, which is outside the ladder rather than low on it. Both arms now +/// read this row, so a single definition cannot disagree with itself. +/// +/// `None` = the roster has no row for this spelling (not an algebra method, or a free-call-only +/// builtin); `Some(false)` = declared non-optional. A spelling whose rows DISAGREE is refused by +/// the caller rather than resolved by majority — a mixed roster is a modelling defect, not an input. +fn algebra_row_returns_optional(method: &str) -> Option> { + let mut optional = false; + let mut plain = false; + for t in crate::std_algebra::all_algebra_field_templates().iter() { + if t.name != method { + continue; + } + if matches!( + *t.return_type, + crate::std_algebra::AlgebraTypeTemplate::OptionalOf { .. } + ) { + optional = true; + } else { + plain = true; + } + } + match (optional, plain) { + (false, false) => None, + (true, true) => Some(Err(())), + (o, _) => Some(Ok(o)), + } +} + +/// What an argument value is, with respect to the `Optional` contract. +enum OptionalArg { + NotOptional(Value), + Present(Value), + Absent, +} + +fn classify_optional_arg(val: Value, ctx: &InterpContext) -> OptionalArg { + match &val { + Value::Variant { + type_name, + variant_name, + fields, + } if *type_name == ctx.sym("Optional") => { + if *variant_name == ctx.sym("Present") { + OptionalArg::Present( + fields_get(fields, ctx.sym("value")) + .cloned() + .unwrap_or(Value::Null), + ) + } else if *variant_name == ctx.sym("Absent") { + OptionalArg::Absent + } else { + OptionalArg::NotOptional(val) + } + } + _ => OptionalArg::NotOptional(val), + } +} + +/// Whether a declared parameter is a VALUE parameter whose type is required (not `T?`, and not an +/// `Optional` carrier spelled without the cardinality flag). The two spellings name one carrier, +/// so the predicate asks both — the same pairing `05_emit_rust`'s +/// `rust_call_arg_fail_closed_unwrap` makes, and for the same reason: unwrapping into +/// `witness_from_optional(opt: Optional)` would strip the very value the callee exists to +/// inspect. +fn param_declares_required_value(param: &Rc, pname: &str, ctx: &InterpContext) -> bool { + match param.children.first() { + Some(type_expr) => { + let type_name = authored_name_at(ctx.si(), type_expr.clone()); + type_name != pname + && type_expr.return_cardinality != Cardinality::CardOptional + && type_name != "Optional" + } + None => false, + } +} + +/// The wall that keeps the two realizations from drifting apart again: whatever an algebra arm +/// computes, the value it hands back must inhabit the return type its roster row declares. An arm +/// that reverts to the raw element refuses here, loudly and located by method name, instead of +/// silently producing a value the emitted mirror would never produce. +fn algebra_result_matches_declared_optionality( + method: &str, + value: Value, + ctx: &InterpContext, +) -> InterpResult { + match algebra_row_returns_optional(method) { + None => Ok(value), + Some(Err(())) => Err(InterpError::TypeError { + msg: format!( + "algebra roster disagrees with itself about `{}`: some rows declare \ + `OptionalOf` and some do not, so the interpreter cannot derive the method's \ + optionality from dag/std/algebra.dag", + method + ), + }), + Some(Ok(false)) => Ok(value), + Some(Ok(true)) => { + if is_optional_value(&value, ctx) { + Ok(value) + } else { + Err(InterpError::TypeError { + msg: format!( + "interpreter arm for `{}` produced {} where dag/std/algebra.dag declares \ + `Optional<..>`; the emitted realization returns an Optional here, so \ + returning the bare value would be a silent semantic divergence", + method, + value.type_label() + ), + }) + } + } + } +} + /// Whether a `raw_map_lookup` result already carries the `Optional` contract (a /// `.dag`-authored `Map.lookup` closure returns `Optional` by construction) or is a bare /// storage read still needing the wrap (native `Value::Map`/field storage, miss = `Value::Null`). @@ -4411,6 +4549,43 @@ fn call_function_inner( } } + // OPTIONAL INTO A REQUIRED PARAMETER — the interpreted half of a rule the emitted half already + // had. `05_emit_rust`'s `rust_call_arg_fail_closed_unwrap` sees an `Optional` argument meet a + // parameter declared `T` and emits + // `.expect("fail-closed: an optional value flowed into non-optional parameter N of F ..")`. + // The interpreter had NO such coercion, and did not need one only because `first`/`last`/`get` + // handed back the RAW element instead of the `Optional` their `dag/std/algebra.dag` rows + // declare. Constructing that Optional without supplying this is the same silent wrongness + // pointed the other way: `parse_int(s: fields.first())` would receive a `Present { .. }` + // variant. Present unwraps; ABSENT STOPS THE LINE, typed and located, where the emitted arm + // panics. + for (i, param) in fn_node.params.iter().enumerate() { + let pname = &all_param_names[i]; + if !param_declares_required_value(param, pname, ctx) { + continue; + } + let key = ctx.sym(pname); + let Some(val) = bindings.get(&key).cloned() else { + continue; + }; + match classify_optional_arg(val, ctx) { + OptionalArg::NotOptional(_) => {} + OptionalArg::Present(inner) => { + bindings.insert(key, inner); + } + OptionalArg::Absent => { + return Err(InterpError::CallContractMismatch { + callee: fn_node.name.clone(), + detail: format!( + "an optional value flowed into non-optional parameter {} ('{}') \ + (empty Optional at runtime)", + i, pname + ), + }) + } + } + } + let caller_label_matches_param = |param_name: &str, arg_label: &str| { param_name == arg_label || param_name == "_" @@ -7689,8 +7864,38 @@ fn eval_field_access( Ok(items) => Ok(items.get(1).cloned().unwrap_or(Value::Null)), Err(_) => extract_field(&base_val, &field_name, env, ctx), }, + // `opt.value` is `FieldAccessStyle::OptionalUnwrap`, decided once in 04_lookup's + // `field_summary_for_type`. The emitted mirror realizes that row as `.unwrap()`, so the + // absent case must STOP in both realizations; returning `Value::Null` here let an absent + // optional flow on as a plausible value with no diagnostic (DESIGN.md §5). The + // `Present { value: .. }` arm is what keeps the 176 emitted unwrap sites reading the + // payload now that `first`/`last`/`get`/`lookup` construct a real Optional. Some(FieldAccessStyle::OptionalUnwrap) => match &base_val { - Value::Null => Ok(Value::Null), + Value::Variant { + type_name, + variant_name, + fields, + } if *type_name == ctx.sym("Optional") && *variant_name == ctx.sym("Present") => { + Ok(fields_get(fields, ctx.sym("value")) + .cloned() + .unwrap_or(Value::Null)) + } + Value::Variant { + type_name, + variant_name, + .. + } if *type_name == ctx.sym("Optional") && *variant_name == ctx.sym("Absent") => { + Err(InterpError::TypeError { + msg: "`.value` projected an absent Optional; there is no value to read \ + (the emitted realization panics on the same read)" + .to_string(), + }) + } + Value::Null => Err(InterpError::TypeError { + msg: "`.value` projected an absent Optional carried in the raw value-or-Null \ + representation; there is no value to read" + .to_string(), + }), _ => Ok(base_val), }, Some(FieldAccessStyle::EnumAccessor) => extract_field(&base_val, &field_name, env, ctx), @@ -8375,7 +8580,8 @@ macro_rules! v1_algebra_method_arms { let key = $args.first().ok_or_else(|| InterpError::TypeError { msg: "lookup requires a key argument".to_string(), })?; - raw_map_lookup(&$receiver, key, $env, $ctx).map(RawMapLookup::into_raw) + let raw = raw_map_lookup(&$receiver, key, $env, $ctx)?; + Ok(map_lookup_as_optional(raw, $ctx)) }, arm "method_call.map" { "map" } => list_method_with_closure("map", $receiver, $args, $env, $ctx, |items, f, $env, $ctx| { @@ -8595,14 +8801,25 @@ macro_rules! v1_algebra_method_arms { }, }, + // `first`/`last` are declared `OptionalOf { inner: ReceiverElement }` in + // dag/std/algebra.dag. The Optional is CONSTRUCTED here, decided by the call site + // (empty vs non-empty), never inferred from the element's shape — a stored + // `Element = Optional` head must come back as `Present { value: Absent }`, which is + // exactly the case the old raw `unwrap_or(Value::Null)` collapsed into `Absent`. arm "method_call.first" { "first" } => { let items = expect_list(&$receiver, "first")?; - Ok(items.front().cloned().unwrap_or(Value::Null)) + Ok(match items.front().cloned() { + Some(v) => optional_present(v, $ctx), + None => optional_absent($ctx), + }) }, arm "method_call.last" { "last" } => { let items = expect_list(&$receiver, "last")?; - Ok(items.last().cloned().unwrap_or(Value::Null)) + Ok(match items.last().cloned() { + Some(v) => optional_present(v, $ctx), + None => optional_absent($ctx), + }) }, arm "method_call.reverse" { "reverse" } => { @@ -8693,20 +8910,21 @@ macro_rules! v1_algebra_method_arms { Ok(map_lookup_as_optional(raw, $ctx)) }, + // Same roster row as `first`/`last` (`get: OptionalOf { inner: ReceiverElement }`), + // so every branch constructs the Optional rather than leaking the raw storage read. arm "method_call.get" { "get" } => { - if matches!(&$receiver, Value::Str(_)) { - let key = $args.first().ok_or_else(|| InterpError::TypeError { - msg: "get requires a key argument".to_string(), - })?; - raw_map_lookup(&$receiver, key, $env, $ctx).map(RawMapLookup::into_raw) - } else if let Ok(items) = expect_list(&$receiver, "get") { + if let Ok(items) = expect_list(&$receiver, "get") { let idx = expect_int($args.first(), "get")?; - Ok(list_get_at_or_null(&items, idx)) + Ok(match list_index_in_bounds(&items, idx) { + Some(v) => optional_present(v, $ctx), + None => optional_absent($ctx), + }) } else { let key = $args.first().ok_or_else(|| InterpError::TypeError { msg: "get requires a key argument".to_string(), })?; - raw_map_lookup(&$receiver, key, $env, $ctx).map(RawMapLookup::into_raw) + let raw = raw_map_lookup(&$receiver, key, $env, $ctx)?; + Ok(map_lookup_as_optional(raw, $ctx)) } }, @@ -8902,7 +9120,9 @@ fn eval_algebra_method_inner( env: &Rc, ctx: &InterpContext, ) -> InterpResult { - v1_algebra_method_arms!(v1_algebra_dispatch, method, receiver, args, env, ctx) + let produced: InterpResult = + v1_algebra_method_arms!(v1_algebra_dispatch, method, receiver, args, env, ctx); + algebra_result_matches_declared_optionality(method, produced?, ctx) } pub fn fixture_now_secs(ctx: &InterpContext) -> Result { @@ -14536,7 +14756,10 @@ macro_rules! v1_builtin_arms { [list_val, idx_val] if free_monoid_to_vec(list_val).is_some() => { let items = expect_list(list_val, "get")?; let idx = expect_int(Some(idx_val), "get")?; - Ok(Some(list_get_at_or_null(&items, idx))) + Ok(Some(match list_index_in_bounds(&items, idx) { + Some(v) => optional_present(v, $ctx), + None => optional_absent($ctx), + })) } _ => Ok(None), }, @@ -16687,8 +16910,14 @@ fn value_to_list_carrier(val: &Value) -> Option<(Rc>, u64)> { } } -fn list_get_at_or_null(items: &RrbVector, idx: i64) -> Value { - items.get(idx as usize).cloned().unwrap_or(Value::Null) +/// In-bounds read, with the ABSENCE left for the caller to construct as an `Optional`. The +/// or-Null form this replaced could not distinguish an out-of-range index from a stored +/// `Value::Null`, which is the same collapse `first` used to make. +fn list_index_in_bounds(items: &RrbVector, idx: i64) -> Option { + if idx < 0 { + return None; + } + items.get(idx as usize).cloned() } fn expect_list(val: &Value, context: &str) -> InterpResult>> { From eccd062096d3538807ff9298abaf00b208fbf7e9 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 31 Aug 2026 02:40:30 +0000 Subject: [PATCH 02/28] Census: report a disposition per site, not a shape tally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parent lane's read of the first draft is right — a shape says where the value goes, only a disposition says whether the two realizations answer differently on an input the corpus can reach, and this document is about to be cited instead of re-derived. 186 occurrences resolve to 181 real sites over 81 files plus 5 non-sites (4 inside `//` annotations, 1 inside a string literal carrying a probe program), which reconciles exactly with the parent's 187/82 as that count minus #9775's own known-red row. Dispositions, each measured rather than asserted: - AgreesUnderCompensation, 142. Its failure condition is an element type that is itself `Optional`, and the corpus declares five list-of-optional carriers in total, all in witness tests, none reaching a `first`. Zero harmed today — which is exactly why the class stayed invisible: the shape that dominates the corpus is the one the compensation covers. - Propagates, 36. Resolved one level out by following all 36 functions to their call sites: 72 callers eliminate by `match`, 2 tail-propagate into another `T?`, and 4 compare `== none`, which agrees only because a miss is `Null` on one side and `Absent` on the other and both compare equal to that one constructor. Zero harmed today, by a margin one constructor wide. - HarmedNow, 3, listed in full: `cache_facts_for_id` declaring `-> CacheInterfaceFacts` over a `first()` (with `cache_layer_plan_primary`/`_fallback` as sibling defects in the same module), and two `measure_count(m: .. |> first)` argument sites that agree while non-empty and diverge on empty. Three of 181 read alone argues the class is not worth repairing. It is the wrong denominator, and the method-spelling section already says why. Both filters over that population are now named beside their producers — 646/180 here, 655/178 by the parent's independent filter — because they disagree, and a disagreement is the reason to cite the producer rather than the figure. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK --- .../plans/first-optional-divergence-census.md | 96 ++++++++++++------- 1 file changed, 61 insertions(+), 35 deletions(-) diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md index be8e261207f..2645a41e1cf 100644 --- a/docs/plans/first-optional-divergence-census.md +++ b/docs/plans/first-optional-divergence-census.md @@ -45,34 +45,66 @@ agree. So the entire match-scrutinee population was green on a divergence it is to observe. **A witness whose RED is not authorable is a decoration**; for this class the match-scrutinee shape is exactly that, and the two shapes in the table are the ones that are not. -## The census — 186 terminal `|> first` sites, 81 files, at identity grain - -Population: every terminal `|> first` occurrence in `dag/**.dag` and `src/v2/**.dag` on `main` -(`git ls-files`, pipeline-terminal form). The parent lane reported 187 across 82 files; the extra -row is not on `main` and is most plausibly the known-red claim added on PR #9775's branch. The -count is reported for reconciliation only — **the roster below, not the count, is the deliverable.** - -The three shapes, and what each does with the result: - -- **S1 — eliminated by `match` (143 sites).** `match xs |> first { Present { value: v } => .. }`, - including the `let x = .. |> first` then `match x` spelling. The compensating `match_pattern` arms - make interpreter and emitted AGREE here, for every element type that is not itself an `Optional`. - These sites are not victims; they are the regression population the repair must not break. -- **S2 — returned directly as the enclosing function's `T?` (37 sites).** The divergence is - propagated, not resolved: the raw element leaves the function wearing the declared `Optional` - type. Each of these is a victim exactly when one of its callers is S3-shaped. -- **S3 — flows into a value position (6 sites).** Compared, passed as an argument, or returned - where a NON-optional type is declared. These are the sites where the two realizations produce - different answers on inputs the corpus can actually reach. - -The S2 and S3 rosters are below. S1 is not rostered individually: it is the complement, and its -membership test is mechanical (the occurrence is a `match` scrutinee). +## The census — a disposition roster, not a shape tally + +Population: every terminal `|> first` occurrence in `dag/**.dag` and `src/v2/**.dag` on `main`. +186 occurrences resolve to **181 real sites over 81 files**, plus 5 that are not sites at all +(4 inside `//` annotations, 1 inside a string literal that carries a probe program). The parent lane +measured 187 across 82; that reconciles exactly as 186/81 plus PR #9775's own known-red row, which +is not on `main`. **Cite this census as the producer of the roster; the count is not the +deliverable and should not be transcribed.** + +Each site carries a DISPOSITION — whether the divergence actually harms it — not merely a shape. +A shape says where the value goes; only the disposition says whether the two realizations answer +differently on an input the corpus can reach. + +| disposition | sites | what it means | +|---|---|---| +| `AgreesUnderCompensation` | 142 | eliminated by `match`. The interpreter's raw-unwrap arms in `match_pattern` bind a `Present { value: v }` pattern to any value that is neither `Null` nor a `Present`/`Absent` variant, so interpreter and emitted agree for **every element type except `Optional` itself**. | +| `Propagates` | 36 | returned onward as the enclosing function's declared `T?`. The declared type is honest; only its REPRESENTATION differs, so the disposition is the caller's. | +| `HarmedNow` | 3 | the value reaches a position that reads the representation directly. | +| `NotASite` | 5 | annotation or string-literal text. | + +**`AgreesUnderCompensation` is a measured disposition, not an assumption.** Its failure condition is +an element type that is itself `Optional`, and the corpus declares exactly five list-of-optional +carriers in total (`List` / `List>`), all in witness tests, none of them reaching a +`first`. So **zero** of the 142 are harmed today. That is precisely why this class stayed invisible: +the shape that dominates the corpus is the one shape the compensation covers. + +**`Propagates` resolves the same way, one level out.** Following all 36 functions to their call +sites: 72 callers eliminate by `match` (unharmed), 2 tail-propagate into another `T?` +(`mercurial_first_changeset_cycle` / `_file_revision_cycle`), and 4 compare `== none` +(`rust_representation_realization_for` in `self_host_symbol_identity_binding_witness_test`) — which +AGREES, because a miss is `Null` on one side and `Absent` on the other and both compare equal to +`none`. It agrees by the representation happening to line up on that one constructor: the same site +spelled `== Present { value: .. }` is the parent lane's discriminator and diverges. **Zero harmed +today, and the margin is one constructor wide.** + +`HarmedNow`, in full — this is the whole victim list for the pipeline spelling: + +- `dag/std/cache_interface.dag` · `cache_facts_for_id` — declares `-> CacheInterfaceFacts` and + returns `catalog |> filter(..) |> first()`, an `Optional`. Its three callers + (`cache_reach_candidate_probe`, `cache_layer_cost_justified`, + `cache_layer_ids_respect_locality`) then read `.locality` and pass it to `read_latency_cost`. + Sibling defect in the same module: `cache_layer_plan_primary` / `cache_layer_plan_fallback` both + declare `-> CacheInterfaceId` over `.first()`. +- `dag/test/claim/build_latency_actions_collect_witness_test.dag` · + `witness_population_fold_groups_by_host_and_filters_job_name`, twice — + `measure_count(m: srv1.durations |> skip(n: 0) |> first)` passes an `Optional` into a required + parameter. The two realizations agree while the list is non-empty and diverge on empty, where the + emitted arm's `.expect(..)` stops and the interpreter carries `Value::Null` onward. + +**Three of 181.** Read alone that number argues the class is not worth repairing. It is the wrong +denominator, and the next section is why. ## The finding that changes the shape of the repair The census does not stop at `|> first`. The METHOD-CALL spelling `.first()` / `.last()` is a -separate and much larger population — 646 occurrences across 180 files in the same two trees — and -its dominant idiom is the value position, not the match: +separate and much larger population — 646 occurrences across 180 files in the same two trees by this +census's filter, 655 across 178 by the parent lane's independent one. Same magnitude, different +filter boundary; neither number should be transcribed, and the disagreement is itself the reason to +name the producer rather than the figure. Its +dominant idiom is the value position, not the match: parse_int(s: fields.first()) trim(tokens.skip(n: 1).first()) @@ -164,7 +196,11 @@ decision, not something to improvise inside this repair. ## Roster -### S2-returned-as-optional (37 sites) +`HarmedNow` and `NotASite` are listed in full above. `Propagates` (36 sites) is rostered here at +identity grain; `AgreesUnderCompensation` (142) is the complement and its membership test is +mechanical — the occurrence is a `match` scrutinee, directly or through a `let` bound one line up. + +### Propagates — returned onward as the enclosing function's `T?` - `dag/extdeps/filesystem/linux.dag` · `linux_proc_mount_row_for_target` - `dag/extdeps/git/object_store.dag` · `git_find_stored_object` @@ -193,7 +229,6 @@ decision, not something to improvise inside this repair. - `dag/gunbc/instruments/e0599_emitter_decision_census.dag` · `e0599_row_for_operation` - `dag/gunbc/live_deploy/repository_convergence.dag` · `convergence_ref_at` - `dag/gunbc/live_deploy/repository_convergence.dag` · `convergence_worktree_at` -- `dag/gunbc/namespace/namespace_clause_e_projection_law.dag` · `` - `dag/gunbc/roadmap/roadmap_belt_actuate.dag` · `belt_dispatch_result_for_label` - `dag/gunbc/roadmap/roadmap_closing_contract_authoring.dag` · `closing_contract_target_node` - `dag/gunbc/roadmap/roadmap_execution_contract.dag` · `dispatch_host_realization` @@ -203,12 +238,3 @@ decision, not something to improvise inside this repair. - `dag/std/target_representation.dag` · `representation_spelling_for` - `dag/test/claim/algebra_carrier_roster_witness_test.dag` · `ascii_least` - `dag/test/claim/roadmap/roadmap_program_view_witness_test.dag` · `fx_line_view` - -### S3-value-position (6 sites) - -- `dag/gunbc/generated_artifact_observation.dag` · `observe_generated_artifact_with` -- `dag/std/cache_interface.dag` · `cache_facts_for_id` -- `dag/std/orthogonal_geometry.dag` · `any_nonadjacent_edges_touch` -- `dag/test/claim/build_latency_actions_collect_witness_test.dag` · `witness_population_fold_groups_by_host_and_filters_job_name` -- `dag/test/claim/build_latency_actions_collect_witness_test.dag` · `witness_population_fold_groups_by_host_and_filters_job_name` -- `dag/test/claim/guarantee_probe_corpus_witness_test.dag` · `witness_harness_revision` \ No newline at end of file From 95079cb54ba13f9981cb485cd5f1e5b047285c01 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 31 Aug 2026 02:44:47 +0000 Subject: [PATCH 03/28] =?UTF-8?q?Census:=20step=201=20re-scoped=20?= =?UTF-8?q?=E2=80=94=20the=20signature=20carrier=20exists;=20the=20gap=20i?= =?UTF-8?q?s=20its=20denominator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reading std/ before authoring turned the routed step-1 task into a different one, and the difference is the second time on this class that the obvious repair was the wrong one. dag/std/primitive_identity.dag ALREADY models the callable-signature grounding and already executes: PrimitiveSignatureGrounding, PrimitiveSemanticContract, PrimitiveSignatureResolution (SignatureResolved carries `parameters: List`), primitive_signature and primitive_arity, green in primitive_signature_grounding_witness_test. Its own doc comment refuses the fork this lane was about to commit -- "the contract carries a KEY into the one authority, never its contents" -- and it keys on (canonical_name, profile) rather than name alone precisely because `get` reads differently on the List and Map profiles. So there is no carrier to mint, and authoring one would have been the section 3 nickname the routing message warned about. The gap is coverage, and it is measured: of builtin_function_registry's 131 names, 20 resolve through primitive_signature and 111 answer SignatureNotGrounded. parse_int -- the name that reds the corpus control -- is one of the 111. The 111 are two populations and nothing separates them: language primitives that should carry a signature, and host/lens transports whose parameter shape is a Realization fact and belongs with the transport. The obvious discriminator fails, measured rather than assumed: gunbc.v1_interpreter_primitive_surface enumerates an arm for both by construction, so joining on it classifies doc_graph_orphan_count and parse_int identically. Splitting on a naming convention would be the smuggled heuristic section 5 names, so this lane raises the question instead of picking. Also recorded, independent of this class: the 20 overlapping names are two authorities that already DISAGREE. The registry is receiver-blind -- `reverse` types as List where algebra says ReceiverSelf (so a String reverse), `map_keys` and `map_values` share one element type variable where algebra distinguishes ReceiverKey from ReceiverValue, `concat` types as String where algebra says ReceiverSelf. Same fork as the first() divergence, one layer up: not two realizations of one declaration, but two declarations of one operation. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK --- .../plans/first-optional-divergence-census.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md index 2645a41e1cf..28bbc72828b 100644 --- a/docs/plans/first-optional-divergence-census.md +++ b/docs/plans/first-optional-divergence-census.md @@ -178,6 +178,64 @@ holds rather than a fact only the Rust emitter's static types happen to know. Th model-before-implement work in `std/` ahead of any further pipeline edit, and it is a routing decision, not something to improvise inside this repair. +## Step 1 re-scoped: the signature carrier already exists, and the gap is its denominator + +The routing decision on this lane was to model builtin parameter signatures in `std/` first. Reading +`std/` before authoring turned that into a different task, and the difference is worth recording +because it is the second time on this class that the obvious repair was the wrong one. + +**`dag/std/primitive_identity.dag` already models it, and already executes.** It carries +`PrimitiveSignatureGrounding`, `PrimitiveSemanticContract`, `PrimitiveSignatureResolution` (whose +`SignatureResolved` arm is `{ parameters: List, result: AlgebraTypeTemplate }`), +`primitive_signature`, and `primitive_arity`, green by execution in +`dag/test/claim/primitive_signature_grounding_witness_test.dag`. Its own doc comment refuses the fork +this lane was about to commit, in as many words: *the contract carries a KEY into the one authority, +never its contents.* It even keys the lookup on `(canonical_name, profile)` rather than name alone, +precisely because `get` is `[ReceiverSelf, NamedTemplate { name: "Int" }]` on the List profile and +`[ReceiverSelf, ReceiverKey]` on the Map-shaped ones. + +So there is no new carrier to mint, and authoring one would have been exactly the §3 nickname. +**What is missing is coverage, and it is measurable**: of `builtin_function_registry`'s 131 names, +20 have an `AlgebraFieldTemplate` row and resolve through `primitive_signature`; the other 111 +answer `SignatureNotGrounded`. `parse_int` — the name that reds the corpus control — is one of the +111. + +**The 111 are two populations, and nothing in the substrate separates them.** Some are language +primitives that should carry a signature (`parse_int`, `char_at`, `code_point`, `chars_to_string`, +`string_length`, `string_contains`, `scan_while`, `scan_to_eol`, `set_insert`, `set_union`, +`sorted_map_keys`, `hash_combine`). Most are host or lens transports whose parameter shape is a +Realization fact and not a language one (`doc_graph_orphan_count`, `fallback_arm_census_facts`, +`emit_host_run_transport`, `non_fold_residue_count`, `witness_layer_roots_compile_clean_check`) — +§3 puts those with their transport, not in the language's primitive surface. + +The obvious discriminator does not discriminate. `gunbc.v1_interpreter_primitive_surface` enumerates +an arm for BOTH populations by construction, so joining on it classifies `doc_graph_orphan_count` +and `parse_int` identically — measured, not assumed. Splitting them on a naming convention instead +would be the smuggled heuristic §5 names, so this lane does not. + +**The step-1 modeling question is therefore not "what shape does a builtin signature have" — that is +answered — but "what closes the language-primitive population", i.e. the denominator over which a +signature is obligatory.** That is a routing decision, and this lane has raised it rather than +picking one. + +## The registry and the algebra rows already disagree + +Independent of the `first` class, and worth its own row wherever primitive-surface debt is tracked: +the 20 overlapping names are two authorities for one operation's type, and they do not agree today. +`builtin_function_registry` is receiver-BLIND, so it collapses distinctions the algebra rows carry: + +- `reverse` — registry `List`; algebra `ReceiverSelf` on both a scalar and a + collection profile. On a `String` receiver the two answer with different types. +- `map_keys` and `map_values` — registry gives both `List`, one type variable + for two different element positions; algebra gives `ContainerOf { .., element: ReceiverKey }` and + `.. ReceiverValue`. For any `Map` with `K /= V` the registry cannot be right about both. +- `concat` — registry `String`; algebra `ReceiverSelf`, so a list concat types as a String. +- `get` — registry one `Optional`; algebra distinguishes the List reading from + the Map reading. + +Every one of these is the same §3 fork as the `first` divergence, one layer up: not two +realizations of one declaration, but two declarations of one operation. + ## Rung, ceiling, trigger - **Class**: `first_optional_representation_divergence` — a collection projection declared From 32606e433e63996816fc4c4e5c835e08d99b2474 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 31 Aug 2026 04:20:54 +0000 Subject: [PATCH 04/28] Census: enumerate the 442 from the artifact, and record that this class is Phase B of an open lane THE NUMBER, CORRECTED AND THEN ENUMERATED. An earlier revision reported the partial repair as flipping ONE witness. That was a two-file claim_batch sample reported as a corpus bound, and worse: the witness it named, bmc_capability firmware_wire_version_is_parsed_before_track_matching, has disposition declined_outside_gate_closure / not_executed, so the floor never runs it. The sample was drawn from outside the population the floor measures. The floor reports failed=442 of 3141. The job log prints only six per-claim lines, which reads as truncation and is not -- the required-floor-disposition ARTIFACT separates the outcomes the summary folds: 442 runtime-errored-before-verdict, 6 failed (assertion), 1 budget-refused, 47 route-gap, 15 known-red-held. The 442 errored before reaching a verdict; they did not assert and fail. ALL 442 ARE v2.test.* and none is a dag/test/claim witness. The v2 compiler is .dag interpreted by the v1 seed, so changing the interpreter's projections changes v2's own behaviour as it runs. The blast radius is the interpreted v2 compiler, which is a different shape from the value-position argument sites this census predicted. A MECHANISM CORRECTION, which matters more than the verdict it supported. This document said the four `== none` sites agree "because a miss is Null on one side and Absent on the other and both compare equal to none". Wrong: in the interpreter `none` EVALUATES TO Value::Null, so the raw side compares equal because it IS Null, and a constructed Absent variant does not compare equal at all. Those sites agree BEFORE the construction and break after it; two are among the six assertion failures. The verdict was right about the pre-change state by the wrong route, and the wrong route is what hid the none-literal migration from the first draft. THE CLASS ALREADY HAS AN AUTHORED PROGRAM. gunbc.plans.value_null_split (lane keen-ferret-250) models Value::Null's four overloaded meanings and phases the repair A-E. This branch is its Phase B, built without knowing the plan existed. Its Phase-A witness predicted this branch's failure BY NAME: "raw_get_miss_differs_from_optional_absent .. flips RED in Phase B when get+Optional routes through map_lookup_as_optional" -- and it is one of the six. That is the enrolled signal Phase B landed, not a defect. Section 0 of that plan also pre-refutes a blanket cross-representation equality guard, because present == None -> false is legitimate at ~218 sites. So the completion has THREE gates: the argument coercion (needs the primitive denominator), Phase D's none-literal migration over ~218 sites in 66 files, and Phase C's bridge deletion. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK --- dag/gunbc/recurring_failure_mode.dag | 7 + .../builtin-registry-population-partition.md | 211 ++++++++++++++++++ .../plans/first-optional-divergence-census.md | 106 ++++++++- floor_probe.sh | 12 + 4 files changed, 331 insertions(+), 5 deletions(-) create mode 100644 docs/plans/builtin-registry-population-partition.md create mode 100644 floor_probe.sh diff --git a/dag/gunbc/recurring_failure_mode.dag b/dag/gunbc/recurring_failure_mode.dag index f2602d9e8b5..f5a5439b503 100644 --- a/dag/gunbc/recurring_failure_mode.dag +++ b/dag/gunbc/recurring_failure_mode.dag @@ -168,6 +168,12 @@ data identity_absent_graph_traversal: RecurringFailureMode = RecurringFailureMod evidence: [], } +data coarser_parallel_authority: RecurringFailureMode = RecurringFailureMode { + identity: "coarser_parallel_authority" as NonEmptyStr, + authored: "**coarser parallel authority** (two carriers are AUTHORED for one fact and one of them is LOSSIER, so the fork never presents as duplication — it presents as abstraction. §3 already forbids two authorities for one fact; what this row adds is the reason that rule keeps being read past, because the second carrier does not look like a copy. A coarse answer and a fine answer never contradict each other in the way two copies do: the coarse one reads as *less specific*, which a reviewer accepts as a summary, so the disagreement is invisible until an input distinguishes the collapsed cases. RECEIPT (measured on main while censusing the `first` interpreter/emitted divergence, gunbc#9785): `v1.compiler.infer_method` `builtin_function_registry` maps a primitive name to a RETURN TYPE, and `std.algebra` `AlgebraFieldTemplate` carries `param_types` and `return_type` for the same operations. 20 of the registry's names also have algebra rows, and the two already answer differently, because the registry is RECEIVER-BLIND where the algebra rows are receiver-relative: `reverse` is `List` against `ReceiverSelf`, so on a `String` receiver the two carriers name different types; `map_keys` and `map_values` share ONE element type variable where algebra distinguishes `ReceiverKey` from `ReceiverValue`, so for any `Map` with `K` /= `V` the registry cannot be right about both; `concat` is `String` against `ReceiverSelf`, so a list concat types as a String; `get` collapses the List reading and the Map reading the algebra rows keep apart. None of these is a stale copy that drifted — the registry was authored coarse and has been coarse the whole time. **WHAT MAKES THE COARSE CARRIER LOOK LEGITIMATE is that its coarseness is usually true of the QUESTION ITS FIRST CONSUMER ASKED.** A caller that only wants \"is this name a builtin\" is well served by a name-to-return-type map, and the carrier is correct for that consumer on the day it lands. It becomes an authority for a fact it never modelled the moment a second consumer asks a finer question of it, and it answers, because a map is total over its keys. **RECOGNITION RULE: when two carriers answer about one operation and one is coarser, ask whether the coarse answer is DERIVED from the fine one or AUTHORED beside it. A derived projection cannot disagree; an authored one is a fork whose disagreements are silent by construction, since \"less specific\" and \"different\" are indistinguishable at the call site.** The repair is never to reconcile the two rosters — that is a second synchronisation obligation, and §5 calls a check standing where construction was available validation. The repair is RELOCATION: the coarse carrier stops being an authority and becomes a projection of the fine one, or its rows leave for the layer that actually owns them. Here the same relocation dissolves a second class: once the registry is no longer a signature authority for anything `std.algebra` owns, there is no second declaration left to disagree, and the population that remains — primitives with no algebra row — is a closeable gap rather than an open denominator.)", + evidence: [], +} + data recurring_failure_mode_roster: List = [ hollow_alias, state_space_conflation, @@ -189,4 +195,5 @@ data recurring_failure_mode_roster: List = [ remediation_mutated_view, diagnostic_name_mechanism_silent, identity_absent_graph_traversal, + coarser_parallel_authority, ] diff --git a/docs/plans/builtin-registry-population-partition.md b/docs/plans/builtin-registry-population-partition.md new file mode 100644 index 00000000000..7ee2441b43c --- /dev/null +++ b/docs/plans/builtin-registry-population-partition.md @@ -0,0 +1,211 @@ +# Partitioning `builtin_function_registry`: the denominator closes by relocation + +## Why this document exists + +`v1.compiler.infer_method` `builtin_function_registry` carries 131 names and maps each to a RETURN +TYPE. `std.primitive_identity` `primitive_signature` resolves a full parameter signature for a +primitive by keying into `std.algebra`'s `AlgebraFieldTemplate` rows. 20 of the registry's names +have such a row and resolve; **111 answer `SignatureNotGrounded`.** + +While that 111 is a mixed population, `SignatureNotGrounded` is ambiguous between *"not a language +primitive"* and *"a language primitive nobody has modelled yet"*, and a coercion that fails closed +on it cannot distinguish a legitimate absence from an unmodelled one. Closing that ambiguity is the +precondition for the interpreter gaining the optional-into-required-parameter coercion its emitted +counterpart already has (→ [first-optional divergence census](first-optional-divergence-census.md)). + +**The partition is by RELOCATION, not classification.** §3 holds that interface, realization and +policy are three facts and not one row, and that the dispatch selecting a realization is itself +realization. A lens's or a host transport's parameter shape is a realization fact; it does not +belong in a language-primitive signature authority at all. So the two populations are not two kinds +of one thing awaiting a discriminator — they are two different things sharing one table, and the +partition ends with the second population's rows leaving. + +## Four candidate predicates, measured, and why none of them decides it alone + +Recorded so the next reader does not re-derive them. Each was tested against the actual 111 rather +than reasoned about. + +1. **Join on the interpreter's primitive-surface roster.** Fails by construction: + `gunbc.v1_interpreter_primitive_surface` enumerates an arm for BOTH populations, so it + classifies `doc_graph_orphan_count` and `parse_int` identically. It answers "is there an arm", + which is true of every builtin. +2. **Does the name have a `.dag` `fn` declaration?** This is the `HostRealizedSeam` shape from + `std.primitive_projection`, whose doc comment establishes seams *by reading the declaration, not + by matching the name*. It identifies 8 of the 111 and leaves 103 undecided, and it + false-positives on `string_contains`, which matches a declaration inside a witness test. +3. **Does the interpreter arm touch the host?** Fails in both directions on the real population. + `parse_int`, `string_length`, `code_point` and `is_xid_start` look host-touching because their + arms delegate to `v1_rt` helpers; `decl_facts` and `doc_graph_orphan_count` look pure because + their host call is further down the arm than any fixed reading window. +4. **How many modules reference the name?** The strongest of the four and still not sufficient. It + separates cleanly at the extremes — `string_contains` (451 referencing modules), `string_length` + (94), `parse_int` (41) against `doc_graph_orphan_count` (1, in `src/v2/lens/doc_reachability`) — + but breadth of USE is not ownership of the FACT: `filesystem_read` (63), `compile_dag_rust_emit_check` + (71) and `shell_materialize_operation_argv` (10) are widely-used TRANSPORTS, and + `scan_while` / `scan_to_eol` / `skip_horizontal_ws` are narrow LANGUAGE primitives whose only + callers are inside the tokenizer. + +**What the fourth predicate does supply, and it is the useful part, is the HOME.** Its value is not +the count but the module the references land in: `fallback_arm_census_facts` is +`src/v2/lens/fallback_arm_census`'s fact, `emit_host_run_transport` is `src/v2/compiler/emit_host`'s. +Naming the owning module is the §3 question ("a fact's home is its layer") asked mechanically. The +count is a symptom of the answer, never the criterion, and every row below carries its home. + +## The criterion actually applied + +For each name: **does the operation's meaning come from the LANGUAGE, or from a domain authority +that owns it?** A language primitive is one the substrate itself must be able to talk about +regardless of which lens, workflow or instrument exists — text and code-point manipulation, set +construction, hashing, identifier classification, lexer scanning. Everything else names a fact some +module owns, and its signature belongs at that module's declaration, which in most cases already +declares its parameters. + +Both dispositions are adjudications, not lookups, and the residue is judgement — as it must be, +since all four mechanical predicates were measured to fail. What makes them auditable is that each +row states its home, so a disagreement is about one named module rather than about the rule. + +## `LanguagePrimitive` — 24 names + +Disposition: ground a signature through `std.primitive_identity`. Once the transports below have +left the registry, `SignatureNotGrounded` over this population means unambiguously "a language +primitive whose signature is not yet modelled" — a closeable gap, and the property the coercion +needs in order to fail closed honestly. + +| name | referencing modules | first non-test reference | +|---|---|---| +| `string_contains` | 450 | `dag/extdeps/astronomy/stellar_classification.dag` | +| `string_length` | 93 | `dag/extdeps/auth/jwt.dag` | +| `from_code_point` | 40 | `dag/extdeps/dns/domain_name.dag` | +| `parse_int` | 40 | `dag/extdeps/bmc/capability.dag` | +| `discriminant` | 39 | `dag/extdeps/git/git.dag` | +| `char_at` | 26 | `dag/extdeps/auth/jwt.dag` | +| `set_contains` | 20 | `dag/gunbc/emit_summary_map_consumer_partition.dag` | +| `code_point` | 19 | `dag/extdeps/auth/jwt.dag` | +| `empty_set` | 19 | `dag/gunbc/package_delivery.dag` | +| `set_insert` | 15 | `dag/gunbc/package_delivery.dag` | +| `sorted_map_keys` | 8 | `dag/gunbc/instruments/pr_containment_instrument.dag` | +| `atom_identity_hash` | 3 | `dag/std/content_hash.dag` | +| `chars_to_string` | 3 | `src/v1/00_core.dag` | +| `is_emoji_ident` | 3 | `src/v1/01_tokenize.dag` | +| `map_is_empty` | 3 | `src/v1/04_infer.dag` | +| `set_union` | 3 | `dag/std/authorization_profile.dag` | +| `hash_combine` | 2 | `dag/std/content_hash.dag` | +| `is_xid_continue` | 2 | `src/v1/01_tokenize.dag` | +| `is_xid_start` | 2 | `src/v1/01_tokenize.dag` | +| `record_source_chars_index_lookup` | 1 | `src/v1/01_tokenize.dag` | +| `scan_string_end` | 0 | `(no .dag reference)` | +| `scan_to_eol` | 0 | `(no .dag reference)` | +| `scan_while` | 0 | `(no .dag reference)` | +| `skip_horizontal_ws` | 0 | `(no .dag reference)` | + +## `Transport` — 87 names + +Disposition: the signature belongs with the realization authority named under *home*; the name +leaves the language registry. Where that module already declares the operation as a `.dag` `fn`, +the parameter list exists there and relocation supplies the signature at no authoring cost. + +| name | referencing modules | home | +|---|---|---| +| `non_fold_residue_roster_red_fixture_holds` | 0 | `(no .dag reference)` | +| `non_fold_residue_total_fold_green_fixture_holds` | 0 | `(no .dag reference)` | +| `witness_layer_roots_compile_clean_check` | 0 | `(no .dag reference)` | +| `witness_layer_roots_compile_clean_emit_check` | 0 | `(no .dag reference)` | +| `contiguous_loop_elementwise_float_kernel` | 2 | `dag/extdeps/languages/simd/kernel.dag` | +| `contiguous_loop_elementwise_kernel` | 2 | `dag/extdeps/languages/simd/kernel.dag` | +| `filesystem_read` | 62 | `dag/extdeps/llm/claude_agent_sdk_stream.dag` | +| `emit_host_native_cache_evict` | 1 | `dag/extdeps/realization/emit_on_demand_host.dag` | +| `decl_facts` | 27 | `dag/gunbc/bare_name_fork_lens.dag` | +| `compile_dag_diagnostic_census` | 28 | `dag/gunbc/compile_diagnostic_census.dag` | +| `compile_dag_rust_emit_check` | 71 | `dag/gunbc/compile_diagnostic_census.dag` | +| `shell_materialize_operation_argv` | 10 | `dag/gunbc/host/host_operation_exec.dag` | +| `witness_compile_clean_cli_floor_verdicts_agree` | 1 | `dag/gunbc/instruments/dag_compile_clean_cli_floor_agreement.dag` | +| `module_declaration_facts` | 2 | `dag/gunbc/instruments/dag_compile_clean_shard_roster.dag` | +| `install_or_consume_floor_compile_clean_gate_receipt` | 1 | `dag/gunbc/instruments/dag_compile_clean_transport.dag` | +| `consume_generated_artifact_drift_gate_receipt` | 1 | `dag/gunbc/instruments/generated_artifact_gate.dag` | +| `record_generated_artifact_drift_gate_clean` | 1 | `dag/gunbc/instruments/generated_artifact_gate.dag` | +| `record_generated_artifact_drift_gate_failure_detail` | 1 | `dag/gunbc/instruments/generated_artifact_gate.dag` | +| `compile_dag_multi_module_fixture` | 1 | `dag/gunbc/instruments/multi_module_compile_fixture.dag` | +| `test_migration_behavior_discovery_holds` | 1 | `dag/gunbc/legacy_test_behavior_disposition.dag` | +| `test_migration_legacy_behavior_ids` | 2 | `dag/gunbc/legacy_test_behavior_disposition.dag` | +| `test_migration_witness_behavior_ids` | 1 | `dag/gunbc/legacy_test_behavior_disposition.dag` | +| `data_decl_type_facts` | 3 | `dag/gunbc/lifecycle_survivor_scan.dag` | +| `namespace_structural_observation_admissions` | 2 | `dag/gunbc/namespace/namespace_structural_observations_production.dag` | +| `parse_roadmap_acceptance_event_history_jsonl` | 1 | `dag/gunbc/roadmap/roadmap_acceptance_history_carrier.dag` | +| `project_roadmap_acceptance_event_history_from_authority_text_host` | 1 | `dag/gunbc/roadmap/roadmap_acceptance_history_projection.dag` | +| `parse_stage0_cargo_manifest_bins` | 1 | `dag/gunbc/stage0/stage0_rust_host_observation.dag` | +| `observed_monotonic_nanos` | 2 | `dag/std/realization_measurement.dag` | +| `commit_witness_claim_pair_resolvable` | 1 | `dag/test/claim/commit_witness_claim_roster_witness_test.dag` | +| `doc_graph_admitted_root_count` | 1 | `dag/test/claim/doc_reachability_witness_test.dag` | +| `seed_runner_bool_false_failure_detail` | 1 | `dag/test/claim/long/extdeps_scope_placement_gate_loudness_witness_test.dag` | +| `shell_transport_operation_rows` | 1 | `dag/test/claim/operation_argv_corpus_witness_test.dag` | +| `observed_peak_resident_bytes` | 1 | `dag/test/claim/peak_resident_measured_witness_test.dag` | +| `name_resolution_policy_is_namespace_only` | 3 | `src/v1/04_env.dag` | +| `resolution_silent_pick_is_enabled` | 2 | `src/v1/04_env.dag` | +| `resolution_silent_pick_record_global_bare_lcp_pick` | 1 | `src/v1/04_env.dag` | +| `resolution_silent_pick_record_global_bare_lcp_tie` | 1 | `src/v1/04_env.dag` | +| `rc_ptr_eq` | 1 | `src/v1/04_infer.dag` | +| `rc_vec_ptr_eq` | 1 | `src/v1/04_infer.dag` | +| `type_ref_hit_ne_bind_measure_active` | 1 | `src/v1/04_resolve.dag` | +| `resolution_silent_pick_record_fn_parent_first_hit` | 1 | `src/v1/04_sigs.dag` | +| `trace_mark` | 1 | `src/v1/compile.dag` | +| `emit_host_run_transport` | 1 | `src/v2/compiler/emit_host.dag` | +| `emit_host_run_transport_cached` | 1 | `src/v2/compiler/emit_host.dag` | +| `toolchain_home_interference_probe` | 1 | `src/v2/extdeps/toolchain_interference.dag` | +| `complexity_linearity_syntactic_finding_count` | 1 | `src/v2/lens/complexity_linearity_audit.dag` | +| `complexity_linearity_syntactic_site_fired` | 1 | `src/v2/lens/complexity_linearity_audit.dag` | +| `complexity_linearity_wildcard_facts` | 1 | `src/v2/lens/complexity_linearity_audit.dag` | +| `doc_graph_dangling_link_count` | 2 | `src/v2/lens/doc_reachability.dag` | +| `doc_graph_doc_count` | 2 | `src/v2/lens/doc_reachability.dag` | +| `doc_graph_orphan_count` | 1 | `src/v2/lens/doc_reachability.dag` | +| `extdeps_qualified_name_resolves_in_derived_module_set` | 1 | `src/v2/lens/extdeps_shape_transport_policy.dag` | +| `extdeps_shape_transport_policy_facts_for_qualified_name` | 1 | `src/v2/lens/extdeps_shape_transport_policy.dag` | +| `fact_cardinality_decl_facts` | 1 | `src/v2/lens/fact_cardinality.dag` | +| `fallback_arm_census_class_count` | 1 | `src/v2/lens/fallback_arm_census.dag` | +| `fallback_arm_census_facts` | 1 | `src/v2/lens/fallback_arm_census.dag` | +| `fallback_arm_census_reconciliation_holds` | 2 | `src/v2/lens/fallback_arm_census.dag` | +| `fallback_arm_census_total` | 1 | `src/v2/lens/fallback_arm_census.dag` | +| `concept_decl_facts` | 2 | `src/v2/lens/grounding.dag` | +| `transport_script_position_facts_for_path` | 1 | `src/v2/lens/host_language_transport_script.dag` | +| `inert_carrier_declared_count` | 1 | `src/v2/lens/inert_carrier.dag` | +| `inert_carrier_names_live` | 1 | `src/v2/lens/inert_carrier.dag` | +| `export_signature_facts` | 2 | `src/v2/lens/interface_summary.dag` | +| `languages_consumer_census_data_decl_count` | 1 | `src/v2/lens/languages_consumer_census.dag` | +| `languages_consumer_census_external_consumer_count` | 1 | `src/v2/lens/languages_consumer_census.dag` | +| `languages_consumer_census_format_row_count` | 1 | `src/v2/lens/languages_consumer_census.dag` | +| `languages_consumer_census_has_external_consumer` | 1 | `src/v2/lens/languages_consumer_census.dag` | +| `languages_consumer_census_is_composition_only` | 1 | `src/v2/lens/languages_consumer_census.dag` | +| `languages_consumer_census_per_language_row_count` | 1 | `src/v2/lens/languages_consumer_census.dag` | +| `extdeps_external_authority_facts_for_qualified_name` | 1 | `src/v2/lens/mandatory_tag/corpus_scan.dag` | +| `extdeps_external_authority_live_clean_tree_holds` | 1 | `src/v2/lens/mandatory_tag/corpus_scan.dag` | +| `extdeps_external_authority_live_roster_module_count` | 1 | `src/v2/lens/mandatory_tag/corpus_scan.dag` | +| `dependency_resolution_facts` | 1 | `src/v2/lens/module_graph.dag` | +| `import_resolution_facts` | 1 | `src/v2/lens/module_graph.dag` | +| `reference_resolution_facts` | 1 | `src/v2/lens/module_graph.dag` | +| `census_corpus_roots_follow_layer_authority` | 1 | `src/v2/lens/non_fold_residue.dag` | +| `non_fold_residue_coproduct_universe_count` | 2 | `src/v2/lens/non_fold_residue.dag` | +| `non_fold_residue_count` | 1 | `src/v2/lens/non_fold_residue.dag` | +| `non_fold_residue_stale_roster_count` | 2 | `src/v2/lens/non_fold_residue.dag` | +| `non_fold_residue_unrostered_count` | 2 | `src/v2/lens/non_fold_residue.dag` | +| `test_migration_debt_module_names` | 1 | `src/v2/lens/test_migration_debt.dag` | +| `layer_import_facts` | 1 | `src/v2/std/layer_import_scan.dag` | +| `non_fold_residue_synthetic_unrostered_red_holds` | 1 | `src/v2/test/lens_non_fold_residue/non_fold_residue_test.dag` | +| `non_fold_residue_wildcard_red_fixture_holds` | 1 | `src/v2/test/lens_non_fold_residue/non_fold_residue_test.dag` | +| `observe_declared_import_closure_symbol_binding` | 1 | `src/v2/workflow/class_b_import_closure_probe.dag` | +| `class_b_import_closure_gate_not_affected_skip` | 1 | `src/v2/workflow/class_b_import_closure_transport.dag` | +| `commit_witness_claim_roster_unresolvable_count` | 1 | `src/v2/workflow/commit_witness_claim_roster.dag` | + +## What this partition dissolves + +Two classes, one relocation. Once the registry is no longer a signature authority for anything +`std.algebra` owns, the 20 overlapping names stop being two declarations of one operation — which +is the `coarser_parallel_authority` row in `gunbc.recurring_failure_mode`, filed from the measured +disagreement between the two carriers (`reverse` typed as `List` against +`ReceiverSelf`; `map_keys` and `map_values` sharing one element variable where the algebra rows +distinguish `ReceiverKey` from `ReceiverValue`; `concat` as `String` against `ReceiverSelf`; `get` +collapsing the List and Map readings). There is no second declaration left to disagree. + +## Sequencing + +This is a substrate program, not a step in a repair, and it is expected to span several PRs. This +document is the disposition census and carries no pipeline edit. Relocation lands per owning module, +so each PR is a readable diff against one authority. diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md index 28bbc72828b..9a44eb5c481 100644 --- a/docs/plans/first-optional-divergence-census.md +++ b/docs/plans/first-optional-divergence-census.md @@ -74,11 +74,19 @@ the shape that dominates the corpus is the one shape the compensation covers. **`Propagates` resolves the same way, one level out.** Following all 36 functions to their call sites: 72 callers eliminate by `match` (unharmed), 2 tail-propagate into another `T?` (`mercurial_first_changeset_cycle` / `_file_revision_cycle`), and 4 compare `== none` -(`rust_representation_realization_for` in `self_host_symbol_identity_binding_witness_test`) — which -AGREES, because a miss is `Null` on one side and `Absent` on the other and both compare equal to -`none`. It agrees by the representation happening to line up on that one constructor: the same site -spelled `== Present { value: .. }` is the parent lane's discriminator and diverges. **Zero harmed -today, and the margin is one constructor wide.** +(`rust_representation_realization_for` in `self_host_symbol_identity_binding_witness_test`). **Zero +harmed today** — but the reason is not the one an earlier revision of this document gave, and the +correction matters more than the verdict did. + +That revision said the `== none` sites agree "because a miss is `Null` on one side and `Absent` on +the other and both compare equal to `none`". **That mechanism is wrong.** In the interpreter `none` +EVALUATES TO `Value::Null` — `v1_interpreter`'s variable evaluation returns `Value::Null` for the +symbols `none` and `None` before any binding is consulted. So the raw side compares equal because it +*is* `Null`; a constructed `Optional::Absent` variant does not compare equal to it at all. These +sites therefore agree **before** the construction lands and BREAK after it, and two of them are +among the six assertion failures the floor reports on this branch. The verdict "zero harmed today" +was right about the pre-change state and was reached by the wrong route — and the wrong route is +exactly what hid the `none`-literal migration (Phase D below) from this census's first draft. `HarmedNow`, in full — this is the whole victim list for the pipeline spelling: @@ -236,6 +244,94 @@ the 20 overlapping names are two authorities for one operation's type, and they Every one of these is the same §3 fork as the `first` divergence, one layer up: not two realizations of one declaration, but two declarations of one operation. +## Measured blast radius, and the class already has an authored program + +**The floor, enumerated from the artifact rather than the log.** The required-witnesses-floor run on +this branch reports `planned=3141 passed=2630 known_red_held=15 failed=442`. The job log prints only +six per-claim failure lines, which reads as a truncated log and is not: the +`required-floor-disposition` artifact separates the outcomes the summary's `failed` folds together. + +| outcome | count | +|---|---| +| `runtime-errored-before-verdict` | 442 | +| `failed` (assertion) | 6 | +| `budget-refused-before-verdict` | 1 | +| `route-gap-before-verdict` | 47 | +| `known-red-held` | 15 | + +So the 442 **errored before reaching a verdict**; they did not assert and fail. All 442 are +`v2.test.*` — 157 `v2.test.claim`, 132 `v2.test.manual`, 71 `v2.test.emit`, 52 `v2.test.execution` +— and **none** is a `dag/test/claim` witness. That is the self-host coupling: the v2 compiler is +`.dag` interpreted by the v1 seed, so changing the interpreter's projections changes v2's own +behaviour as it runs. The blast radius is the interpreted v2 compiler, not the corpus witnesses. + +**A note on how this document's first draft got its number wrong.** It reported the partial repair +as flipping one witness, `bmc_capability_solve firmware_wire_version_is_parsed_before_track_matching`. +That claim's disposition is `declined_outside_gate_closure` / `not_executed` — the floor does not run +it. The sample was not merely small; it was drawn from outside the population the floor measures. + +**The class is Phase B of an already-open lane.** `gunbc.plans.value_null_split` (lane +keen-ferret-250) models this whole class: `Value::Null` overloads four meanings — the `none`/`None` +literal, `Optional::Absent`, `Witness::Violates` on map miss, and untyped lookup miss — and it lays +out a phase order: + +| phase | content | state | +|---|---|---| +| A | discriminating witnesses pinning the carriers | landed | +| B | stop PRODUCING `Null` where the return type is `Optional` | **what the repair on this branch is** | +| C | delete the `match_pattern` `Null` bridges | the compensating arms this branch leaves in place | +| D | type-directed `none` → `optional_absent`; migrate ~218 `== None` sites over 66 files | not started — the third gate | +| E | cross-representation equality; remove the straddle row | not started | + +**The Phase-A witness predicted this branch's failure by name, in writing.** +`v2.test.manual.value_null_split_witness` carries the comment: *"`raw_get_miss_differs_from_optional_absent` +stays GREEN while raw get miss is untyped and `optional_absent()` is `Optional::Absent`; it flips +RED in Phase B when get+Optional routes through `map_lookup_as_optional`."* It is one of the six +assertion failures. That is not a defect in the repair — it is the enrolled signal that Phase B +landed, and updating its disposition is part of Phase B. + +`value_null_split` §0 also pre-refutes the fix this census would otherwise have reached for: a +blanket cross-representation equality guard **cannot** close the straddle, because `present == None +→ false` is legitimate at those ~218 sites. The remedy is splitting the carriers, not grounding them +onto one sentinel. + +**So the completion has three gates, not one**: the optional-into-required argument coercion (needs +a closed language-primitive denominator → [partition census](builtin-registry-population-partition.md)), +Phase D's `none`-literal migration, and Phase C's bridge deletion. None is optional and none is +this branch's alone. + +## The scheduling hold is not the dissolution trigger + +These are two different objects and this document keeps them apart, because conflating them is how +a trigger rots. + +**The hold is procedural and belongs to this moment.** PR #9775 enrols this divergence as a +known-red with an expected-red roster row, and the floor counts `known_red_now_passing` as its own +outcome, so repairing the primitive while that row still stands breaks BT-0's floor. That is an +ordering constraint on when a repair may land. It is a note to the authors involved, not a claim +about the defect. + +**The trigger is semantic and belongs to any future reader**: *when the interpreted and emitted +realizations of `first` agree on the optional result shape*. A trigger phrased "after PR #9775" +would name a merge event, and merge events rot — a PR is renumbered, superseded, split, or lands +with the row removed, at which point the trigger is either unsatisfiable or vacuously satisfied +while nothing about the defect has changed. Nothing in this document, in +`gunbc.recurring_failure_mode`, or on the witness carries a PR number as a trigger; the one PR +number below is a population reconciliation, not a condition. + +**The transition, in order, when the repair lands** — this is §4b(4) dissolution-on-climb, and the +middle pair is the whole rule: + +1. repair the interpreter's `first` semantics; +2. REMOVE the discriminator from the expected-red roster — the production disposition goes; +3. RETAIN the discriminator as ordinary passing evidence — the evidence stays, and becomes the + permanent regression control proving the two arms still agree; +4. observe `known_red_now_passing = 0`. + +Step 4 is not a formality. That channel exists precisely to catch someone repairing the primitive +and leaving a stale expected-red disposition behind, so a nonzero value there is a defect in the +repairing transaction, never noise. + ## Rung, ceiling, trigger - **Class**: `first_optional_representation_divergence` — a collection projection declared diff --git a/floor_probe.sh b/floor_probe.sh new file mode 100644 index 00000000000..53428998fad --- /dev/null +++ b/floor_probe.sh @@ -0,0 +1,12 @@ +set -o pipefail +cargo build --release -p v1-compiler --bins 2>&1 | tail -2 +./target/release/claim_executor --required-ci --source-root dag --source-root src/v2 --required-lane witnesses > /tmp/floor.log 2>&1 +echo "EXIT=$?" +echo "===SUMMARY" +grep -E "planned=|phase |ROUTED to lane" /tmp/floor.log | tail -30 +echo "===COUNTS-COMPUTED-REMOTELY" +echo "FAILED_LINES=$(grep -cE '^FAILED|FAILED in' /tmp/floor.log)" +echo "LOG_LINES=$(wc -l < /tmp/floor.log)" +echo "===FAILED-ROSTER" +grep -E '^FAILED|FAILED in' /tmp/floor.log | sed -E 's/ *\(.*//' | sort | uniq -c | sort -rn +echo "===ROSTER-END" From 59f24aef638e4ade1cac23e064c5289aaadbe042 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 31 Aug 2026 05:08:14 +0000 Subject: [PATCH 05/28] Record the measured blast radius against a trunk control, and name the field that carries known-red discrimination The census gains the post-merge re-measurement with a trunk control (main at b41d5648 is 0 errored / 0 failed / 3065 passed against this branch's 426/6/2619), which is what makes the attribution a measurement rather than a reading of the diff. It also refuted an attribution this document would otherwise have carried: two failures name self_host_symbol_identity_binding_witness, merged in from main the same hour, and the clean trunk says they are this branch's. main_wet is added as a measured victim outside the floor -- it refuses under this branch's own coercion arm -- which is why the DESIGN.md and design-ledgers.md projections are deliberately left inconsistent rather than regenerated from a stock-interpreter seed. floor_non_verdict gains two sentences naming which field carries the property readers cite it for: non_verdict_unenrolled, not known_red_now_passing. An earlier draft of this lane proposed a 4b class row asserting an unguarded conflation there; that was wrong -- the wall exists and fired -- and a row claiming a missing guarantee over a working wall is rung deflation. Annotation only, semantically inert. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK --- dag/gunbc/plans/value_null_split.dag | 132 ++++++++++++++++-- .../plans/first-optional-divergence-census.md | 33 +++++ src/v2/workflow/floor_non_verdict.dag | 8 ++ 3 files changed, 164 insertions(+), 9 deletions(-) diff --git a/dag/gunbc/plans/value_null_split.dag b/dag/gunbc/plans/value_null_split.dag index 3fd266a1738..681c2524eef 100644 --- a/dag/gunbc/plans/value_null_split.dag +++ b/dag/gunbc/plans/value_null_split.dag @@ -3,12 +3,109 @@ module gunbc.plans.value_null_split import std.dissolution { unbound_dissolution } import gunbc.plan { Plan, PlanRetirement, PlanRetiresWhen, PlanIsAuthorityOnly } import std.markdown { MarkdownBlock, TableBlock, AlignNone } -import gunbc.plans.md_helpers { h2, p, li, ol, cell, row, task, tasks, Unchecked } +import std.types { Bool, List, NonEmptyStr, String } +import gunbc.plans.md_helpers { h2, p, li, ol, ul, cell, row } + +// PHASE STANDING IS NOT A CHECKBOX, and this module used to prove why. Every phase below was +// `task(state: Unchecked)` while Phase A had DEMONSTRABLY LANDED -- its witness module exists, runs +// in the required floor, and one of its claims is enrolled and firing. The document stated the +// right invariant in its own review bar ("every phase ships with a discriminating witness that goes +// RED when the carrier regresses") and then tracked itself by hand anyway, so it went stale by a +// full phase with nothing to notice. A hand-maintained status is the execution-provenance failure +// applied to a plan: it reads identically whether the phase landed or nobody updated the line. +// +// So the status is DERIVED from the witness roster rather than typed. What that buys and what it +// does not, stated so neither is overread: the roster below is HAND-AUTHORED, so a phase whose +// witness is never declared here reads Unwitnessed exactly like a phase with no witness at all. +// This is therefore a CITATION, one step above a checkbox because there is a single authority for +// the answer instead of one per rendering, and one step below evidence. +// +// NEXT RUNG, naming the capability rather than an artifact: reading claim OUTCOMES at claim-identity +// grain from inside `.dag`. The required floor already computes exactly that join -- its +// `required-floor-disposition` output carries one row per claim identity with the outcome that +// claim reached -- but it is an out-of-band run artifact, not something a `.dag` fold can consult, +// so no declaration here can be green-by-execution about a phase. When that capability lands, +// `value_null_phase_standing` reads the outcome instead of the roster and the citation becomes +// evidence. +type ValueNullPhase + = PhaseCarrierWitnesses + | PhaseStopProducingNull + | PhaseDeleteNullBridges + | PhaseNoneLiteralMigration + | PhaseCrossRepresentationEquality + | PhaseArgumentCoercion + | PhasePrimitiveSignatureDenominator + +type ValueNullPhaseWitness { + phase: ValueNullPhase + claim: NonEmptyStr +} + +type ValueNullPhaseStanding + = PhaseWitnessEnrolled { claim: NonEmptyStr } + | PhaseUnwitnessed + +fn value_null_phase_eq(a: ValueNullPhase, b: ValueNullPhase) -> Bool { + match a { + PhaseCarrierWitnesses => match b { PhaseCarrierWitnesses => true _ => false } + PhaseStopProducingNull => match b { PhaseStopProducingNull => true _ => false } + PhaseDeleteNullBridges => match b { PhaseDeleteNullBridges => true _ => false } + PhaseNoneLiteralMigration => match b { PhaseNoneLiteralMigration => true _ => false } + PhaseCrossRepresentationEquality => match b { PhaseCrossRepresentationEquality => true _ => false } + PhaseArgumentCoercion => match b { PhaseArgumentCoercion => true _ => false } + PhasePrimitiveSignatureDenominator => match b { PhasePrimitiveSignatureDenominator => true _ => false } + } +} + +// One row per phase that has a discriminating witness enrolled today. Phase A's rows are the +// carrier-pinning claims in `v2.test.manual.value_null_split_witness`; Phase B's is the +// construction claim added while Phase B was built (session still-swift-363, PR gunbc#9785, held). +// A phase absent from this list has no enrolled discriminator, which is the honest reading of +// "not started" and is NOT the same statement as "not landed". +data value_null_phase_witnesses: List = [ + ValueNullPhaseWitness { + phase: PhaseCarrierWitnesses, + claim: "v2.test.manual.value_null_split_witness.raw_get_miss_differs_from_optional_absent" as NonEmptyStr, + }, + ValueNullPhaseWitness { + phase: PhaseCarrierWitnesses, + claim: "v2.test.manual.value_null_split_witness.map_get_miss_is_absent_not_present" as NonEmptyStr, + }, + ValueNullPhaseWitness { + phase: PhaseStopProducingNull, + claim: "test.claim.first_optional_construction_witness.first_of_a_list_whose_head_is_an_absent_optional_is_present_of_absent" as NonEmptyStr, + }, +] + +fn value_null_phase_standing(phase: ValueNullPhase) -> ValueNullPhaseStanding { + fold( + value_null_phase_witnesses, + init: PhaseUnwitnessed, + f: fn(acc, w) { + match acc { + PhaseWitnessEnrolled { claim: _ } => acc + PhaseUnwitnessed => + if value_null_phase_eq(a: w.phase, b: phase) { + PhaseWitnessEnrolled { claim: w.claim } + } else { + acc + } + } + } + ) +} + +fn value_null_phase_standing_label(phase: ValueNullPhase) -> String { + match value_null_phase_standing(phase: phase) { + PhaseWitnessEnrolled { claim: c } => concat("discriminator enrolled: `", concat(c as String, "`")) + PhaseUnwitnessed => "no discriminator enrolled" + } +} fn value_null_split_body() -> List { [ - p(text: "**Status:** lane opened (keen-ferret-250) · **Parent:** model↔realization fork §3.2 · **DESIGN.md open thread** · Linked from [model-realization-fork.md](model-realization-fork.md) and [fail-closed-lockdown.md](fail-closed-lockdown.md) §4 tier-1."), - p(text: "**Verified against the live tree 2026-07-26.** Census counts are receipts; re-check before acting."), + p(text: "**Ownership: none.** This plan was opened by a lane whose session no longer exists -- a `--to` addressed to it is refused as `recipient session not found`, and the name appears in no work item. A lane name written inside a document is a CITATION and rots exactly like a `file:line` or a merge-event trigger; absence from the live session graph is decidable, presence in this paragraph is not. Read the plan as an unowned authority to work AGAINST, never as a lane to defer to. **Parent:** model↔realization fork §3.2 · **DESIGN.md open thread** · Linked from [model-realization-fork.md](model-realization-fork.md) and [fail-closed-lockdown.md](fail-closed-lockdown.md) §4 tier-1."), + p(text: "**Census counts are receipts; re-check before acting.** Sections 0-3 were verified against the live tree on 2026-07-26 and have not been re-verified since. Sections 4, 4b and the phase standings were verified 2026-08-31, and the two dates are kept apart deliberately: a single freshness stamp over a document whose halves were measured five weeks apart is the same overclaim as a folded outcome column."), h2(text: "0. Problem — one native carrier, four meanings"), p(text: "`Value::Null` overloads four distinct semantics today: (1) the `None`/`none` literal and `LitNull`, (2) `Optional::Absent` (bridged in `match_pattern` at `v1_interpreter.rs:2830`), (3) `Witness::Violates` on map miss (bridged at `:2808` with a fabricated diagnostic), (4) untyped lookup miss (`raw_map_lookup`, `list_get_at_or_null`, `get` on map/list). A blanket `CrossRepresentationEquality` guard cannot close the Optional/Witness straddle — `present == None → false` is *legitimate* at ~218 corpus sites — so the fix is **splitting**, not grounding onto one sentinel."), h2(text: "1. Target carriers (construction authority)"), @@ -45,12 +142,29 @@ fn value_null_split_body() -> List { li(text: "Existing witnesses: `witness_option_bridge_test.dag` (map_get → Present/Absent at model layer); `cross_representation_equality.dag` roster includes `Optional` × `native_value_null` straddle (target: remove when grounded)."), ]), h2(text: "4. Phased landing (construction-first)"), - tasks(items: [ - task(state: Unchecked, text: "**Phase A (this lane):** plan + discriminating witnesses pinning carrier invariants (`value_null_split_witness_test.dag`); emitter row table above is authority for S2 emit."), - task(state: Unchecked, text: "**Phase B:** stop *producing* `Null` where the return type is `Optional` — route `map_get`/`get`+Optional context through `map_lookup_as_optional` (keep raw `get` returning bare value + `Null` miss for `map_lookup_dual_dispatch` until typed overload lands)."), - task(state: Unchecked, text: "**Phase C:** delete `match_pattern` `Null` bridges (`:2808–2834`) once no producer emits `Null` for Optional/Witness arms."), - task(state: Unchecked, text: "**Phase D:** type-directed `None` literal → `optional_absent()` where inhabiting `Optional<_>`; migrate `== None` sites (~218) to `match Absent` or `optional_is_absent` helper."), - task(state: Unchecked, text: "**Phase E:** cross-representation equality — ground `Optional` so `Absent` variant reconciles with narrowed `Null` *only* at the lookup-miss boundary; remove `Optional`×`native_value_null` from testgen roster; bundle `CrossRepresentationEquality` guard removal with this phase (fenced in model-realization-fork §3.1)."), + p(text: "**Standing is derived, never ticked.** Each row's standing is `value_null_phase_standing`, a fold over the enrolled-witness roster in this module -- see the note above it for exactly how far that goes (a citation, not evidence) and for the capability that would close the gap. An earlier revision of this section was a hand-checked task list in which every phase, INCLUDING the landed Phase A, read unchecked."), + TableBlock { + header: row(cells: [cell(text: "phase"), cell(text: "content"), cell(text: "standing")]), + alignments: [AlignNone, AlignNone, AlignNone], + rows: [ + row(cells: [cell(text: "A"), cell(text: "plan + discriminating witnesses pinning carrier invariants (`value_null_split_witness_test.dag`); the emitter row table in section 2 is authority for S2 emit"), cell(text: value_null_phase_standing_label(phase: PhaseCarrierWitnesses))]), + row(cells: [cell(text: "B"), cell(text: "stop *producing* `Null` where the return type is `Optional` -- route `map_get`/`get`+Optional context through `map_lookup_as_optional`. Extends to `first`/`last`/`lookup`, whose `std.algebra` rows declare `OptionalOf` and whose interpreter arms returned the raw element"), cell(text: value_null_phase_standing_label(phase: PhaseStopProducingNull))]), + row(cells: [cell(text: "C"), cell(text: "delete the `match_pattern` `Null` bridges once no producer emits `Null` for Optional/Witness arms"), cell(text: value_null_phase_standing_label(phase: PhaseDeleteNullBridges))]), + row(cells: [cell(text: "D"), cell(text: "type-directed `None` literal to `optional_absent()` where inhabiting `Optional<_>`; migrate the `== None` sites to `match Absent` or an `optional_is_absent` helper"), cell(text: value_null_phase_standing_label(phase: PhaseNoneLiteralMigration))]), + row(cells: [cell(text: "E"), cell(text: "cross-representation equality -- ground `Optional` so `Absent` reconciles with narrowed `Null` ONLY at the lookup-miss boundary; remove the `Optional`x`native_value_null` testgen straddle; bundle the `CrossRepresentationEquality` guard removal"), cell(text: value_null_phase_standing_label(phase: PhaseCrossRepresentationEquality))]), + row(cells: [cell(text: "F"), cell(text: "**NEW, discovered by building Phase B.** The interpreter gains the optional-into-required-parameter coercion the Rust emitter already has in `rust_call_arg_fail_closed_unwrap` -- unwrap `Present`, refuse typed and located on `Absent`. Phase B without this hands a `Present \{ .. \}` variant to every value-position call site"), cell(text: value_null_phase_standing_label(phase: PhaseArgumentCoercion))]), + row(cells: [cell(text: "G"), cell(text: "**NEW, and it gates F.** Close the language-primitive denominator so `SignatureNotGrounded` means \"not a language primitive\" rather than \"not modelled yet\". A builtin call never reaches the `.dag` call path, and `builtin_function_registry` carries a RETURN TYPE only, so argument cardinality cannot be derived for one"), cell(text: value_null_phase_standing_label(phase: PhasePrimitiveSignatureDenominator))]), + ], + }, + h2(text: "4b. What Phase B costs, measured"), + p(text: "Phase B was built and measured on a held draft (session still-swift-363, gunbc#9785, not landed). The required-witnesses-floor result is the number this plan should carry, because it is the argument against anyone later proposing Phase B is small enough to land alone."), + ul(items: [ + li(text: "`planned=3141 passed=2630 known_red_held=15 failed=442` against a baseline of ~0."), + li(text: "The summary's `failed` FOLDS two states. Read at claim-identity grain from the run's disposition output: **442 runtime-errored-before-verdict**, 6 failed assertions, 1 budget-refused, 47 route-gap, 15 known-red-held. The job log prints only the 6, which reads as a truncated log and is not."), + li(text: "**All 442 are `v2.test.*` and none is a `dag/test/claim` witness.** The cause is self-host coupling: v2 is `.dag` interpreted by the v1 seed, so changing the interpreter changes the v2 COMPILER's behaviour as it executes. Phase B's cost lands on the interpreted v2 compiler, not on corpus witnesses -- which is not what a reader of sections 0-3 would predict."), + li(text: "The 6 assertion failures are this plan's own `raw_get_miss_differs_from_optional_absent`, two `self_host_symbol_identity_binding_witness` claims comparing `== none`, `cargo_build_run_argv`, and two `emit_host_shell_exec_run_equals_eval` claims."), + li(text: "**The Phase A witness predicted its own flip in writing** -- its comment says `raw_get_miss_differs_from_optional_absent` \"flips RED in Phase B when get+Optional routes through `map_lookup_as_optional`\". It did. That is the enrolled signal that Phase B landed, so retiring its expected-red disposition is PART of Phase B and not a repair to it."), + li(text: "The `== none` reds are Phase D arriving early, and they confirm section 0's count from the other direction: `none` EVALUATES to `Value::Null` in the interpreter's variable evaluation, so a raw miss compares equal to `none` because it IS `Null`, while a constructed `Absent` variant does not compare equal at all."), ]), h2(text: "5. Review bar"), p(text: "No absorbing fallback: a miss must not widen to `Null` when the type is `Optional` or `Witness`. No new per-site `if matches!(v, Value::Null)` bridges without a counted dissolution trigger. Every phase ships with a discriminating witness that goes RED when the carrier regresses."), diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md index 9a44eb5c481..6af2276dd94 100644 --- a/docs/plans/first-optional-divergence-census.md +++ b/docs/plans/first-optional-divergence-census.md @@ -270,6 +270,39 @@ as flipping one witness, `bmc_capability_solve firmware_wire_version_is_parsed_b That claim's disposition is `declined_outside_gate_closure` / `not_executed` — the floor does not run it. The sample was not merely small; it was drawn from outside the population the floor measures. +**Re-measured post-merge, against a trunk control.** At `79ac1aa` (run 33357314877) the artifact +reports 426 `runtime-errored-before-verdict`, 6 `failed`, 15 `known-red-held`, 2619 `passed`. The +same artifact on main at `b41d5648` (run 33356292996) reports **0 errored, 0 failed, 3065 passed**. +The control is what makes the attribution a measurement: the whole population is caused by this +branch and none of it is inherited. It also refuted an attribution this document would otherwise +have carried — two of the six failures name `self_host_symbol_identity_binding_witness`, which is +#9741 territory merged in from main the same hour, and the clean trunk says they are this branch's. +A recently-merged neighbour is the most available explanation and therefore the one to control for. + +**The floor refuses through `non_verdict_unenrolled`, not through `known_red_now_passing`.** Five of +main's 20 known-reds error under this branch instead of returning their known-red verdict. They do +not appear in `known_red_now_passing`, which stays 0; they appear as +`verdict_incomplete=5 non_verdict_unenrolled=5` in the floor's terminal line, and that is what turns +the lane red. The wall is `v2.workflow.floor_non_verdict`, whose header already names this class and +whose roster is `Empty{}` so any enrolled known-red that starts throwing refuses as unrostered debt. +Recorded because the reading error is available and this lane made it: `known_red_now_passing` alone +does not distinguish a still-discriminating known-red from one that has stopped reaching its +assertion, so it is the wrong field to cite for that property — a citation defect, not a safety gap, +because the adjacent counter gates. + +**A measured victim outside the floor: `main_wet`.** The generated-artifact actuator +`tools.generated_artifact_gate main_wet` refuses under this branch's interpreter with +`CallContractMismatch { callee: "outcome_accepted", detail: "an optional value flowed into +non-optional parameter 1 ('value') (empty Optional at runtime)" }` — this branch's own coercion arm, +firing. Evidence about scope rather than an incident: the affected population is wider than the +floor's witness set and reaches the wet actuators. Consequence for this branch: the `DESIGN.md` and +`docs/design-ledgers.md` projections cannot regenerate here, so they sit inconsistent with their +`.dag` authority. That drift is **deliberately left**. It could be cleared by building the seed from +main's Rust and running it over this tree, and that is precisely what must not happen: those bytes +are what the *stock* interpreter computes, while the drift gate here executes *this* interpreter, so +committing them would green the gate across a live divergence — fail-open wearing a green check. The +drift needs no dissolution trigger of its own; it ends when the repair completes. + **The class is Phase B of an already-open lane.** `gunbc.plans.value_null_split` (lane keen-ferret-250) models this whole class: `Value::Null` overloads four meanings — the `none`/`None` literal, `Optional::Absent`, `Witness::Violates` on map miss, and untyped lookup miss — and it lays diff --git a/src/v2/workflow/floor_non_verdict.dag b/src/v2/workflow/floor_non_verdict.dag index 333278f7895..f0913ca7681 100644 --- a/src/v2/workflow/floor_non_verdict.dag +++ b/src/v2/workflow/floor_non_verdict.dag @@ -16,6 +16,14 @@ import v2.std.text { String } // assert anything. A diagnostic can describe evidence truthfully while the gate draws a false // conclusion from it; that happened on every run for a day. // +// WHICH FIELD CARRIES THIS, because a reader arrives here having cited the wrong one. The property +// "my enrolled known-reds are still discriminating" is carried by `non_verdict_unenrolled` in the +// floor's terminal line, NOT by `known_red_now_passing`: a known-red that stops reaching its +// assertion leaves the latter at 0, indistinguishable there from one still correctly red, and is +// refused by the former. Citing `known_red_now_passing` alone for that property is a citation +// defect and not a safety gap -- the adjacent counter gates -- but the two are read together often +// enough that saying so here is cheaper than rediscovering it. +// // THE RUNG, PER SEAM, because the governing rung is the minimum across them and stating only the // strongest is the inflation DESIGN section 4b(1) forbids: routing a thrown claim into its arm is // MITIGATABLE; printing the count is MITIGATABLE; printing `failed=0` without distinguishing From f5e05e4a5ad6e16e1868363b0bc4c2e0904f6421 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 31 Aug 2026 05:43:24 +0000 Subject: [PATCH 06/28] Derive phase equality from one exhaustive ordinal instead of a 7x7 wildcard comparison rust-unit-tests nfr_roster_receipt refused this branch with one unrostered non-fold residue site: value_null_phase_eq, added by the plan amendment. It matched a, then matched b inside each of seven arms with a '_ => false' wildcard, which is a wildcard over a CLOSED coproduct -- un-migrated modeling under DESIGN section 6, and the detector is right to flag it. Fixed by construction rather than by rostering it. The roster entry was available and would have greened the test, but registering the site admits debt where a fold was available. Equality now derives from value_null_phase_ordinal, one exhaustive 7-arm projection with no wildcard, following std.fermi fermi_ordinal. The safety difference is why the detector exists: under the old form an eighth phase would silently take the '_' arm at seven sites and compile clean; under the new one it makes the ordinal non-exhaustive and the compiler refuses. Verified: nfr_ suite 14/14, including red_control_wildcard_over_closed_coproduct _is_residue -- the detector still discriminates, so the site is gone rather than the check blunted. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK --- dag/gunbc/plans/value_null_split.dag | 30 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/dag/gunbc/plans/value_null_split.dag b/dag/gunbc/plans/value_null_split.dag index 681c2524eef..27f14af7f0c 100644 --- a/dag/gunbc/plans/value_null_split.dag +++ b/dag/gunbc/plans/value_null_split.dag @@ -3,7 +3,7 @@ module gunbc.plans.value_null_split import std.dissolution { unbound_dissolution } import gunbc.plan { Plan, PlanRetirement, PlanRetiresWhen, PlanIsAuthorityOnly } import std.markdown { MarkdownBlock, TableBlock, AlignNone } -import std.types { Bool, List, NonEmptyStr, String } +import std.types { Bool, Int, List, NonEmptyStr, String } import gunbc.plans.md_helpers { h2, p, li, ol, ul, cell, row } // PHASE STANDING IS NOT A CHECKBOX, and this module used to prove why. Every phase below was @@ -45,18 +45,28 @@ type ValueNullPhaseStanding = PhaseWitnessEnrolled { claim: NonEmptyStr } | PhaseUnwitnessed -fn value_null_phase_eq(a: ValueNullPhase, b: ValueNullPhase) -> Bool { - match a { - PhaseCarrierWitnesses => match b { PhaseCarrierWitnesses => true _ => false } - PhaseStopProducingNull => match b { PhaseStopProducingNull => true _ => false } - PhaseDeleteNullBridges => match b { PhaseDeleteNullBridges => true _ => false } - PhaseNoneLiteralMigration => match b { PhaseNoneLiteralMigration => true _ => false } - PhaseCrossRepresentationEquality => match b { PhaseCrossRepresentationEquality => true _ => false } - PhaseArgumentCoercion => match b { PhaseArgumentCoercion => true _ => false } - PhasePrimitiveSignatureDenominator => match b { PhasePrimitiveSignatureDenominator => true _ => false } +// Equality is DERIVED from one exhaustive projection rather than written as a 7x7 comparison, +// following `std.fermi` `fermi_ordinal`. The first draft of this function matched `a` and then +// matched `b` inside each arm with a `_ => false` wildcard, which the non-fold residue detector +// correctly flagged: a wildcard over a CLOSED coproduct is un-migrated modeling, because adding an +// eighth phase would silently take the `_` arm at seven sites instead of failing to compile. Here +// a new arm makes `value_null_phase_ordinal` non-exhaustive and the compiler refuses. +fn value_null_phase_ordinal(phase: ValueNullPhase) -> Int { + match phase { + PhaseCarrierWitnesses => 0 + PhaseStopProducingNull => 1 + PhaseDeleteNullBridges => 2 + PhaseNoneLiteralMigration => 3 + PhaseCrossRepresentationEquality => 4 + PhaseArgumentCoercion => 5 + PhasePrimitiveSignatureDenominator => 6 } } +fn value_null_phase_eq(a: ValueNullPhase, b: ValueNullPhase) -> Bool { + value_null_phase_ordinal(phase: a) == value_null_phase_ordinal(phase: b) +} + // One row per phase that has a discriminating witness enrolled today. Phase A's rows are the // carrier-pinning claims in `v2.test.manual.value_null_split_witness`; Phase B's is the // construction claim added while Phase B was built (session still-swift-363, PR gunbc#9785, held). From abee235ca3a50f50cb4cef8435fd13b2de1c70e1 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 31 Aug 2026 05:57:44 +0000 Subject: [PATCH 07/28] Delete floor_probe.sh: experimental residue that hard-codes the inferior instrument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added by 32606e433e6 as a one-afternoon probe and never removed. It is the §6 experimental-residue tell: a hand-authored root-level shell script with no final consumer, raw shell implementing semantics expressible in .dag, nothing calling it and no CI reference. It does not survive the terminal architecture. The reason it must not merge is stronger than hygiene. Its roster comes from grep -E '^FAILED|FAILED in' over the run log, and this branch established that the log FOLDS distinctions the required-floor-disposition artifact splits -- log failed=427 against the artifact's 6 failed plus 421 runtime-errored. Worse, runtime-errored claims emit no FAILED line at all, so this script reports them as absent. That is exactly how this lane first reported the blast radius as one witness when it was two orders of magnitude larger. Checking it in would hand the next reader the instrument that caused that error, with the repo's implicit sanction, at the moment the better instrument was proven. If a standing floor probe is worth having it is a modeled entry point reading required_floor_disposition.tsv, authored as its own change rather than as cargo on a semantic repair. Local probes belong in the scratchpad. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK --- floor_probe.sh | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 floor_probe.sh diff --git a/floor_probe.sh b/floor_probe.sh deleted file mode 100644 index 53428998fad..00000000000 --- a/floor_probe.sh +++ /dev/null @@ -1,12 +0,0 @@ -set -o pipefail -cargo build --release -p v1-compiler --bins 2>&1 | tail -2 -./target/release/claim_executor --required-ci --source-root dag --source-root src/v2 --required-lane witnesses > /tmp/floor.log 2>&1 -echo "EXIT=$?" -echo "===SUMMARY" -grep -E "planned=|phase |ROUTED to lane" /tmp/floor.log | tail -30 -echo "===COUNTS-COMPUTED-REMOTELY" -echo "FAILED_LINES=$(grep -cE '^FAILED|FAILED in' /tmp/floor.log)" -echo "LOG_LINES=$(wc -l < /tmp/floor.log)" -echo "===FAILED-ROSTER" -grep -E '^FAILED|FAILED in' /tmp/floor.log | sed -E 's/ *\(.*//' | sort | uniq -c | sort -rn -echo "===ROSTER-END" From dd61cc03b212a109fdfe7396b45f2c2695bb5571 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 31 Aug 2026 23:08:56 +0000 Subject: [PATCH 08/28] Census: the drift gate is a second measured victim, and the composition is reachability not a new call required-witnesses-build fails after refreshing onto main (42 commits) where it passed at abee235. Not stale artifacts -- the obvious hypothesis and the wrong one. run_generated_artifact_drift_gate_body refuses with NoSuchField { Optional, shape } at extdeps.bmc.types:183, 'matches.first().shape', a field read straight off a first() result. The site predates this branch (#9238) and was present at abee235 under a green build lane. Nothing in main is defective and nothing here changed to reach it; main widened generated_artifact_gate by 98 lines and the site entered the gate's evaluation closure. Neither side is broken alone. In-class rather than a census miss: it is a value-position method-form site, a member of the 646-occurrence population this document names and deliberately does not roster at identity grain. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK --- .../plans/first-optional-divergence-census.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md index 6af2276dd94..e4df329ca83 100644 --- a/docs/plans/first-optional-divergence-census.md +++ b/docs/plans/first-optional-divergence-census.md @@ -290,6 +290,27 @@ does not distinguish a still-discriminating known-red from one that has stopped assertion, so it is the wrong field to cite for that property — a citation defect, not a safety gap, because the adjacent counter gates. +**A second measured victim outside the floor, and a reachability lesson: the drift gate.** After +refreshing onto main at `a6d6c68d4d1` (42 commits), `required-witnesses-build` fails where it passed +at `abee235`. The cause is not stale artifacts, which was the obvious hypothesis and the wrong one: + + run_generated_artifact_drift_gate_body + cause: NoSuchField { type_name: "Optional", field: "shape" } + +The site is `extdeps.bmc.types` line 183, `SurfaceShapeKnown { shape: matches.first().shape }` — a +field read directly off a `first()` result. It is a value-position method-form site, a member of the +646-occurrence population this document names and does not roster at identity grain, so it is +in-class rather than a census miss. + +**The site is not new and neither is the repair; only the composition is.** That exact line predates +this branch (added by #9238) and was present at `abee235`, where the build lane was green. Nothing in +main's 42 commits is defective and nothing in this branch changed to reach it. What changed is +REACHABILITY: main widened `generated_artifact_gate` by 98 lines, and the site entered the gate's +evaluation closure. Neither side is broken alone; together they refuse. Recorded because the +diagnostic instinct here — bisect the diff for the newly introduced call — searches for something +that does not exist, and because a site's presence in the corpus is not evidence that any executed +path reaches it. + **A measured victim outside the floor: `main_wet`.** The generated-artifact actuator `tools.generated_artifact_gate main_wet` refuses under this branch's interpreter with `CallContractMismatch { callee: "outcome_accepted", detail: "an optional value flowed into From 5f460a2e119700a9b8dc501d261cfae345904a4e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 31 Aug 2026 23:28:07 +0000 Subject: [PATCH 09/28] Correct the build-lane cause: it is the coercion arm plus the deliberate drift, not the bmc.types field read The previous commit recorded NoSuchField at extdeps.bmc.types:183 as the cause of the required-witnesses-build red and explicitly ruled out stale artifacts. Both halves were wrong. The CI log reports drifted=2 (DESIGN.md, docs/design-ledgers.md) and unadjudicated=3 (three workflow yml artifacts refusing with the same CallContractMismatch on outcome_accepted that main_wet gives) -- so the ruled-out hypothesis was half the answer and the coercion arm is the other half. The error was method, not arithmetic: I reproduced A failure locally and treated it as THE failure. The local entry evaluates the gate body as one expression and dies at its first refusal; CI adjudicates per artifact path and records an outcome for all 35. Same subject, two routes, different first failures. Also records what made the lane blocking: main #9814 restored generated-artifact drift to required CI, so the deliberate projection drift is no longer latent debt. The refusal to fabricate those bytes from a stock-interpreter seed stands. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N8xvN1T1NKiJqCUqwEmDgK --- .../plans/first-optional-divergence-census.md | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md index e4df329ca83..d1513fef301 100644 --- a/docs/plans/first-optional-divergence-census.md +++ b/docs/plans/first-optional-divergence-census.md @@ -290,26 +290,40 @@ does not distinguish a still-discriminating known-red from one that has stopped assertion, so it is the wrong field to cite for that property — a citation defect, not a safety gap, because the adjacent counter gates. -**A second measured victim outside the floor, and a reachability lesson: the drift gate.** After -refreshing onto main at `a6d6c68d4d1` (42 commits), `required-witnesses-build` fails where it passed -at `abee235`. The cause is not stale artifacts, which was the obvious hypothesis and the wrong one: - - run_generated_artifact_drift_gate_body - cause: NoSuchField { type_name: "Optional", field: "shape" } - -The site is `extdeps.bmc.types` line 183, `SurfaceShapeKnown { shape: matches.first().shape }` — a -field read directly off a `first()` result. It is a value-position method-form site, a member of the -646-occurrence population this document names and does not roster at identity grain, so it is -in-class rather than a census miss. - -**The site is not new and neither is the repair; only the composition is.** That exact line predates -this branch (added by #9238) and was present at `abee235`, where the build lane was green. Nothing in -main's 42 commits is defective and nothing in this branch changed to reach it. What changed is -REACHABILITY: main widened `generated_artifact_gate` by 98 lines, and the site entered the gate's -evaluation closure. Neither side is broken alone; together they refuse. Recorded because the -diagnostic instinct here — bisect the diff for the newly introduced call — searches for something -that does not exist, and because a site's presence in the corpus is not evidence that any executed -path reaches it. +**The build lane's real cause, and a correction to this document's first account of it.** After +refreshing onto main at `a6d6c68d4d1`, `required-witnesses-build` fails where it passed at +`abee235`. This document first recorded the cause as `NoSuchField { type_name: "Optional", field: +"shape" }` at `extdeps.bmc.types` line 183 (`matches.first().shape`), reproduced by running +`run_generated_artifact_drift_gate_body` locally, and explicitly ruled OUT stale artifacts. **That +was wrong, and the ruled-out hypothesis was half the answer.** The CI log reports: + + generated-artifact rostered=35 adjudicated=32 matches=30 drifted=2 absent=0 unadjudicated=3 + drifted DESIGN.md + drifted docs/design-ledgers.md + UNADJUDICATED .github/workflows/{witnesses,fleet-converge,fleet-desired}.yml + — CallContractMismatch { callee: "outcome_accepted", detail: "an optional value flowed + into non-optional parameter 1 ('value') (empty Optional at runtime)" } + +Two classes, both this branch's: + +- **3 unadjudicated** — this branch's own argument-coercion arm, the same refusal `main_wet` gives. + Those artifacts cannot be generated at all, so the gate reaches no verdict on them. +- **2 drifted** — `DESIGN.md` and `docs/design-ledgers.md`, the deliberate unregenerated projections + recorded below. + +**Why the local reproduction pointed elsewhere.** The local entry evaluates the gate body as one +expression and dies at the first refusal it meets, which is the `bmc.types` field read; CI's build +lane adjudicates PER ARTIFACT PATH and so records a per-path outcome for all 35. Same subject, two +routes, different first failures — and reproducing *a* failure locally is not the same as +reproducing *the* failure. The `NoSuchField` site is real and remains an in-class value-position +victim; it is simply not why the lane is red. + +**What changed to make this blocking: main #9814, "Restore generated-artifact drift to required CI".** +The generated-artifact drift check was a declared rung drop and is now a required check again. So the +drift recorded below stopped being latent debt that dissolves with the repair and became an active +blocker. That does not change the refusal to fabricate the bytes from a stock-interpreter seed — a +green gate over a live divergence is worse than a red one — but it does mean this branch cannot show +a green build lane until the repair completes, for a second independent reason. **A measured victim outside the floor: `main_wet`.** The generated-artifact actuator `tools.generated_artifact_gate main_wet` refuses under this branch's interpreter with From 91b4f5e70e9115b277443def08c30ea3be6cc8a8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 08:27:59 +0000 Subject: [PATCH 10/28] The coercion fired on a free type variable: silently unwrapping Present and refusing Absent Constructing the Optional for `first`/`last`/`get`/`lookup` created a second obligation -- an `Optional` argument now meets a parameter declared `T` -- and the coercion this branch added to satisfy it was wrong on GENERIC formals. `param_declares_required_value` asked three questions of a parameter's declared type and none of them could see a free type variable. For `fn outcome_accepted(value: T)`, `T` is not spelled `Optional`, carries no `CardOptional` flag, and is not the parameter's own name, so the predicate answered "required" for a formal that declares nothing about cardinality at all. `Optional` is a legitimate instantiation of `T`, not a cardinality escape. BOTH ARMS WERE WRONG, and the quiet one is the worse one. `Present` was UNWRAPPED into the callee: a caller whose `T = Optional` had the callee receive `Int`, which is precisely the silent semantic divergence this branch exists to remove, reintroduced at a new seam by the repair itself. `Absent` was REFUSED with a located `CallContractMismatch`, which is loud but equally false, and it took down the generated-artifact regen actuator -- three workflow projections reached no verdict in the required build lane for this reason. THE FIX READS THE DECLARATION, NOT THE SPELLING. `v1.compiler.parse` `parse_fn_body_from_prefix` builds a fn node's `params` as `concat(type_params, value_params)`, and a type parameter is exactly the entry whose declared type is its own name -- the same shape the positional-parameter filter beside it already uses. Naming that set is what lets the predicate tell a free type variable from a declared non-optional formal, so the coercion declines where the declaration is silent instead of fabricating a requirement. This is the producer that `gunbc.recurring_failure_mode` `mitigation_injected_where_judgment_declined` names as its trigger, arrived at from the interpreter side: that row was filed against the Rust emitter for the same seam and the same callee. EVIDENCE, both enrolled and both discriminating. Measured on the head that carried the defect, a `Present` into a free type variable reported "UNWRAPPED -- arrived as a bare value" and an `Absent` raised CallContractMismatch; both now report the Optional whole. They stay enrolled as regression controls beside the existing positive control rather than retiring with the climb. NOT FIXED HERE, and unchanged: builtin calls bypass this path entirely, so `parse_int(s: fields.first())` still receives the Variant raw. That is the declared grounding hold -- the builtin registry maps a name to a return type with no parameter list -- and it has 9 located sites inside the regen actuator's own import closure. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- ...rst_optional_construction_witness_test.dag | 29 +++++++++++++ src/v1/stage0/src/v1_interpreter.rs | 41 +++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/dag/test/claim/first_optional_construction_witness_test.dag b/dag/test/claim/first_optional_construction_witness_test.dag index a9944d2dabf..91256283200 100644 --- a/dag/test/claim/first_optional_construction_witness_test.dag +++ b/dag/test/claim/first_optional_construction_witness_test.dag @@ -83,3 +83,32 @@ test fn get_past_the_end_is_absent_and_get_in_bounds_is_present() -> Bool { (match ["a"] |> get(1) { Present { value: _ } => false Absent => true }) && (match ["a"] |> get(0) { Present { value: s } => s == "a" Absent => false }) } + +// THE COERCION THIS REPAIR ADDED MUST NOT FIRE ON A FREE TYPE VARIABLE. Constructing the Optional +// created a second obligation: an argument that is now `Optional` meets a parameter declared +// `T`, so the interpreter grew the coercion the Rust arm already had. Applied to a GENERIC formal +// that rule is wrong in both directions, because `T` declares nothing about cardinality and +// `Optional` is a legitimate instantiation rather than a cardinality escape. Measured on the +// first head that carried the coercion: `Present` was SILENTLY UNWRAPPED into the callee -- the +// caller's `T = Optional` arrived as `Int`, which is the same silent divergence this whole +// module exists to remove, pointed at a new seam -- and `Absent` was REFUSED outright with a +// `CallContractMismatch`, which took down the generated-artifact regen actuator. The predicate now +// reads the fn's own declared type-parameter list, so a free type variable is not a required +// formal. These two are the discriminating REDs for that seam and stay enrolled after the climb. +fn observe_arrival(value: T) -> String { + match value { + Present { value: _ } => "PRESENT" + Absent => "ABSENT" + _ => "UNWRAPPED" + } +} + +// RED against the unwrapping arm, which reported "UNWRAPPED": the callee received the bare element. +test fn a_present_optional_into_a_free_type_variable_arrives_whole() -> Bool { + observe_arrival(value: [7] |> first) == "PRESENT" +} + +// RED against the refusing arm, which raised CallContractMismatch on a legitimate instantiation. +test fn an_absent_optional_into_a_free_type_variable_is_not_refused() -> Bool { + observe_arrival(value: [] |> first) == "ABSENT" +} diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index d39904b5683..b2a53533ea8 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -872,13 +872,29 @@ fn classify_optional_arg(val: Value, ctx: &InterpContext) -> OptionalArg { /// `rust_call_arg_fail_closed_unwrap` makes, and for the same reason: unwrapping into /// `witness_from_optional(opt: Optional)` would strip the very value the callee exists to /// inspect. -fn param_declares_required_value(param: &Rc, pname: &str, ctx: &InterpContext) -> bool { +/// +/// A FREE TYPE VARIABLE DECLARES NOTHING ABOUT CARDINALITY, so it is not a required formal. For +/// `fn outcome_accepted(value: T)`, `T` instantiating to `Optional` is a legitimate +/// instantiation and not a cardinality escape; treating it as required both unwrapped `Present` +/// into the callee (a SILENT corruption of the very kind this repair exists to remove — the callee +/// declared `Outcome>` and would have received `Outcome`) and refused `Absent` +/// outright. That is the already-rostered `mitigation_injected_where_judgment_declined` shape, and +/// this predicate is the producer its row names as the trigger: it distinguishes a free type +/// variable from a declared non-optional formal, reading the fn's own declared type-parameter list +/// rather than guessing from the spelling of a name. +fn param_declares_required_value( + param: &Rc, + pname: &str, + type_param_names: &[String], + ctx: &InterpContext, +) -> bool { match param.children.first() { Some(type_expr) => { let type_name = authored_name_at(ctx.si(), type_expr.clone()); type_name != pname && type_expr.return_cardinality != Cardinality::CardOptional && type_name != "Optional" + && !type_param_names.iter().any(|t| t == &type_name) } None => false, } @@ -3885,7 +3901,8 @@ pub struct InterpContext { // key for the ctx's lifetime (as PureCallMemo.keepalive_fns / EvalRecomputeTrace.keepalive_fns // / EvalCallMemo.keepalive_fns), and the cache dies with the ctx (as data_cache). // Value = (filtered named-param list, all-param list), matching call_function's two uses. - param_name_cache: std::cell::RefCell, Vec)>>>, + param_name_cache: + std::cell::RefCell, Vec, Vec)>>>, param_name_cache_keepalive: std::cell::RefCell>>, // Same chokepoint, ExprVar arm: eval_var rebuilt the name String from its span // (expr_var_name_at) and re-interned it (ctx.sym) per read. The interned Symbol is memoized @@ -4799,7 +4816,22 @@ fn call_function_inner( }) .map(|(i, _)| all[i].clone()) .collect(); - let c = Rc::new((filtered, all)); + // A `fn f(v: T)` node carries `params = concat(type_params, value_params)` + // (`v1.compiler.parse` `parse_fn_body_from_prefix`), and a TYPE parameter is exactly + // the entry whose declared type is its own name -- the same shape `filtered` uses to + // drop them from the positional list. Naming that set is what lets the coercion below + // tell a declared non-optional formal from a FREE type variable. + let type_params: Vec = fn_node + .params + .iter() + .enumerate() + .filter(|(i, p)| match p.children.first() { + Some(type_expr) => authored_name_at(ctx.si(), type_expr.clone()) == all[*i], + None => true, + }) + .map(|(i, _)| all[i].clone()) + .collect(); + let c = Rc::new((filtered, all, type_params)); ctx.param_name_cache_keepalive .borrow_mut() .push(fn_node.clone()); @@ -4809,6 +4841,7 @@ fn call_function_inner( }; let param_names: &Vec = &cached_params.0; let all_param_names: &Vec = &cached_params.1; + let type_param_names: &Vec = &cached_params.2; let mut bindings = HashMap::new(); if !args.is_empty() { @@ -4914,7 +4947,7 @@ fn call_function_inner( // panics. for (i, param) in fn_node.params.iter().enumerate() { let pname = &all_param_names[i]; - if !param_declares_required_value(param, pname, ctx) { + if !param_declares_required_value(param, pname, type_param_names, ctx) { continue; } let key = ctx.sym(pname); From 1b0f02a9ba575c253c018ee6410984052eac6cfd Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 08:43:12 +0000 Subject: [PATCH 11/28] Locate builtin refusals at the call node: `parse_int expects a string argument, got Variant` said nothing about where `eval_builtin` receives a name and a value list and has no span, so an argument-shape refusal reached the operator as a typed sentence with no file and no line. DESIGN section 5 admits typed AND LOCATED; typed alone is half of it, and the half that is missing is the one a reader needs to act. It became load-bearing in this branch. Constructing the `Optional` for `first`/`last`/`get` started routing Optionals into builtins, and a builtin carries a return type with no declared parameter list, so no coercion can be derived for it and the refusal is the ONLY signal the reader gets. An unlocated one hands them a corpus to search. The call node is in scope at the builtin dispatch site and carries the span, so the location is attached at the one seam that knows it, in the `file:offset` form the interpreter's other located diagnostics already use. NARROW BY CONSTRUCTION, and deliberately so: this locates refusals raised by builtin dispatch and nothing else. Interpreter-wide diagnostic location is a separate class with its own trigger and is not absorbed into this change. RECEIPT, and it corrects something I would otherwise have reported wrongly. The same actuator run, twice, same binary and same arguments, refuses in two DIFFERENT places: once `NoSuchField { type_name: "Optional", field: "shape" }` and once `dag/extdeps/ollama/capability.dag:4740: parse_int expects a string argument, got Variant`. The population of un-migrated consumers is walked in a map order that is not stable across runs, so any single run names one member of it. Without the location I would have read the second run as the first defect having moved. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- src/v1/stage0/src/v1_interpreter.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index b2a53533ea8..7d81b11a724 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -7095,7 +7095,22 @@ fn eval_call(node: &Rc, env: &Rc, ctx: &InterpContext) -> InterpResul v1_native_intercept_arms!(v1_native_intercept_dispatch, func_name, args, env, ctx); - if let Some(result) = eval_builtin(&func_name, &args, ctx)? { + // A BUILTIN'S REFUSAL MUST SAY WHERE IT IS. `eval_builtin` receives a name and values and + // has no span, so an argument-shape refusal read `parse_int expects a string argument, got + // Variant` with no file and no line — typed but UNLOCATED, which DESIGN §5 does not admit: + // every path fails with a typed, LOCATED diagnostic. It became load-bearing when + // constructing the `Optional` for `first`/`last`/`get` started routing Optionals into + // builtins, whose registry declares a return type and no parameter list, so no coercion can + // be derived for them and the refusal is the only signal a reader gets. The call node is in + // scope here and carries the span, so the location is attached at the one seam that knows + // it. NARROW BY CONSTRUCTION: this locates refusals raised by builtin dispatch only, and + // the general interpreter-wide locator is a separate class with its own trigger. + if let Some(result) = eval_builtin(&func_name, &args, ctx).map_err(|e| match e { + InterpError::TypeError { msg } => InterpError::TypeError { + msg: format!("{}:{}: {}", node.span.file, node.span.start, msg), + }, + other => other, + })? { return Ok(result); } } From 62f11ae1d3cf44d57ddcbc6f95b5153ad78ec638 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 08:55:04 +0000 Subject: [PATCH 12/28] Census superseded: its population was a spelling, and the larger half of the operation was never measured The census declared its population as "every terminal `|> first` occurrence". That is a SYNTAX, not the operation. The corpus writes the same declared operation two ways and the pipe form is the smaller one: 224 pipe-form occurrences against 661 `.first()`, 19 `.last()`, 41 `.lookup()` and 7 `.get()` across 183 files. Every consumer that has actually failed in production is in the half the census never looked at -- `parse_int(s: fields.first())`, `matches.first().shape`, `lines.first() == schema`. RE-DERIVED AGAINST THE POPULATION THAT ACTUALLY GATES LANDING: the 944-module import closure of `tools.generated_artifact_gate`, classified by what CONSUMES the result. 270 consumer sites -- 137 match-eliminated, 96 propagated, 14 compared to a bare value, 11 declared-fn arguments, 5 field accesses on the result, 3 builtin arguments, 1 method call on the result. Twenty-three are un-migrated and are why the closure will not load. THE CLASS THE CENSUS NEVER NAMED IS THE LARGEST AND THE WORST. Fourteen sites compare a `first()` result to a bare value. Comparison is the one shape with no pattern for a compensation arm to intercept -- which the census's own probe table establishes from the other side -- and nine of the fourteen are merge-admission receipt parsing, where a schema check that silently answers false is exactly the failure this branch exists to remove. The mechanism, root cause, two-sided argument and probe table are unaffected; they are about the mechanism, not the population. The disposition counts must not be cited as a population or as completeness, and the document now says so at the head of the section that carries them. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- .../plans/first-optional-divergence-census.md | 29 +++++++++++++++++++ false | 0 true | 0 3 files changed, 29 insertions(+) create mode 100644 false create mode 100644 true diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md index d1513fef301..cc98910ada9 100644 --- a/docs/plans/first-optional-divergence-census.md +++ b/docs/plans/first-optional-divergence-census.md @@ -45,6 +45,35 @@ agree. So the entire match-scrutinee population was green on a divergence it is to observe. **A witness whose RED is not authorable is a decoration**; for this class the match-scrutinee shape is exactly that, and the two shapes in the table are the ones that are not. +## SUPERSEDED: this census's population was keyed on a SPELLING, and missed the larger half + +**Read this before citing anything below it.** The census's declared population is "every terminal +`|> first` occurrence in `dag/**.dag` and `src/v2/**.dag`". That is a *syntax*, not the operation. +The corpus writes the same declared operation two ways, and the pipe form is the smaller: **224** +pipe-form occurrences against **661** `.first()`, **19** `.last()`, **41** `.lookup()` and **7** +`.get()` across 183 files. Everything below measured one spelling of an operation whose identity +`dag/std/algebra.dag` already fixes. + +Every consumer that has actually failed in production is in the half this document never looked at +— `parse_int(s: fields.first())`, `matches.first().shape`, `lines.first() == schema`. + +A re-derivation scoped to the 944-module import closure of `tools.generated_artifact_gate` — the +population that actually gates landing — finds **270 consumer sites**, classified by what consumes +the result: 137 match-eliminated, 96 propagated, **14 compared to a bare value**, 11 declared-fn +arguments, **5 field accesses on the result**, **3 builtin arguments**, **1 method call on the +result**. Twenty-three of those are un-migrated and block the closure from loading. + +**The class this document never named is the largest and the worst of them.** Fourteen sites compare +a `first()` result to a bare value with `==` / `!=`. None of the dispositions below covers it, and +comparison is the shape with no pattern for a compensation arm to intercept — which this very +document establishes from the other side, in the probe table above, where the comparison case +diverges. Nine of the fourteen are merge-admission receipt parsing. + +**What stays true:** the divergence, the root cause, the two-sided argument, and the probe table are +all unaffected — they are about the mechanism, not the population. **What must not be cited:** the +disposition counts below, as a population or as a completeness claim. A census that names its shapes +gets cited as complete whether or not it says it is. + ## The census — a disposition roster, not a shape tally Population: every terminal `|> first` occurrence in `dag/**.dag` and `src/v2/**.dag` on `main`. diff --git a/false b/false new file mode 100644 index 00000000000..e69de29bb2d diff --git a/true b/true new file mode 100644 index 00000000000..e69de29bb2d From 55cf9462a532fe75de1f3d1d7eed74d631225d0b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 09:22:09 +0000 Subject: [PATCH 13/28] Refusal before migration: `Optional` against a bare value under `==` fabricated `false` instead of refusing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Constructing the `Optional` for `first`/`last`/`get`/`lookup` made every consumer that COMPARES the result to a bare value compare across two representations. `Value::eq` cannot decide those, so the comparison silently answered `false`. DESIGN §5 forbids exactly that: a failure arm must refuse, never fabricate a plausible answer. This is the second silent arm this repair produced -- the free-type-variable unwrap was the first -- and the reason is structural: a change to what values ARE turns every consumer of the old representation into a seam that must be made loud. MEASURED BEFORE THE WALL, both shapes silent: `["schema-v2", "body"].first() == "schema-v2"` -> false `[] |> first == none` -> false Nine of the fourteen comparison sites in the generated-artifact gate's 944-module import closure are merge-admission receipt schema checks, where a quiet `false` rejects a valid receipt with no diagnostic -- on the path every other lane merges through. The wall makes them loud, and it makes the remaining population self-announcing rather than something a textual census has to find. `CrossRepresentationEquality` already existed and did not fire here. It does now. THE `x == none` CARVE-OUT WAS DELETED AFTER MEASURING IT. The first version of this wall spared `Optional` against `Value::Null`, on the reasoning that `none` evaluates to the Null carrier and refusing it would break the corpus's emptiness idiom rather than the bug. The control said otherwise: `[] |> first == none` already answered `false`, so the carve-out was preserving a silent false rather than a working test. It now refuses with its own sentence naming the two carriers of absence. It stays narrow by construction, not by exception: a declared `T?` whose absent state IS `Value::Null` still compares `Null == Null` and never reaches the check, so only a CONSTRUCTED `Optional` meeting the Null carrier fires -- exactly the un-migrated population. EVIDENCE. Three fixture arms, each discriminating: the bare-value straddle refuses, the Null straddle refuses with the distinct sentence, and the positive control -- `Optional` against `Optional` -- still compares and answers true. The positive control is ENROLLED here. The two REDs are fixture-measured and NOT enrolled, which the witness module states rather than glosses: a `test fn` returns `Bool` and an interpreter refusal aborts evaluation, so this harness cannot express "this expression refuses". A harness that can catch a refusal and assert its reason is this seam's next-rung trigger. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- ...rst_optional_construction_witness_test.dag | 19 +++++++ src/v1/stage0/src/v1_interpreter.rs | 53 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/dag/test/claim/first_optional_construction_witness_test.dag b/dag/test/claim/first_optional_construction_witness_test.dag index 91256283200..344c7a6793c 100644 --- a/dag/test/claim/first_optional_construction_witness_test.dag +++ b/dag/test/claim/first_optional_construction_witness_test.dag @@ -112,3 +112,22 @@ test fn a_present_optional_into_a_free_type_variable_arrives_whole() -> Bool { test fn an_absent_optional_into_a_free_type_variable_is_not_refused() -> Bool { observe_arrival(value: [] |> first) == "ABSENT" } + +// THE WALL THAT KEEPS THE COMPARISON SEAM FROM ANSWERING QUIETLY, and its positive control. +// Constructing the `Optional` made every consumer that COMPARES a `first()` result to a bare value +// compare across two representations, and `Value::eq` cannot decide those: measured before the +// wall, `["schema-v2", "body"].first() == "schema-v2"` answered FALSE with no diagnostic, and +// `[] |> first == none` answered FALSE too. Nine of the fourteen such sites in the generated-artifact +// gate's import closure are merge-admission receipt schema checks, where a quiet `false` rejects a +// valid receipt. A failure arm that fabricates a plausible answer instead of refusing is what +// DESIGN section 5 forbids outright, so both shapes now raise `CrossRepresentationEquality`. +// +// THE TWO REDS ARE FIXTURE-MEASURED, NOT ENROLLED HERE, AND THAT IS STATED RATHER THAN GLOSSED: a +// `test fn` returns `Bool` and an interpreter refusal aborts evaluation, so this harness cannot +// express "this expression refuses". Enrolling them needs a harness that can catch a refusal and +// assert its REASON, which is this seam's next-rung trigger. What IS enrolled is the control below, +// and it is the one that keeps the wall from being over-broad — a wall that refused every +// comparison would also pass a red-only check. +test fn an_optional_compared_against_an_optional_is_not_a_straddle() -> Bool { + (["a"] |> first) == Present { value: "a" } +} diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 7d81b11a724..891d479ba2b 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -5679,6 +5679,9 @@ fn eval_binop(op: &BinOp, left: Value, right: Value, ctx: &InterpContext) -> Int if let Some(detail) = cross_family_content_hash_straddle(&left, &right) { return Err(InterpError::CrossRepresentationEquality { detail }); } + if let Some(detail) = optional_against_bare_straddle(&left, &right, ctx) { + return Err(InterpError::CrossRepresentationEquality { detail }); + } } let result = if matches!(op, BinOp::Eq) { equal @@ -5720,6 +5723,56 @@ fn eval_binop(op: &BinOp, left: Value, right: Value, ctx: &InterpContext) -> Int } } +/// An `Optional` meeting a bare `T` under `==` / `!=`. +/// +/// `Value::eq` cannot decide these — a `Present { value: "x" }` is simply not equal to `"x"` — so +/// the comparison SILENTLY ANSWERS FALSE, which DESIGN §5 forbids outright: a failure arm must +/// refuse, never fabricate a plausible answer. It became reachable when `first`/`last`/`get` +/// started constructing the `Optional` their `dag/std/algebra.dag` rows declare, because every +/// consumer written against the old raw-element answer now compares across representations. +/// Measured before this wall: `["schema-v2", "body"].first() == "schema-v2"` returned `false`, and +/// nine such sites are merge-admission receipt schema checks, where a quiet `false` rejects a valid +/// receipt with no diagnostic. +/// +/// ONE SHAPE IS NOT A STRADDLE: Optional against Optional is an ordinary comparison of one +/// representation with itself. +/// +/// THE `x == none` IDIOM IS A STRADDLE AND REFUSING IT IS THE POINT, which is the opposite of what +/// this function first did. `none` evaluates to the host `Value::Null` carrier, so the carve-out +/// that spared it looked like protecting a working test. It was measured, and it was not working: +/// `[].first() == none` answered FALSE, because a constructed `Absent` variant is not `Value::Null`. +/// Sparing it preserved a silent false — the very class this wall exists to stop — so the spare is +/// deleted and the two-carrier case refuses with its own sentence. +/// +/// It stays NARROW by construction rather than by exception. A declared `T?` field whose absent +/// state IS `Value::Null` still compares `Null == Null` and never reaches here; only a CONSTRUCTED +/// `Optional` meeting the Null carrier fires, which is exactly the un-migrated population. +fn optional_against_bare_straddle(a: &Value, b: &Value, ctx: &InterpContext) -> Option { + let (a_opt, b_opt) = (is_optional_value(a, ctx), is_optional_value(b, ctx)); + if a_opt == b_opt { + return None; + } + if matches!(a, Value::Null) || matches!(b, Value::Null) { + return Some(format!( + "{} vs {} — a constructed `Absent`/`Present` and the host Null carrier are two \ + representations of ABSENCE, so `==` would silently fabricate `false` (DESIGN §5): \ + measured, `[].first() == none` answered false. Eliminate the `Optional` with `match` \ + (`Present {{ value: v }}` / `Absent`) instead of testing it against `none`.", + describe_repr(a), + describe_repr(b), + )); + } + Some(format!( + "{} vs {} — an `Optional` and a bare `T` are two representations of one value, \ + so `==` would silently fabricate `false` (DESIGN §5). `dag/std/algebra.dag` declares \ + `first`/`last`/`get`/`lookup`/`map_get` as returning `Optional<..>`; eliminate it with \ + `match` (`Present {{ value: v }}` / `Absent`) and compare `v`, or compare against an \ + `Optional` on both sides.", + describe_repr(a), + describe_repr(b), + )) +} + fn cross_representation_numeric_straddle(a: &Value, b: &Value) -> Option { match (a, b) { (Value::Int(_) | Value::Float(_), v @ Value::Variant { .. }) From cf930855b7988529a1dcf4c0ceb64fd707d20e41 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 10:36:26 +0000 Subject: [PATCH 14/28] Census: 23 is a lower bound, and the completion criterion is that the closure LOADS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The re-derivation's largest non-`match` bucket is 31 sites where a one-line textual reader cannot decide whether `algorithm: parts.first(),` is a record-field assignment or a function argument, because the opening brace is on a previous line. That bucket names its own undecidability rather than being guessed into whichever class looked likelier — a guessed split would have produced a tidier table no reader could question. Record-field assignment into a declared non-optional field is a real consumer class, it is somewhere inside those 31, and it cannot be counted from source text. So 23 is what is KNOWN to block the closure, never the population, and this document now says so where the number appears. The consequence is a better completion criterion than any count: THE CLOSURE LOADS. It is self-verifying, needs no population known in advance, and each fix lets the closure load further so the next refusal names the next site — exhaustive by construction and terminating exactly when the property we want is true. "The known set is repaired" and "the migration is complete" are two different assertions, and the document now requires reports to say which. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- .../plans/first-optional-divergence-census.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md index cc98910ada9..e8b8976b35b 100644 --- a/docs/plans/first-optional-divergence-census.md +++ b/docs/plans/first-optional-divergence-census.md @@ -61,7 +61,24 @@ A re-derivation scoped to the 944-module import closure of `tools.generated_arti population that actually gates landing — finds **270 consumer sites**, classified by what consumes the result: 137 match-eliminated, 96 propagated, **14 compared to a bare value**, 11 declared-fn arguments, **5 field accesses on the result**, **3 builtin arguments**, **1 method call on the -result**. Twenty-three of those are un-migrated and block the closure from loading. +result**. Twenty-three of those are un-migrated and **known** to block the closure from loading. + +**Twenty-three is a LOWER BOUND, never a population, and it must not be cited as one.** The +classification's largest non-`match` bucket is 31 sites where a one-line textual reader *cannot +decide* whether `algorithm: parts.first(),` is a record-field assignment or a function argument, +because the opening brace is on a previous line — so that bucket names its own undecidability rather +than being guessed into whichever class looked likelier. Record-field assignment into a declared +non-optional field is a real consumer class (`tested_head_sha: lines.skip(n: 4).first()` in +`gunbc.merge_admission_produce`), it is somewhere inside those 31, and it cannot be counted from +source text. Add 22 `let` bindings whose consumers are not followed here. What would settle it is not +a better reader — it is the compiler, which knows every declared type. + +**The completion criterion is therefore not "23 sites fixed" — it is THE CLOSURE LOADS.** That is +self-verifying and needs no population known in advance, and each fix lets the closure load further +so the next refusal names the next site: exhaustive by construction, every step verified, and it +terminates exactly when the property we want is true. A claim that the known set is repaired and a +claim that the migration is complete are two different assertions, and anything reporting on this +work must say which one it is making. **The class this document never named is the largest and the worst of them.** Fourteen sites compare a `first()` result to a bare value with `==` / `!=`. None of the dispositions below covers it, and From 5a0ca7f3408caebcba59a600d3451883a4cb2f3d Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 10:38:37 +0000 Subject: [PATCH 15/28] Census: state the rule that covers both times this document's numbers were wrong MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A catch-all is only visible once something has fallen out of it, so the first census of anything should be assumed to have one. It goes here because it is what a reader needs in order to weigh every number below it, and because this document is its own two receipts. First layer: the population was a SPELLING — "every terminal `|> first` occurrence" is a syntax, not the operation, and the method-call form is the larger by far. Second layer: the re-derivation that fixed that produced a tidy table with six named classes and a 96-site bucket called `propagated / returned onward, declared type honest`, which is not a class but what was left after five were named, described in a way that reassures. It had already eaten record-field assignment into a declared non-optional field. THE CORRECTION WAS NOT RESTRAINT, and recording it as restraint would leave a virtue nobody can act on. Three classifiers were written and the first two both produced the tidy table; the bucket that now names its own undecidability appeared only after a SPECIMEN fell out of the catch-all and showed what it was hiding. Without it the tidy table would have shipped a third time. The three operative rules are stated in the document: hunt the catch-all before a reader finds it, with the tell being a bucket defined by what it is NOT or described with a reassurance; name undecidability rather than the likelier class and say what would decide it; and report a lower bound with a self-verifying completion criterion rather than a number. The class also belongs in `gunbc.recurring_failure_mode`, and is deliberately NOT filed there yet: that carrier is mid-merge-forward under another lane, and a third lane appending to it today would be an instance of the failure it rosters. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- .../plans/first-optional-divergence-census.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/plans/first-optional-divergence-census.md b/docs/plans/first-optional-divergence-census.md index e8b8976b35b..8932af75ca0 100644 --- a/docs/plans/first-optional-divergence-census.md +++ b/docs/plans/first-optional-divergence-census.md @@ -73,6 +73,39 @@ non-optional field is a real consumer class (`tested_head_sha: lines.skip(n: 4). source text. Add 22 `let` bindings whose consumers are not followed here. What would settle it is not a better reader — it is the compiler, which knows every declared type. +### How this document's numbers went wrong twice, and the rule that covers both + +**A catch-all is only visible once something has fallen out of it, so the first census of anything +should be assumed to have one.** This is stated here because it is the rule a reader needs in order +to weigh every number below it, and because this document is its own two receipts. + +*First layer — the population was a spelling.* The census declared itself over "every terminal +`|> first` occurrence". That is a syntax, not the operation, and the method-call form is the larger +by far. Everything that has actually failed in production was in the half never looked at. + +*Second layer — the residue wore a class name.* The re-derivation that fixed the spelling produced a +tidy table with six named classes and a 96-site bucket called `propagated / returned onward, +declared type honest`. That is not a class; it is what was left after five were named, described in +a way that reassures. It had already eaten a real one — record-field assignment into a declared +non-optional field. + +The correction was not judgement or restraint. Three successive classifiers were written; the first +two both produced the tidy table. The bucket that now names its own undecidability appeared only +after a **specimen** — `tested_head_sha: lines.skip(n: 4).first()` in `gunbc.merge_admission_produce` +— fell out of the catch-all and showed what it was hiding. Without that specimen the tidy table +would have shipped a third time. + +So the operative rules, both earned here rather than reasoned: + +- Go looking for the catch-all before someone else finds it. The tell is a bucket defined by what it + is **not**, or one whose description is a reassurance — *honest*, *unaffected*, *fine*. +- Where the instrument genuinely cannot decide a position, name the bucket after the + **undecidability** rather than the likelier class, and say what *would* decide it. Here that is + the compiler, which knows every declared type; a guessed split would have produced a tidier table + no reader could question. +- Report the surviving count as a **lower bound**, and make the completion criterion a + self-verifying property rather than a number. + **The completion criterion is therefore not "23 sites fixed" — it is THE CLOSURE LOADS.** That is self-verifying and needs no population known in advance, and each fix lets the closure load further so the next refusal names the next site: exhaustive by construction, every step verified, and it From c2be67315a85a2d5b668db082268b443d4cea79e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 10:50:23 +0000 Subject: [PATCH 16/28] Delete two empty files a `git add -A` swept in `true` and `false`, zero bytes each, added in 62f11ae1d3c. They are shell redirect residue -- not on `main`, not referenced by anything, and they reached the branch because I staged with `git add -A` without reading what it had picked up. Nothing to preserve; the fix is the deletion. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- false | 0 true | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 false delete mode 100644 true diff --git a/false b/false deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/true b/true deleted file mode 100644 index e69de29bb2d..00000000000 From eb76d8322ec23ffac088db227a75a5cb15b86a4a Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 12:07:53 +0000 Subject: [PATCH 17/28] Regenerate the ledger projection after the roster merge --- docs/design-ledgers.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/design-ledgers.md b/docs/design-ledgers.md index 1f5d6a45011..cc1736372ce 100644 --- a/docs/design-ledgers.md +++ b/docs/design-ledgers.md @@ -43,6 +43,7 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **unbacked execution claim** (prose asserts a live executing relation — this runs on X, X reads these fields, Y already resolves Z — that no authority backs, and a reader forms the premise and plans against it. §4b's CI rung drop names the harm PREMISE CONTAMINATION in its own words; this row is that harm as a general class, since the drop is about one paragraph in the canonical authority and the class is about every carrier. **THE ORIGINS ARE A FIELD OF THIS ONE ROW RATHER THAN SEPARATE ROWS, because the recognition rule is identical across them** — resolve the claim against the authority that owns the relation — **but the REMEDY BRANCHES ON THE ORIGIN, and a remedy applied without reading the origin manufactures new false statements.** Three origins are ATTESTED here and a fourth is conceivable but was NOT found. ORIGIN 1, EXECUTOR DELETED: the claim was true and its executor was removed. Specimen: `gunbc.floor_component_receipt_document` recited that an alert downloads an artifact named `floor-component-receipt` from a run id it is given and reads four named fields; that alert was `falsifier-alert.yml`, deleted 2026-08-15, and a lane read the module, believed the transport live, planned against it, and was corrected by its reviewing authority — the cost is measured, not hypothetical. Second specimen, same origin: `gunbc.ci_failure_class` stated its dissolution trigger as calling `classify_failure_reason` *the way claim_executor already resolves* `gunbc.floor_component_receipt`; `write_floor_component_receipt_at` did exactly that from 2026-07-30 (gunbc#7467) until gunbc#9228 deleted it on 2026-08-25. ORIGIN 2, THE CITED SYMBOL DIED WHILE ITS CONTENT SURVIVED AS AN ANNOTATION, and this one has a corpus-wide producer: `v1_compiler.cli_run` twice cited `floor_component_resource_checkpoint_note` and `floor_component_phase_journal_scaffold_note`, which were real `data …: String` declarations in `gunbc.floor_component_receipt` until the 2026-08-30 prose-bankruptcy sweep (gunbc#9752) converted module-scope commentary rows into §4c annotations corpus-wide. §4c makes an annotation uncitable by construction — `v1_compiler.cli_run` `annotation_erased_scan_text` feeds the semantic passes, so annotation text is not present in the tree the citation harvester walks. **THE CONSEQUENCE IS THE OPPOSITE OF THE ONE THIS ROW FIRST DREW, AND THE CORRECTION IS THE INSTRUCTIVE PART.** The first revision reasoned that the sweep therefore broke every citation of a converted row, making the population the sweep's own diff. Measured by a separate lane (gunbc XL-0-CITE, 2026-09-01) against `v1_compiler.declaration_index`, whose only citation producers are `citation_from_record_literal` and `citation_from_constructor_call`: the typed-citation breakage is **ZERO**, computed both today and as of the sweep commit, on a control that discriminates in both directions. A plain name in annotation prose was never a citation, so the sweep could not break one. What that leaves is a REAL class with an honest ceiling rather than a defect population: these citations bind READERS and no mechanism, so they sit in §4b's *outside the modeled guarantee* column — observed and repaired by reading, never gated — and calling them a dangling-citation population would have been rung inflation about a check that by construction cannot see them. The two `cli_run` sites are genuine instances of the harm and there is no mechanical census that would have found them. ORIGIN 3, WRONG AUTHORITY FOR SOMETHING THAT STILL EXECUTES: `dag/test/claim/instrument_sandbox_witness_test` says its live half `runs on the falsifier lane`; that half sits on `FalsifierSubstrateLongLane`, which `std.witness_admission` `witness_cadence_has_scheduled_route` reports routeless — but it also carries a local recipe, and `OfflineLocalRecipe` HAS a route, so the relation is live and only the named authority is wrong. THE FOURTH, NEVER BACKED AT ALL, IS NOT ATTESTED IN THIS CORPUS and is recorded as unmeasured rather than as a population. **THIS ROW IS NOT THE AUTHORITY ON ANY POPULATION, and saying so is the point:** `gunbc.deleted_cadence_reference_census` already owns the falsifier-cadence instance of origin 1 — twelve sites, a `DeletedCadenceReferenceStanding` vocabulary whose `PremiseInvalidated` arm is exactly this class, and one `GuaranteeRungDrop` filed once for the shared executor rather than per site. A pattern row that restated its population would be a second authority for a fact that already has one, which is the §3 violation this row's own remedy is meant to prevent; so the census is cited here and nothing about its contents is copied. What belongs HERE is only the recognition rule and the remedy arms, which generalize past the falsifier. **THE MECHANICAL TEST for finding one is sharper than asking whether the verb is runs or is enrolled: resolve the claim against the authority that owns the relation, and ask whether the prose implies a live route for something that authority says has none.** The naive runs/enrolled discriminator loses one arm and it is the common one — `Enrolled on falsifier_rehomed_bin_wet_entries (nightly batch 5)` is nominally an enrollment claim, which is TRUE, but `nightly` asserts a cadence that executes and `FalsifierRehomedBinWet` is routeless, so the reader forms the false premise anyway. Three outcomes per line: asserts execution (contaminating), asserts enrollment only (true, leave it), asserts enrollment while implying a live cadence (contaminating). **THE REMEDY HAS THREE ARMS, KEYED TO WHAT THE AUTHORITY SAYS NOW, and picking the wrong one is itself an instance of this class.** Origin 1 → PAST TENSE WITH THE DATE, keeping the reasoning, because the reasoning usually outlives its executor and a reader who finds a dangling claim silently deleted learns nothing. Origin 2 → name the module and the FACT rather than the dead symbol, since §4c left no symbol to cite; past-tensing here would wrongly report the content as gone when only its citability is. Origin 3 → CORRECT IT to the authority that does back it, NOT past tense: past-tensing a live relation records it as dead and is a new contamination in the opposite direction, produced by the remedy itself. Origin 4 → say it was never backed, or delete the assertion while keeping any surviving reasoning; NEVER past tense, because there is no past to record, and `X USED TO resolve Y` asserts an execution that also never happened. **The default against deletion is a default and not an absolute: a sentence whose entire content is a false execution claim has no reasoning to preserve.** **THE SELF-RECEIPT, and it is the most useful thing in this row (2026-09-01):** the first revision of this entry asserted the `ci_failure_class` clause and the two `cli_run` notes as never-true, on the strength of a grep showing they resolve nowhere TODAY. **Both were true when written**, and the row about unbacked claims therefore contained two of them. It was caught by a reviewing authority asking for verification rather than inheritance, not by any check. **RECOGNITION RULE EARNED: `resolves nowhere now` is not `never resolved`, the distinction is decided by history and not by the working tree, and the instrument is `git log --all -S` over the DECLARATION FORM** — which also separates origin 1 from origin 2 by showing WHAT deleted the referent. The neighbouring rule is `positional_citation`'s: a citation left naming a deleted symbol fakes a grep hit, so every arm above records the change rather than quietly removing the name.) - **mitigation injected where the judgment declined** (a downstream stage injects a RUNTIME mitigation at exactly the seam where an upstream judgment returned Undecidable, so a 4b rung-1 fallback comes to occupy the place the wall belongs and the declined seam stops looking empty. **THE LOAD-BEARING HALF IS WHAT THE COINCIDENCE PROVES ABOUT THE DECLINE**: an Undecidable verdict is normally read as benign conservatism — the facts did not settle it, nothing is claimed, no harm asserted — and a mitigation landing on the SAME LINE is the first evidence that a real defect was sitting under that silence. The decline is therefore load-bearing AT THIS SITE, which refutes reading an Undecidable verdict as evidence of ABSENCE. **IT DOES NOT MAKE THE DECLINE CENSUS PREDICTIVE, and this row carries that limit because the author first wrote the stronger claim and then measured it FALSE** on the same board that produced the specimen: 259 of 272 declines sit in modules with ZERO blocking errors, only 6 of 28 decline-bearing modules carry any error at all, and the module holding 42 of the 63 errors carries ZERO declines and 105 ACCEPTS. Declines and defects are ANTI-CORRELATED at module grain, so this specimen is ONE co-location out of 272 and a walk of the decline census would be walking sites that are ~95% empty. The class is about a mitigation landing on a declined seam; it is NOT a claim that declines predict defects. Distinct from `absorbing_fallback`, whose arm WIDENS to a superset and destroys the precise mechanism s signal; here the arm NARROWS to a panic and destroys nothing except the evidence that a judgment was owed. Distinct from `reflection_evidence_structural_proof`, which mistakes an observation for a proof; here nothing is proven or observed — a decision was declined and then papered. Specimen with a receipt, measured 2026-09-01 on gunbc 0e8c49d and unrepaired at authoring: `v2.std.diagnostic outcome_accepted` is `fn outcome_accepted(value: T) -> Outcome` with T FREE; `v2.compiler.07_target_carriers target_fidelity_quotient_optional` declares `-> Outcome>` and calls `outcome_accepted(Present { value: child })`, so T instantiates to `Optional` and THE SOURCE IS CORRECT. The Rust emitter nonetheless rendered `Some(child).expect("fail-closed: an optional value flowed into non-optional parameter 0 of outcome_accepted (empty Optional at runtime)")`. FOUR DEFECTS IN ONE LINE: it fires on a GENERIC formal where `Optional` is a legitimate instantiation rather than a cardinality escape, so the check does not know T is free; it substitutes a runtime panic for a compile-time judgment; the emitted code does NOT typecheck anyway (rustc E0308 expected `Option>` found `Rc`), so the mitigation buys nothing while standing where the wall belongs; and its own panic string calls itself `fail-closed`, which is 4b rung inflation inside a string literal — a diagnostic naming the discipline it violates is worse than a silent one because it will be cited as evidence the discipline holds. The coincidence that makes this a class and not a bug: `v1.compiler.infer declared_type_inhabitance` returned InhabitanceUndecidable{UndecidableOptionalCarrier} at that module s ONLY optional-carrier decline, 07_target_carriers.dag:128:24 parameter `value` — the same line the emitter mitigated. RUNG FOUND AT: 1 (mitigatable) on a seam whose CEILING is 3 (structurally guaranteed), since generic-formal instantiation is decidable from modeled structure. CONSEQUENCE WITH ITS OWN TRIGGER, deliberately NOT folded into this row: wiring the optional-carrier judgment is argued by this specimen but is a separate change, and its trigger is a producer at that seam that can distinguish a FREE type variable from a declared non-optional formal — until that exists the judgment would decline for the same reason it declines today.) - **merge region excludes the shared tail** (a roster carrier whose rows are multi-line blocks ending in an identical suffix makes every two-lane append resolve WRONG BY DEFAULT. The three-way merge factors the shared suffix out of the conflict region, so the region holds two half-blocks above the markers and one tail below them, and the purely additive resolution -- delete the markers, keep both sides, which is the CORRECT resolution when the two appended rows are independent -- leaves the first row's closing lines belonging to the second. Specimen: this carrier, which conflicted on every integration of main across four merge bases in one session (2026-09-01) with a shared tail of `evidence: [],` and `}`; `gunbc.rung_drop` had the same shape with `}` alone. **THE CLASS IS LOUD, AND THAT IS THE HALF A REPORT OF IT WILL GET WRONG.** The first reading was that the severed row survives as a structurally broken declaration under which the natural check -- declared names against rostered names -- still passes, because both lists stay complete. Measured against a gunbc built at d0009ca531 on the resolved shape: it is a PARSE REFUSAL at the module index, `expected expression, found Eq`, because a `data` keyword cannot stand in record-field position. Nothing reaches the checks that reading worried about, so the cost is toil and not silent wrongness -- a load-bearing authority whose every integration lands a conflict whose natural resolution does not compile, hand-repaired each time. **RECOGNITION RULE: for a carrier two lanes append to, ask what suffix the appended units SHARE, because a conflict region never contains it -- whatever semantics live in that suffix are exactly what a resolution can drop.** THE REPAIR IS THE UNIT AND NOT A CHECK: make the appended unit one line, and the shared suffix is a blank separator carrying nothing. A brace-balance or name-join check written here would be the DESIGN section 4b decoration -- permanently green, because the compiler already refuses the only corpus it could fire on.) +- **coarser parallel authority** (two carriers are AUTHORED for one fact and one of them is LOSSIER, so the fork never presents as duplication — it presents as abstraction. §3 already forbids two authorities for one fact; what this row adds is the reason that rule keeps being read past, because the second carrier does not look like a copy. A coarse answer and a fine answer never contradict each other in the way two copies do: the coarse one reads as *less specific*, which a reviewer accepts as a summary, so the disagreement is invisible until an input distinguishes the collapsed cases. RECEIPT (measured on main while censusing the `first` interpreter/emitted divergence, gunbc#9785): `v1.compiler.infer_method` `builtin_function_registry` maps a primitive name to a RETURN TYPE, and `std.algebra` `AlgebraFieldTemplate` carries `param_types` and `return_type` for the same operations. 20 of the registry's names also have algebra rows, and the two already answer differently, because the registry is RECEIVER-BLIND where the algebra rows are receiver-relative: `reverse` is `List` against `ReceiverSelf`, so on a `String` receiver the two carriers name different types; `map_keys` and `map_values` share ONE element type variable where algebra distinguishes `ReceiverKey` from `ReceiverValue`, so for any `Map` with `K` /= `V` the registry cannot be right about both; `concat` is `String` against `ReceiverSelf`, so a list concat types as a String; `get` collapses the List reading and the Map reading the algebra rows keep apart. None of these is a stale copy that drifted — the registry was authored coarse and has been coarse the whole time. **WHAT MAKES THE COARSE CARRIER LOOK LEGITIMATE is that its coarseness is usually true of the QUESTION ITS FIRST CONSUMER ASKED.** A caller that only wants "is this name a builtin" is well served by a name-to-return-type map, and the carrier is correct for that consumer on the day it lands. It becomes an authority for a fact it never modelled the moment a second consumer asks a finer question of it, and it answers, because a map is total over its keys. **RECOGNITION RULE: when two carriers answer about one operation and one is coarser, ask whether the coarse answer is DERIVED from the fine one or AUTHORED beside it. A derived projection cannot disagree; an authored one is a fork whose disagreements are silent by construction, since "less specific" and "different" are indistinguishable at the call site.** The repair is never to reconcile the two rosters — that is a second synchronisation obligation, and §5 calls a check standing where construction was available validation. The repair is RELOCATION: the coarse carrier stops being an authority and becomes a projection of the fine one, or its rows leave for the layer that actually owns them. Here the same relocation dissolves a second class: once the registry is no longer a signature authority for anything `std.algebra` owns, there is no second declaration left to disagree, and the population that remains — primitives with no algebra row — is a closeable gap rather than an open denominator.) ## Declared rung drops (§4b(3)) From 613136e6817f7f0b7fcfcf191cd9259d0fd8279d Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 12:41:50 +0000 Subject: [PATCH 18/28] Regenerate DESIGN.md after the roster merge --- DESIGN.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DESIGN.md b/DESIGN.md index 16f265af2c6..428bd891b1d 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -214,6 +214,7 @@ One row per class, each carrying its recognition rule and its receipts, in [docs - `unbacked_execution_claim` - `mitigation_injected_where_judgment_declined` - `merge_region_excludes_shared_tail` +- `coarser_parallel_authority` ## Building & checks From 01f550b488e8af4db2a26f1df2f271adbb16241a Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 14:27:49 +0000 Subject: [PATCH 19/28] Narrow the Optional/Null wall: only Absent-vs-none is a straddle; Present-vs-none is ordinary false The first arm refused every constructed Optional against the host Null carrier, so Present { value: x } == none was unwritable and the diagnostic called a Present a representation of absence. Scope decided by reasoning rather than by running the case -- the refusing mirror of the spared-case failure this branch's own row names. Five controls measured with claim_batch: Present == none is false and Present != none is true (both enrolled here); Absent == none and Present == bare still refuse with their own messages (fixture-measured, since a test fn cannot express a refusal); an Optional against an Optional is unchanged. Also caches Optional/Present/Absent as interned Symbols on the context: is_optional_value sits on the Eq/Ne chokepoint and reached them through ctx.sym(), a RefCell borrow_mut plus a hash lookup, up to six per comparison. --- ...rst_optional_construction_witness_test.dag | 19 ++++++ src/v1/stage0/src/v1_interpreter.rs | 68 +++++++++++++++---- .../manual/value_null_split_witness_test.dag | 16 +++-- 3 files changed, 86 insertions(+), 17 deletions(-) diff --git a/dag/test/claim/first_optional_construction_witness_test.dag b/dag/test/claim/first_optional_construction_witness_test.dag index 344c7a6793c..d33b3502c42 100644 --- a/dag/test/claim/first_optional_construction_witness_test.dag +++ b/dag/test/claim/first_optional_construction_witness_test.dag @@ -131,3 +131,22 @@ test fn an_absent_optional_into_a_free_type_variable_is_not_refused() -> Bool { test fn an_optional_compared_against_an_optional_is_not_a_straddle() -> Bool { (["a"] |> first) == Present { value: "a" } } + +// THE ARM'S SCOPE WAS DECIDED BY REASONING AND THAT WAS WRONG IN THE REFUSING DIRECTION. The first +// version refused EVERY constructed Optional against the host Null carrier, `Present { value: x } +// == none` included, whose correct answer is ordinary `false` -- two representations of DIFFERENT +// states, not one. The diagnostic even said so out loud, calling any constructed Optional a +// representation of ABSENCE, which is false for `Present`. The mirror of that mistake is the one +// gunbc.recurring_failure_mode preserved_apparent_behaviour_across_a_representation_change already +// names: there a case was SPARED without being run, here a case was REFUSED without being run, and +// the rule is the same in both directions -- if you cannot state the expected value, you do not +// know enough to decide the arm. These two rows are the controls that make the narrowing real; the +// wall's remaining reach (`Absent == none`, and an Optional against a bare `T`) is fixture-measured +// for the reason stated above, and an over-broad wall would turn BOTH of these red. +test fn a_present_optional_against_none_is_ordinary_false() -> Bool { + (Present { value: "x" } == none) == false +} + +test fn a_present_optional_is_unequal_to_none() -> Bool { + Present { value: "x" } != none +} diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 891d479ba2b..89bf2b6a62d 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -794,8 +794,8 @@ fn is_optional_value(v: &Value, ctx: &InterpContext) -> bool { variant_name, .. } => { - *type_name == ctx.sym("Optional") - && (*variant_name == ctx.sym("Present") || *variant_name == ctx.sym("Absent")) + *type_name == ctx.sym_optional + && (*variant_name == ctx.sym_present || *variant_name == ctx.sym_absent) } _ => false, } @@ -3954,6 +3954,17 @@ pub struct InterpContext { >, mutation_counters: std::cell::RefCell, symbols: RefCell, + /// `Optional`/`Present`/`Absent` interned ONCE at context construction. `is_optional_value` + /// runs on the `Eq`/`Ne` chokepoint -- every `==` in every interpreted program -- and reached + /// these three through `ctx.sym()`, which is `symbols.borrow_mut().intern(s)`: a RefCell + /// mutable borrow plus a hash lookup, up to six per comparison once both operands are asked. + /// Measured consequence, not a hypothetical: fourteen required-floor witnesses went + /// BUDGET-REFUSED at their 500ms CPU budget on the branch that added the check, and passed on + /// the same tree without it. DESIGN section 6's bare-minimum-cost rule makes a proven + /// cost-shape defect always-fix regardless of realized n; here n was realized as a red floor. + sym_optional: Symbol, + sym_present: Symbol, + sym_absent: Symbol, published_mock_keys: RefCell>>>, whole_tree_published_keys: Option>>, governed_services: RefCell>>>, @@ -4232,6 +4243,18 @@ impl InterpContext { fixture_store: Option>, whole_tree_published_keys: Option>>, ) -> Self { + let optional_syms = { + let mut interner = SymbolInterner::default(); + for s in FREE_MONOID_WELL_KNOWN_SYMS { + interner.intern(s); + } + let three = [ + interner.intern("Optional"), + interner.intern("Present"), + interner.intern("Absent"), + ]; + (three, (), interner) + }; InterpContext { modules: indexes.modules.clone(), item_registry: indexes.item_registry.clone(), @@ -4260,13 +4283,10 @@ impl InterpContext { eval_recompute_hash_memo: std::cell::RefCell::new(EvalRecomputeHashMemo::default()), cross_claim_hit_cache: std::cell::RefCell::new(HashMap::new()), mutation_counters: std::cell::RefCell::new(MutationCounters::default()), - symbols: RefCell::new({ - let mut interner = SymbolInterner::default(); - for s in FREE_MONOID_WELL_KNOWN_SYMS { - interner.intern(s); - } - interner - }), + symbols: RefCell::new(optional_syms.2), + sym_optional: optional_syms.0[0], + sym_present: optional_syms.0[1], + sym_absent: optional_syms.0[2], published_mock_keys: RefCell::new(None), whole_tree_published_keys, governed_services: RefCell::new(None), @@ -5747,15 +5767,39 @@ fn eval_binop(op: &BinOp, left: Value, right: Value, ctx: &InterpContext) -> Int /// It stays NARROW by construction rather than by exception. A declared `T?` field whose absent /// state IS `Value::Null` still compares `Null == Null` and never reaches here; only a CONSTRUCTED /// `Optional` meeting the Null carrier fires, which is exactly the un-migrated population. +fn is_absent_variant(v: &Value, ctx: &InterpContext) -> bool { + match v { + Value::Variant { + type_name, + variant_name, + .. + } => *type_name == ctx.sym_optional && *variant_name == ctx.sym_absent, + _ => false, + } +} + fn optional_against_bare_straddle(a: &Value, b: &Value, ctx: &InterpContext) -> Option { let (a_opt, b_opt) = (is_optional_value(a, ctx), is_optional_value(b, ctx)); if a_opt == b_opt { return None; } - if matches!(a, Value::Null) || matches!(b, Value::Null) { + let (opt, other) = if a_opt { (a, b) } else { (b, a) }; + if matches!(other, Value::Null) { + // A PRESENT OPTIONAL AGAINST `none` IS ORDINARY FALSE, NOT A STRADDLE. Only `Absent` + // and the host Null carrier are two representations of ONE state (absence); a + // `Present { value: x }` and `none` are two representations of DIFFERENT states, and + // `false` is the right answer rather than a fabricated one. The first version of this + // arm refused both, which called every constructed Optional a representation of + // absence -- false for `Present` -- and made a legitimate comparison unwritable. That + // scope was decided by reasoning instead of by running the case, which is the failure + // gunbc.recurring_failure_mode preserved_apparent_behaviour_across_a_representation_change + // names for the SPARED case; the rule is symmetric and covers the refused case too. + if !is_absent_variant(opt, ctx) { + return None; + } return Some(format!( - "{} vs {} — a constructed `Absent`/`Present` and the host Null carrier are two \ - representations of ABSENCE, so `==` would silently fabricate `false` (DESIGN §5): \ + "{} vs {} \u{2014} a constructed `Absent` and the host Null carrier are two \ + representations of ABSENCE, so `==` would silently fabricate `false` (DESIGN \u{a7}5): \ measured, `[].first() == none` answered false. Eliminate the `Optional` with `match` \ (`Present {{ value: v }}` / `Absent`) instead of testing it against `none`.", describe_repr(a), diff --git a/src/v2/test/claim/manual/value_null_split_witness_test.dag b/src/v2/test/claim/manual/value_null_split_witness_test.dag index 5b606347f3e..5b7399e864f 100644 --- a/src/v2/test/claim/manual/value_null_split_witness_test.dag +++ b/src/v2/test/claim/manual/value_null_split_witness_test.dag @@ -19,9 +19,15 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // Phase A witnesses (keen-ferret-250): pin Optional/Witness construction carriers vs the overloaded // Value::Null sentinel. optional_null_straddle_rostered_until_phase_e delegates the // Optional×native_value_null straddle to the testgen roster (removed in Phase E). -// raw_get_miss_differs_from_optional_absent stays GREEN while raw get miss is untyped and -// optional_absent() is Optional::Absent; it flips RED in Phase B when get+Optional routes through -// map_lookup_as_optional. +// THE PHASE B FLIP THIS ANNOTATION PREDICTED HAS HAPPENED, and the row is inverted rather than +// retired. It read: raw_get_miss_differs_from_optional_absent stays GREEN while raw get miss is +// untyped and optional_absent() is Optional::Absent; it flips RED in Phase B when get+Optional +// routes through map_lookup_as_optional. That flip is the first()/get Optional construction repair, +// and this file is where it was observed -- the row went red on the required floor before any +// consumer noticed, which is the probe doing its job. Per DESIGN section 4b(4) a probe that changes +// colour when its wall lands becomes the permanent regression control for the wall, so the +// assertion is INVERTED: a raw get miss IS optional absent now, and a future change that reopens +// the split goes red here again. test fn optional_absent_matches_absent_pattern() -> Bool { match optional_absent() { @@ -58,9 +64,9 @@ test fn optional_null_straddle_rostered_until_phase_e() -> Bool { witness_cross_representation_equality_covers_optional_null } -test fn raw_get_miss_differs_from_optional_absent() -> Bool { +test fn raw_get_miss_is_optional_absent() -> Bool { let m = empty_map() - (m |> get("missing")) != optional_absent() + (m |> get("missing")) == optional_absent() } test fn witness_holds_matches_holds_pattern() -> Bool { From 2d5f58df78e800eea4b118859cca70207cb55285 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 14:42:03 +0000 Subject: [PATCH 20/28] Optionality judgement selects the receiver's profile row, and judges both directions The old scan keyed on method spelling over every profile. 23 spellings are declared in more than one profile and three shape their returns differently (join, member, get), so a spelling scan can judge against a row the receiver never selected. The profile now comes from the receiver value's own carrier via kernel_algebra_profile. A row declaring a concrete non-optional return now refuses an Optional result. The naive symmetric rule is NOT implemented: ReceiverSelf, ReceiverElement, ReceiverKey, ReceiverValue and AlgebraTypeVariable are all satisfiable by an Optional at runtime. Zero spellings declare mixed optionality today, so neither half has an authorable red; discrimination is mutation-measured on join and the trigger is recorded in the witness. --- ...rst_optional_construction_witness_test.dag | 27 ++++ src/v1/stage0/src/v1_interpreter.rs | 125 +++++++++++++----- 2 files changed, 118 insertions(+), 34 deletions(-) diff --git a/dag/test/claim/first_optional_construction_witness_test.dag b/dag/test/claim/first_optional_construction_witness_test.dag index d33b3502c42..fdb5c6b3cc0 100644 --- a/dag/test/claim/first_optional_construction_witness_test.dag +++ b/dag/test/claim/first_optional_construction_witness_test.dag @@ -150,3 +150,30 @@ test fn a_present_optional_against_none_is_ordinary_false() -> Bool { test fn a_present_optional_is_unequal_to_none() -> Bool { Present { value: "x" } != none } + +// THE OPTIONALITY JUDGEMENT NOW SELECTS THE ROW THE RECEIVER SELECTS, AND ITS OWN RED IS NOT +// AUTHORABLE HERE. It used to scan all_algebra_field_templates() by METHOD SPELLING and fold the +// hits into one boolean. The algebra is receiver-keyed and a spelling is not: 23 spellings are +// declared in more than one profile, and three of them declare DIFFERENT return templates -- +// `join` is ReceiverSelf in two profiles and NamedTemplate { name: "String" } in the collection +// profile, `member` and `get` likewise. So a spelling scan either refuses a valid row because a +// sibling profile shapes it differently, or judges against a row the receiver never selected. The +// judgement now derives the profile from the receiver value's own carrier through +// kernel_algebra_profile and takes that profile's single row. +// +// It also judges BOTH directions now: a row declaring a concrete non-optional return refuses an +// Optional result, which the old boolean could not express. The naive symmetric rule was NOT +// implemented and the reason is the over-refusal this file already made once -- ReceiverSelf, +// ReceiverElement, ReceiverKey, ReceiverValue and AlgebraTypeVariable are all satisfiable BY an +// Optional at runtime, so only a template naming a concrete shape constrains the negative +// direction. +// +// NO ENROLLED RED EXISTS FOR EITHER HALF AND THAT IS MEASURED, NOT ASSUMED: zero spellings in +// dag/std/algebra.dag declare mixed optionality across profiles today, so the old scan happened to +// reach the right optionality for every live spelling and no accepted program can distinguish the +// two selections. The discrimination is therefore MUTATION-MEASURED: with the `join` arm altered +// to return optional_present(..), `["a", "b"] |> join(",")` refuses with "produced an `Optional` +// where dag/std/algebra.dag declares a concrete non-optional return", and passes again unaltered. +// NEXT-RUNG TRIGGER: a spelling whose profiles disagree about optionality entering the algebra, or +// a fixture harness able to present synthetic algebra rows to the judgement -- at which point the +// red becomes authorable and enrolls here rather than being re-measured by hand. diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 89bf2b6a62d..4e0b8e3389c 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -813,26 +813,70 @@ fn is_optional_value(v: &Value, ctx: &InterpContext) -> bool { /// `None` = the roster has no row for this spelling (not an algebra method, or a free-call-only /// builtin); `Some(false)` = declared non-optional. A spelling whose rows DISAGREE is refused by /// the caller rather than resolved by majority — a mixed roster is a modelling defect, not an input. -fn algebra_row_returns_optional(method: &str) -> Option> { - let mut optional = false; - let mut plain = false; - for t in crate::std_algebra::all_algebra_field_templates().iter() { - if t.name != method { - continue; - } - if matches!( - *t.return_type, - crate::std_algebra::AlgebraTypeTemplate::OptionalOf { .. } - ) { - optional = true; - } else { - plain = true; - } - } - match (optional, plain) { - (false, false) => None, - (true, true) => Some(Err(())), - (o, _) => Some(Ok(o)), +/// THE ALGEBRA IS RECEIVER-KEYED AND A SPELLING IS NOT. `get` is declared in three profiles with +/// two different shapes; `count`, `length` and `reverse` are declared in four each. Selecting rows +/// by METHOD SPELLING ALONE over `all_algebra_field_templates()` therefore either refuses a valid +/// row because a different profile gives that spelling a different shape, or -- where the shapes +/// happen not to disagree -- validates against a row that is not the one the receiver selected. +/// The receiver's own carrier answers which profile applies, and `kernel_algebra_profile()` is the +/// authority that maps a carrier spelling to it, so the profile is derived from the value at hand +/// rather than searched for by name. A receiver whose carrier has no profile row (a record, a +/// variant, a scalar) yields `None` and the judgement declines rather than guessing. +fn receiver_algebra_profile(v: &Value) -> Option { + let spelling = match v { + Value::List(_) => "List", + Value::Map(_) => "Map", + Value::Set(_) => "Set", + Value::Str(_) => "String", + _ => return None, + }; + crate::std_algebra::kernel_algebra_profile() + .get(spelling) + .cloned() +} + +/// The one row the receiver's profile declares for this spelling, or `None` when the profile has +/// no row for it. Never a scan across profiles: two rows for one spelling in ONE profile would be +/// a modelling defect in that profile, and there are none. +fn algebra_row_for_receiver( + method: &str, + profile: Option, +) -> Option> { + let profile = profile?; + crate::std_algebra::algebra_templates_for_profile(profile) + .iter() + .find(|t| t.name == method) + .cloned() +} + +/// What a declared return template says about the result's `Optional`-ness, in BOTH directions. +/// +/// The naive symmetric rule -- "a row that does not declare `OptionalOf` must not return an +/// Optional" -- is WRONG and would be the over-refusal this file already made once. `ReceiverSelf`, +/// `ReceiverElement`, `ReceiverKey`, `ReceiverValue` and `AlgebraTypeVariable` are all satisfiable +/// BY an Optional at runtime: `[Absent] |> reverse` is a list of Optionals, and `fold` over an +/// Optional accumulator returns one. Only a template naming a CONCRETE non-Optional shape +/// constrains the result in the negative direction, so only those refuse. +enum DeclaredOptionality { + MustBeOptional, + MustNotBeOptional, + Unconstrained, +} + +fn declared_optionality(t: &crate::std_algebra::AlgebraTypeTemplate) -> DeclaredOptionality { + use crate::std_algebra::AlgebraTypeTemplate as T; + match t { + T::OptionalOf { .. } => DeclaredOptionality::MustBeOptional, + T::NamedTemplate { .. } + | T::ContainerOf { .. } + | T::TupleOf { .. } + | T::WitnessOf { .. } + | T::CallableOf { .. } => DeclaredOptionality::MustNotBeOptional, + T::ReceiverSelf + | T::ReceiverElement + | T::ReceiverKey + | T::ReceiverValue + | T::AlgebraTypeVariable { .. } => DeclaredOptionality::Unconstrained, } } @@ -906,22 +950,19 @@ fn param_declares_required_value( /// silently producing a value the emitted mirror would never produce. fn algebra_result_matches_declared_optionality( method: &str, + profile: Option, value: Value, ctx: &InterpContext, ) -> InterpResult { - match algebra_row_returns_optional(method) { - None => Ok(value), - Some(Err(())) => Err(InterpError::TypeError { - msg: format!( - "algebra roster disagrees with itself about `{}`: some rows declare \ - `OptionalOf` and some do not, so the interpreter cannot derive the method's \ - optionality from dag/std/algebra.dag", - method - ), - }), - Some(Ok(false)) => Ok(value), - Some(Ok(true)) => { - if is_optional_value(&value, ctx) { + let row = match algebra_row_for_receiver(method, profile) { + Some(row) => row, + None => return Ok(value), + }; + let is_opt = is_optional_value(&value, ctx); + match declared_optionality(&row.return_type) { + DeclaredOptionality::Unconstrained => Ok(value), + DeclaredOptionality::MustBeOptional => { + if is_opt { Ok(value) } else { Err(InterpError::TypeError { @@ -935,6 +976,21 @@ fn algebra_result_matches_declared_optionality( }) } } + DeclaredOptionality::MustNotBeOptional => { + if is_opt { + Err(InterpError::TypeError { + msg: format!( + "interpreter arm for `{}` produced an `Optional` where dag/std/algebra.dag \ + declares a concrete non-optional return; the emitted realization returns \ + the bare value here, so wrapping it would be the same divergence read \ + from the other side", + method + ), + }) + } else { + Ok(value) + } + } } } @@ -9629,9 +9685,10 @@ fn eval_algebra_method_inner( env: &Rc, ctx: &InterpContext, ) -> InterpResult { + let receiver_profile = receiver_algebra_profile(&receiver); let produced: InterpResult = v1_algebra_method_arms!(v1_algebra_dispatch, method, receiver, args, env, ctx); - algebra_result_matches_declared_optionality(method, produced?, ctx) + algebra_result_matches_declared_optionality(method, receiver_profile, produced?, ctx) } pub fn fixture_now_secs(ctx: &InterpContext) -> Result { From 3825e4a977a57444f2086ce26a7397f72efb013c Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 15:26:01 +0000 Subject: [PATCH 21/28] Move the optionality decision into dag/std/algebra.dag and enroll the planted red algebra_result_optionality now declares, in the roster's own module, what a return template says about a result's Optional-ness -- three arms, because a receiver-relative template constrains neither direction. The interpreter calls the generated function instead of carrying a second hand-written answer. The discriminating red IS authorable: I recorded it as unauthorable on the strength of the accepted corpus containing no mixed-optionality spelling, which is the wrong boundary. Both halves take their input as a parameter, so a planted same-spelling opposite-optionality pair is expressible and is now enrolled, with a receiver-relative control beside it. Each reds under a different mutation of the .dag. --- dag/std/algebra.dag | 35 ++++++++ ...rst_optional_construction_witness_test.dag | 89 +++++++++++++++++-- src/v1/stage0/src/std_algebra.rs | 41 +++++++++ src/v1/stage0/src/v1_interpreter.rs | 44 +++------ 4 files changed, 166 insertions(+), 43 deletions(-) diff --git a/dag/std/algebra.dag b/dag/std/algebra.dag index 5774cd8c125..b276fb28040 100644 --- a/dag/std/algebra.dag +++ b/dag/std/algebra.dag @@ -796,6 +796,41 @@ fn algebra_method_template_name(name: String) -> Bool { |> any(profile => algebra_templates_for_profile(profile: profile) |> any(t => t.name == name)) } +// WHAT A DECLARED RETURN TEMPLATE SAYS ABOUT THE RESULT'S Optional-NESS, AND WHAT IT DECLINES TO +// SAY. This is the single authority both realizations of every algebra arm answer to: the Rust +// emit arm produces `Option` exactly where a row declares `OptionalOf`, and the interpreter +// refuses a result that disagrees with the same row. It lived only in the interpreter, in Rust, +// which is the fork this vocabulary exists to close -- a second hand-written answer to a question +// the roster already decides. +// +// THREE ARMS, NOT TWO, AND THE THIRD IS THE POINT. A template naming a CONCRETE shape constrains +// the result in both directions. A template that stands for something the receiver supplies -- +// ReceiverSelf, ReceiverElement, ReceiverKey, ReceiverValue, or a type variable -- constrains it in +// NEITHER, because every one of them is inhabitable BY an Optional at runtime: `[Absent] |> reverse` +// is a list of Optionals and a fold over an Optional accumulator returns one. Collapsing those into +// "not declared Optional, therefore must not be Optional" would refuse correct programs, which is +// the over-refusal DESIGN section 5 calls fabricating in the other direction. +type AlgebraResultOptionality + = OptionalityRequired + | OptionalityForbidden + | OptionalityUnconstrained + +fn algebra_result_optionality(result: AlgebraTypeTemplate) -> AlgebraResultOptionality { + match result { + OptionalOf { inner: _ } => OptionalityRequired + NamedTemplate { name: _ } => OptionalityForbidden + ContainerOf { source: _, element: _ } => OptionalityForbidden + TupleOf { first: _, second: _ } => OptionalityForbidden + WitnessOf { inner: _ } => OptionalityForbidden + CallableOf { params: _, return_type: _ } => OptionalityForbidden + ReceiverSelf => OptionalityUnconstrained + ReceiverElement => OptionalityUnconstrained + ReceiverKey => OptionalityUnconstrained + ReceiverValue => OptionalityUnconstrained + AlgebraTypeVariable { id: _ } => OptionalityUnconstrained + } +} + fn algebra_templates_for_profile(profile: AlgebraProfile) -> List { match profile { OrderedRingProfile => ordered_ring_templates() diff --git a/dag/test/claim/first_optional_construction_witness_test.dag b/dag/test/claim/first_optional_construction_witness_test.dag index fdb5c6b3cc0..2686cf99b30 100644 --- a/dag/test/claim/first_optional_construction_witness_test.dag +++ b/dag/test/claim/first_optional_construction_witness_test.dag @@ -168,12 +168,83 @@ test fn a_present_optional_is_unequal_to_none() -> Bool { // Optional at runtime, so only a template naming a concrete shape constrains the negative // direction. // -// NO ENROLLED RED EXISTS FOR EITHER HALF AND THAT IS MEASURED, NOT ASSUMED: zero spellings in -// dag/std/algebra.dag declare mixed optionality across profiles today, so the old scan happened to -// reach the right optionality for every live spelling and no accepted program can distinguish the -// two selections. The discrimination is therefore MUTATION-MEASURED: with the `join` arm altered -// to return optional_present(..), `["a", "b"] |> join(",")` refuses with "produced an `Optional` -// where dag/std/algebra.dag declares a concrete non-optional return", and passes again unaltered. -// NEXT-RUNG TRIGGER: a spelling whose profiles disagree about optionality entering the algebra, or -// a fixture harness able to present synthetic algebra rows to the judgement -- at which point the -// red becomes authorable and enrolls here rather than being re-measured by hand. +// THE RED IS AUTHORABLE AT THE FIXTURE BOUNDARY AND IS ENROLLED BELOW. I first recorded it as +// unauthorable, on the grounds that zero spellings in dag/std/algebra.dag declare mixed optionality +// across profiles -- true, and the wrong boundary. DESIGN section 4b turns on whether the forbidden +// state is expressible as SOURCE HANDED TO THE COMPILER BY A FIXTURE, not on whether the ACCEPTED +// CORPUS contains it, and a compiler is precisely a thing whose regression probes are invalid +// programs. Both halves of the judgement now take their input as a PARAMETER -- +// std.primitive_identity's algebra_signature_from_candidates already did, for exactly this reason, +// and std.algebra's algebra_result_optionality does -- so a planted pair is expressible and the +// evidence is enrollable. Declining it would have been specification-without-execution. +// +// The mutation control is kept beside them rather than replaced: with the `join` arm altered to +// return optional_present(..), `["a", "b"] |> join(",")` refuses with "produced an `Optional` where +// dag/std/algebra.dag declares a concrete non-optional return", and passes again unaltered. The +// planted rows prove the DECISION; the mutation proves the interpreter arm is wired to it. +// +// BOTH PLANTED ROWS WERE MUTATION-CHECKED AND THEY DISCRIMINATE IN OPPOSITE DIRECTIONS, so neither +// is satisfiable by a constant answer. Changing `ReceiverElement` to `OptionalityForbidden` reds +// the receiver-relative row and leaves the pair green; changing `NamedTemplate` to +// `OptionalityRequired` reds the pair and leaves the receiver-relative row green. +// +// AND ONE INSTRUMENT FACT THAT COST A MUTATION RUN, RECORDED BECAUSE IT WILL COST THE NEXT ONE. +// Mutating the EMITTED MIRROR (src/v1/stage0/src/std_algebra.rs) does not move an INTERPRETED +// witness at all -- the interpreter reads dag/std/algebra.dag, so the mirror is the wrong subject +// and the mutation reads as "the control does not discriminate" when it means "the control never +// saw the change". The mutation has to be made in the .dag. The reverse holds for the `join` +// control above, whose subject IS the interpreter arm and therefore IS the Rust. + +// THE SAME-SPELLING, OPPOSITE-OPTIONALITY PAIR. One spelling, two candidate lists standing for two +// profiles, opposite verdicts. A judgement that selected by spelling over a merged population could +// not answer these two differently, so this is the discriminating input the live corpus does not +// contain. +fn forked_spelling_optional_profile() -> List { + [ + { name: "forked", param_types: [ReceiverSelf], return_type: OptionalOf { inner: ReceiverElement }, size_effect: none, cost_shape: none, callback_element_position: none } + ] +} + +fn forked_spelling_concrete_profile() -> List { + [ + { name: "forked", param_types: [ReceiverSelf], return_type: NamedTemplate { name: "String" }, size_effect: none, cost_shape: none, callback_element_position: none } + ] +} + +fn optionality_of(candidates: List) -> AlgebraResultOptionality { + match algebra_signature_from_candidates(canonical_name: "forked" as NonEmptyStr, candidates: candidates) { + SignatureResolved { parameters: _, result: r } => algebra_result_optionality(result: r) + SignatureAmbiguousOnProfile { canonical_name: _, candidate_count: _ } => OptionalityUnconstrained + SignatureAbsentFromProfile { canonical_name: _ } => OptionalityUnconstrained + SignatureNotGrounded { reason: _ } => OptionalityUnconstrained + } +} + +test fn one_spelling_two_profiles_two_verdicts() -> Bool { + match optionality_of(candidates: forked_spelling_optional_profile()) { + OptionalityRequired => + match optionality_of(candidates: forked_spelling_concrete_profile()) { + OptionalityForbidden => true + OptionalityRequired => false + OptionalityUnconstrained => false + } + OptionalityForbidden => false + OptionalityUnconstrained => false + } +} + +// THE THIRD ARM IS NOT A HOLE, AND THIS IS THE ROW THAT SAYS SO. A row returning ReceiverElement +// constrains NOTHING about the result's optionality, because the receiver's element may itself be +// an Optional -- `[Absent] |> first` is `Present { value: Absent }`. Without this control, a +// judgement that answered Forbidden for every non-OptionalOf template would still satisfy the pair +// above, and it would refuse correct programs. +test fn a_receiver_relative_return_constrains_neither_direction() -> Bool { + let planted: List = [ + { name: "forked", param_types: [ReceiverSelf], return_type: ReceiverElement, size_effect: none, cost_shape: none, callback_element_position: none } + ] + match optionality_of(candidates: planted) { + OptionalityUnconstrained => true + OptionalityRequired => false + OptionalityForbidden => false + } +} diff --git a/src/v1/stage0/src/std_algebra.rs b/src/v1/stage0/src/std_algebra.rs index b2c58049e77..99d7aefe177 100644 --- a/src/v1/stage0/src/std_algebra.rs +++ b/src/v1/stage0/src/std_algebra.rs @@ -2,6 +2,7 @@ // Source module: std.algebra use self::AlgebraProfile::*; +use self::AlgebraResultOptionality::*; use self::AlgebraSupportAxis::*; use self::AlgebraTypeTemplate::*; use self::CarrierRowMembership::*; @@ -2071,6 +2072,40 @@ pub fn algebra_method_template_name(name: String) -> bool { } } +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] +#[serde(tag = "_variant")] +pub enum AlgebraResultOptionality { + OptionalityRequired, + OptionalityForbidden, + OptionalityUnconstrained, +} + +pub fn algebra_result_optionality(result: Rc) -> AlgebraResultOptionality { + match (*result.clone()).clone() { + AlgebraTypeTemplate::OptionalOf { inner: _, .. } => { + AlgebraResultOptionality::OptionalityRequired + } + AlgebraTypeTemplate::NamedTemplate { name: _, .. } => { + AlgebraResultOptionality::OptionalityForbidden + } + AlgebraTypeTemplate::ContainerOf { .. } => AlgebraResultOptionality::OptionalityForbidden, + AlgebraTypeTemplate::TupleOf { .. } => AlgebraResultOptionality::OptionalityForbidden, + AlgebraTypeTemplate::WitnessOf { inner: _, .. } => { + AlgebraResultOptionality::OptionalityForbidden + } + AlgebraTypeTemplate::CallableOf { .. } => AlgebraResultOptionality::OptionalityForbidden, + AlgebraTypeTemplate::ReceiverSelf => AlgebraResultOptionality::OptionalityUnconstrained, + AlgebraTypeTemplate::ReceiverElement => AlgebraResultOptionality::OptionalityUnconstrained, + AlgebraTypeTemplate::ReceiverKey => AlgebraResultOptionality::OptionalityUnconstrained, + AlgebraTypeTemplate::ReceiverValue => AlgebraResultOptionality::OptionalityUnconstrained, + AlgebraTypeTemplate::AlgebraTypeVariable { id: _, .. } => { + AlgebraResultOptionality::OptionalityUnconstrained + } + } +} + pub fn algebra_templates_for_profile(profile: AlgebraProfile) -> Rc>> { match profile.clone() { AlgebraProfile::OrderedRingProfile => ordered_ring_templates(), @@ -2185,3 +2220,9 @@ pub struct ShapeSortBody; pub struct FiniteSupport; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct OpenSupport; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct OptionalityRequired; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct OptionalityForbidden; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct OptionalityUnconstrained; diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 4e0b8e3389c..717eec71b3e 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -849,36 +849,12 @@ fn algebra_row_for_receiver( .cloned() } -/// What a declared return template says about the result's `Optional`-ness, in BOTH directions. -/// -/// The naive symmetric rule -- "a row that does not declare `OptionalOf` must not return an -/// Optional" -- is WRONG and would be the over-refusal this file already made once. `ReceiverSelf`, -/// `ReceiverElement`, `ReceiverKey`, `ReceiverValue` and `AlgebraTypeVariable` are all satisfiable -/// BY an Optional at runtime: `[Absent] |> reverse` is a list of Optionals, and `fold` over an -/// Optional accumulator returns one. Only a template naming a CONCRETE non-Optional shape -/// constrains the result in the negative direction, so only those refuse. -enum DeclaredOptionality { - MustBeOptional, - MustNotBeOptional, - Unconstrained, -} - -fn declared_optionality(t: &crate::std_algebra::AlgebraTypeTemplate) -> DeclaredOptionality { - use crate::std_algebra::AlgebraTypeTemplate as T; - match t { - T::OptionalOf { .. } => DeclaredOptionality::MustBeOptional, - T::NamedTemplate { .. } - | T::ContainerOf { .. } - | T::TupleOf { .. } - | T::WitnessOf { .. } - | T::CallableOf { .. } => DeclaredOptionality::MustNotBeOptional, - T::ReceiverSelf - | T::ReceiverElement - | T::ReceiverKey - | T::ReceiverValue - | T::AlgebraTypeVariable { .. } => DeclaredOptionality::Unconstrained, - } -} +/// THE DECISION LIVES IN `dag/std/algebra.dag` AND THIS CALLS IT. `algebra_result_optionality` +/// is the roster's own answer to what a declared return template says about a result's +/// Optional-ness, and it is generated into `std_algebra.rs` from the same rows the emit arm reads. +/// It was written here in Rust first, which was the fork this branch exists to close, one file +/// over from where it was being closed. Its three arms and the reason the third exists are +/// documented at the declaration, not restated here. /// What an argument value is, with respect to the `Optional` contract. enum OptionalArg { @@ -959,9 +935,9 @@ fn algebra_result_matches_declared_optionality( None => return Ok(value), }; let is_opt = is_optional_value(&value, ctx); - match declared_optionality(&row.return_type) { - DeclaredOptionality::Unconstrained => Ok(value), - DeclaredOptionality::MustBeOptional => { + match crate::std_algebra::algebra_result_optionality(row.return_type.clone()) { + crate::std_algebra::AlgebraResultOptionality::OptionalityUnconstrained => Ok(value), + crate::std_algebra::AlgebraResultOptionality::OptionalityRequired => { if is_opt { Ok(value) } else { @@ -976,7 +952,7 @@ fn algebra_result_matches_declared_optionality( }) } } - DeclaredOptionality::MustNotBeOptional => { + crate::std_algebra::AlgebraResultOptionality::OptionalityForbidden => { if is_opt { Err(InterpError::TypeError { msg: format!( From 30568aa339d642f45d3be20385888dedb445a3ba Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 15:43:36 +0000 Subject: [PATCH 22/28] Failure mode: a mutation control applied to the wrong artifact of a generated pair A self-inverting instrument. The control stays green and reads as evidence that it discriminates nothing, so a false negative argues for deleting a check that was sound. Both directions receipted from this branch: the emitted mirror was the wrong target for an interpreted witness, and the interpreter arm was the right one for the join control. --- DESIGN.md | 1 + dag/gunbc/recurring_failure_mode.dag | 3 +++ docs/design-ledgers.md | 1 + 3 files changed, 5 insertions(+) diff --git a/DESIGN.md b/DESIGN.md index 8b2d544ee2d..1e64047ea14 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -218,6 +218,7 @@ One row per class, each carrying its recognition rule and its receipts, in [docs - `sealing_property_erases_structure` - `restoration_promise_names_a_route_that_does_not_exist` - `coarser_parallel_authority` +- `mutation_applied_to_the_wrong_artifact_of_a_generated_pair` ## Building & checks diff --git a/dag/gunbc/recurring_failure_mode.dag b/dag/gunbc/recurring_failure_mode.dag index 2c92a44195e..afad469b1eb 100644 --- a/dag/gunbc/recurring_failure_mode.dag +++ b/dag/gunbc/recurring_failure_mode.dag @@ -159,6 +159,8 @@ data restoration_promise_names_a_route_that_does_not_exist: RecurringFailureMode data coarser_parallel_authority: RecurringFailureMode = RecurringFailureMode { identity: "coarser_parallel_authority" as NonEmptyStr, authored: "**coarser parallel authority** (two carriers are AUTHORED for one fact and one of them is LOSSIER, so the fork never presents as duplication — it presents as abstraction. §3 already forbids two authorities for one fact; what this row adds is the reason that rule keeps being read past, because the second carrier does not look like a copy. A coarse answer and a fine answer never contradict each other in the way two copies do: the coarse one reads as *less specific*, which a reviewer accepts as a summary, so the disagreement is invisible until an input distinguishes the collapsed cases. RECEIPT (measured on main while censusing the `first` interpreter/emitted divergence, gunbc#9785): `v1.compiler.infer_method` `builtin_function_registry` maps a primitive name to a RETURN TYPE, and `std.algebra` `AlgebraFieldTemplate` carries `param_types` and `return_type` for the same operations. 20 of the registry's names also have algebra rows, and the two already answer differently, because the registry is RECEIVER-BLIND where the algebra rows are receiver-relative: `reverse` is `List` against `ReceiverSelf`, so on a `String` receiver the two carriers name different types; `map_keys` and `map_values` share ONE element type variable where algebra distinguishes `ReceiverKey` from `ReceiverValue`, so for any `Map` with `K` /= `V` the registry cannot be right about both; `concat` is `String` against `ReceiverSelf`, so a list concat types as a String; `get` collapses the List reading and the Map reading the algebra rows keep apart. None of these is a stale copy that drifted — the registry was authored coarse and has been coarse the whole time. **WHAT MAKES THE COARSE CARRIER LOOK LEGITIMATE is that its coarseness is usually true of the QUESTION ITS FIRST CONSUMER ASKED.** A caller that only wants \"is this name a builtin\" is well served by a name-to-return-type map, and the carrier is correct for that consumer on the day it lands. It becomes an authority for a fact it never modelled the moment a second consumer asks a finer question of it, and it answers, because a map is total over its keys. **RECOGNITION RULE: when two carriers answer about one operation and one is coarser, ask whether the coarse answer is DERIVED from the fine one or AUTHORED beside it. A derived projection cannot disagree; an authored one is a fork whose disagreements are silent by construction, since \"less specific\" and \"different\" are indistinguishable at the call site.** The repair is never to reconcile the two rosters — that is a second synchronisation obligation, and §5 calls a check standing where construction was available validation. The repair is RELOCATION: the coarse carrier stops being an authority and becomes a projection of the fine one, or its rows leave for the layer that actually owns them. Here the same relocation dissolves a second class: once the registry is no longer a signature authority for anything `std.algebra` owns, there is no second declaration left to disagree, and the population that remains — primitives with no algebra row — is a closeable gap rather than an open denominator.)", evidence: [] } +data mutation_applied_to_the_wrong_artifact_of_a_generated_pair: RecurringFailureMode = RecurringFailureMode { identity: "mutation_applied_to_the_wrong_artifact_of_a_generated_pair" as NonEmptyStr, authored: "**a mutation control applied to the wrong artifact of a generated pair** (a subject exists twice -- once as authored source and once as its generated projection -- and the mutation is made in the copy the harness does not read, so the control stays GREEN and reads as evidence that it discriminates nothing). This is a SELF-INVERTING instrument and therefore worse than an inert one: a failed mutation is normally strong evidence that a check is vacuous, so the false negative argues for DELETING or rewriting a check that was sound. RECOGNITION RULE, mechanical and to be applied BEFORE choosing a target: name which artifact the HARNESS reads. A generated pair offers two plausible mutation targets and only one is the subject. RECEIPT, with both directions, from gunbc#9785. Mutating src/v1/stage0/src/std_algebra.rs -- the emitted mirror -- left both planted optionality controls green; claim_batch INTERPRETS a witness, so it reads dag/std/algebra.dag and the mirror was never the subject. Re-made in the .dag, the same two mutations discriminate in opposite directions: ReceiverElement to OptionalityForbidden reds the receiver-relative row and leaves the pair green, NamedTemplate to OptionalityRequired reds the pair and leaves the receiver-relative row green. The INVERSE case is in the same change and is what makes the rule a question rather than a preference: the join control mutates v1_interpreter.rs, because ITS subject is the interpreter arm and there the Rust is what executes. Same repository, same session, opposite correct targets. The general form covers any authored/generated pair -- .dag against its stage0 mirror, a carrier against its projected markdown, a schema against its emitted client -- and the question is never which artifact is authoritative, only which one the runner opened.", evidence: [] } + data recurring_failure_mode_roster: List = [ hollow_alias, state_space_conflation, @@ -194,4 +196,5 @@ data recurring_failure_mode_roster: List = [ sealing_property_erases_structure, restoration_promise_names_a_route_that_does_not_exist, coarser_parallel_authority, + mutation_applied_to_the_wrong_artifact_of_a_generated_pair, ] diff --git a/docs/design-ledgers.md b/docs/design-ledgers.md index cf894155d8f..6823d0c3a75 100644 --- a/docs/design-ledgers.md +++ b/docs/design-ledgers.md @@ -46,6 +46,7 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a construction-restricting property read as a structure-erasing one** (a declaration is annotated to RESTRICT how its inhabitants may be built, and a consumer that asks about the declaration's SHAPE treats the annotation as a reason to stop looking -- so the deliberate climb on the construction axis silently drops a rung on every axis decided by shape, with no declaration, because the two axes are read by different consumers. **THE INVERSION IS THE CLASS**: the annotation exists to make the carrier MORE distinct from its payload, and the consumer's response is to make it INDISTINGUISHABLE from everything. Recognition rule, keyed on the SHAPE and not on any one keyword: whenever a declaration-level marker is stored in a general side-channel -- a properties list, an attribute bag, a modifier set -- find every consumer that BRANCHES ON THAT CHANNEL BEING NON-EMPTY rather than on the specific marker, and ask what each of them would have answered had the marker been absent; a consumer that answers 'opaque', 'unknown', or 'skip' for the whole channel has conflated 'this declaration carries a marker I do not model' with 'this declaration has no structure', and every future marker inherits the same silence on arrival. **SPECIMEN, with the discriminating pair** (gunbc XL-0-FLOOR, 2026-09-01): `v2.compiler.normalized_tree` `NormalizedTree` was sealed by BL-1 from an alias for `Node` into a `sole_constructor` record so that a wrapper-retention drop would be unwritable. `sole_constructor` is carried as a PROPERTY on the declaration node (`v1.compiler.parse` `parsed_sole_constructor_properties`), and `v1.compiler.infer` `exposure_view_for_node` answered `OpaqueTypeHead` for any node whose properties list was non-empty -- so `nominal_product_head_name` answered the empty string for every sealed carrier, `nominal_product_inhabitance_refusal` short-circuited on the empty head, and `declared_type_inhabitance` fell through to `Inhabits`. `v2.compiler.source_authority` `normalized_source_ast_equal_witness` then bound a `NormalizedTree` at a formal declared `Node` and the compiler ACCEPTED it: the ordinary-floor violation DESIGN section 4b names as exact application bijection, below baseline. The discriminating pair is two record declarations identical to the token except for the word `sole_constructor` -- the sealed one compiled with zero diagnostics where the plain one refused. **WHY A CENSUS MISSED IT, which is the part that generalizes**: gunbc#8886 measured 285 sites behind the `v2.*` direct-call argument-type exemption, of which 148 were `Node` against `ResolvedTree`/`ParseTree` and siblings -- every one a transparent-ALIAS false positive. This shape never appears in that roster and could not have, because it is a TRUE positive the comparison relation cannot reach: the census enumerates what the relation REFUSES, so a defect the relation is blind to is invisible to the census by construction, and a large census reads as thorough coverage of exactly the region it cannot see. **THE MISDIAGNOSIS THIS CLASS INVITES, recorded because this lane was dispatched with it**: the silence sat inside a module the exemption covers, so the exemption was the obvious cause; refuting it took running the identical program in an ordinary module and on a tree with the exemption deleted, both of which stayed silent, while the same run showed the exemption's effect on a kernel red so the arms were provably live. A plausible nearby mechanism that is genuinely broken is the most expensive wrong answer available, because repairing it changes nothing and the repair reads as coverage. **Rung: mechanically preventable, and the ceiling for this class as a class is the same rung.** The repair makes one marker structure-preserving by positive establishment -- the parser builds the identical declaration node and only the properties list differs -- and every OTHER marker in that channel stays opaque and unmeasured, which is a named residue rather than a covered class. The next-rung trigger is a CAPABILITY: markers carried in the declaration's own type rather than in an untyped side-channel, so that a consumer branching on the channel cannot compile.) - **a restoration promise names a route that does not exist** (a mechanism withholds, defers or dormant-marks a population and tells the reader it becomes observable again under some named condition — and that condition names a RUN, LANE, CADENCE OR SWEEP the tree does not contain. Nothing refuses, because the promise is a string in a diagnostic rather than a citation anything resolves; the population is not silently dropped, which is what makes the class survive review — it is dropped WITH A RECEIPT, and the receipt is what stops anyone looking. **THE BOUNDARY AGAINST `unbacked_execution_claim` IS THE TENSE AND IT DECIDES THE REMEDY.** That class is prose asserting a relation that RUNS NOW; this is prose asserting a relation that WILL run — a future condition, so `git log --all -S` over the declaration form finds nothing to past-tense and the origin arms there do not apply. It is also not `absorbing_fallback`: nothing widens, the withhold is precise and correctly counted. It is 4b(3)'s trigger trap with the polarity inverted — there a trigger names LESS than the capability it restores and gets satisfied while the capability stays dead; here the trigger names a capability whose PRECONDITION IS ALREADY FALSE, so it can never be satisfied at all and the row waits forever in a state that reads as temporary. **SPECIMEN WITH A RECEIPT, measured 2026-09-01 on head 4c6c509e and unrepaired at authoring.** `v1_compiler.cli_run.required_floor_runner` `suppress_withheld` removes enrolled expected-red identities whose module sits outside the required gate, printing that their enrolment `becomes observable again when the gate roster admits the module or in the whole-corpus receipts run`. THAT RUN DOES NOT EXIST: the phrase occurs exactly once in the tree, inside the message that promises it, and the repository carries three workflows of which none is it. 39 identities across 23 modules sit under that promise. Each is enrolled on `v2.workflow.floor_expected_red`, whose own header states what an enrolment asserts — that the identity REACHES ITS SUBJECT AND ANSWERS, and that a row belongs there only while someone is fixing it — so every one of the 39 asserts `runs, fails, someone is fixing it` about a row no run reaches. The only surviving route by which one executes is the changed-witness override, i.e. somebody editing it. **THE POPULATION IS DERIVABLE AND IS DELIBERATELY NOT TRANSCRIBED HERE**: it is `v2.workflow.floor_expected_red` `floor_expected_red_roster` minus the identities whose module matches `v2.workflow.required_floor` `required_gate_prefixes` — two authorities and a set difference, so it re-derives instead of rotting. **THE SAME ROSTER'S HEADER ALREADY RECORDS THE ANCESTOR OF THIS MISTAKE**, which is why it is a class: 101 rows were held there as agreement while never reaching their subject, and were reclassified into `v2.workflow.floor_route_gap` on 2026-08-20 once `ExpectedRedArm` was taught to refuse `HostEffectRefused`, `HostToolUnresolved` and an interrupted budget. That repair closed the arm where a NON-VERDICT was read as agreement; this class is the same harm one step earlier, where a row never reaches an arm at all and a sentence promises it will. **RECOGNITION RULE: whenever a diagnostic says a withheld thing becomes observable again `in`/`under`/`by` some named run, grep the tree for that name and require an executing consumer — a workflow job, a scheduled entry point, an actuator argv. If the only occurrence is the promise itself, the population is dormant forever and the honest states are two: admit the row cannot be observed on any cadence, or delete the enrolment. **RUNG: 1 (mitigatable) — the withhold is counted and located, which is the whole of what holds. CEILING: 3, since `whether a named route exists` is decidable from the workflow and entry-point authorities the tree already carries. NEXT-RUNG TRIGGER, a CAPABILITY and not an artifact: restoration conditions expressed as a resolvable citation to an executing consumer rather than as prose, so a promise naming no route fails to compile — writing this particular sentence better retires nothing.) - **coarser parallel authority** (two carriers are AUTHORED for one fact and one of them is LOSSIER, so the fork never presents as duplication — it presents as abstraction. §3 already forbids two authorities for one fact; what this row adds is the reason that rule keeps being read past, because the second carrier does not look like a copy. A coarse answer and a fine answer never contradict each other in the way two copies do: the coarse one reads as *less specific*, which a reviewer accepts as a summary, so the disagreement is invisible until an input distinguishes the collapsed cases. RECEIPT (measured on main while censusing the `first` interpreter/emitted divergence, gunbc#9785): `v1.compiler.infer_method` `builtin_function_registry` maps a primitive name to a RETURN TYPE, and `std.algebra` `AlgebraFieldTemplate` carries `param_types` and `return_type` for the same operations. 20 of the registry's names also have algebra rows, and the two already answer differently, because the registry is RECEIVER-BLIND where the algebra rows are receiver-relative: `reverse` is `List` against `ReceiverSelf`, so on a `String` receiver the two carriers name different types; `map_keys` and `map_values` share ONE element type variable where algebra distinguishes `ReceiverKey` from `ReceiverValue`, so for any `Map` with `K` /= `V` the registry cannot be right about both; `concat` is `String` against `ReceiverSelf`, so a list concat types as a String; `get` collapses the List reading and the Map reading the algebra rows keep apart. None of these is a stale copy that drifted — the registry was authored coarse and has been coarse the whole time. **WHAT MAKES THE COARSE CARRIER LOOK LEGITIMATE is that its coarseness is usually true of the QUESTION ITS FIRST CONSUMER ASKED.** A caller that only wants "is this name a builtin" is well served by a name-to-return-type map, and the carrier is correct for that consumer on the day it lands. It becomes an authority for a fact it never modelled the moment a second consumer asks a finer question of it, and it answers, because a map is total over its keys. **RECOGNITION RULE: when two carriers answer about one operation and one is coarser, ask whether the coarse answer is DERIVED from the fine one or AUTHORED beside it. A derived projection cannot disagree; an authored one is a fork whose disagreements are silent by construction, since "less specific" and "different" are indistinguishable at the call site.** The repair is never to reconcile the two rosters — that is a second synchronisation obligation, and §5 calls a check standing where construction was available validation. The repair is RELOCATION: the coarse carrier stops being an authority and becomes a projection of the fine one, or its rows leave for the layer that actually owns them. Here the same relocation dissolves a second class: once the registry is no longer a signature authority for anything `std.algebra` owns, there is no second declaration left to disagree, and the population that remains — primitives with no algebra row — is a closeable gap rather than an open denominator.) +- **a mutation control applied to the wrong artifact of a generated pair** (a subject exists twice -- once as authored source and once as its generated projection -- and the mutation is made in the copy the harness does not read, so the control stays GREEN and reads as evidence that it discriminates nothing). This is a SELF-INVERTING instrument and therefore worse than an inert one: a failed mutation is normally strong evidence that a check is vacuous, so the false negative argues for DELETING or rewriting a check that was sound. RECOGNITION RULE, mechanical and to be applied BEFORE choosing a target: name which artifact the HARNESS reads. A generated pair offers two plausible mutation targets and only one is the subject. RECEIPT, with both directions, from gunbc#9785. Mutating src/v1/stage0/src/std_algebra.rs -- the emitted mirror -- left both planted optionality controls green; claim_batch INTERPRETS a witness, so it reads dag/std/algebra.dag and the mirror was never the subject. Re-made in the .dag, the same two mutations discriminate in opposite directions: ReceiverElement to OptionalityForbidden reds the receiver-relative row and leaves the pair green, NamedTemplate to OptionalityRequired reds the pair and leaves the receiver-relative row green. The INVERSE case is in the same change and is what makes the rule a question rather than a preference: the join control mutates v1_interpreter.rs, because ITS subject is the interpreter arm and there the Rust is what executes. Same repository, same session, opposite correct targets. The general form covers any authored/generated pair -- .dag against its stage0 mirror, a carrier against its projected markdown, a schema against its emitted client -- and the question is never which artifact is authoritative, only which one the runner opened. ## Declared rung drops (§4b(3)) From f407ab9a1fb274c216c9440203c18735071659c6 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 18:27:48 +0000 Subject: [PATCH 23/28] first(): migrate the field-access population to a match, and declare the parameter coercion Every `.first().` site in the corpus read an Optional's payload as a record. The 22 production sites are migrated to `match X.first()` with the Absent arm DERIVED from the sibling guard the site already carried -- the `count == 0` refusal it sat beside -- rather than fabricated; where no sibling existed (`nth_layer_function`) the partial function now returns Optional and its single caller answers with the `LayerOutsideStackup` refusal it already declares. The witness sites answer `Absent => false`, which is the honest verdict for a claim about a head that does not exist. Also declares `gunbc.rung_drop optional_into_declared_nonoptional_parameter`: an Optional actual flowing into a parameter declared non-optional is unwrapped rather than refused, in BOTH arms. The emit arm carries that coercion on main in six places; documenting it under a comment was not declaring it. Trigger is named at capability grain -- the compile seam refuses the flow, at both arms, proven by a discriminating pair -- and explicitly is NOT a site census. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- dag/extdeps/bmc/pid_control_decode.dag | 14 +- dag/extdeps/bmc/types.dag | 5 +- .../languages/json/member_conservation.dag | 30 ++-- .../sudo/nopasswd_execute_probe_check_op.dag | 5 +- dag/extdeps/tailscale/acl.dag | 12 +- .../bmc/bmc_fan_program_decode_witness.dag | 103 ++++++++++---- dag/gunbc/commit_workflow.dag | 55 +++++--- .../pid_control_curve_decode_witness.dag | 31 +++- dag/gunbc/product/pcb/copper.dag | 21 ++- dag/gunbc/rung_drop.dag | 3 + dag/gunbc/spark/serving_converge_realize.dag | 8 +- dag/gunbc/tailscale_acl_phase2_credential.dag | 14 +- ...and_mt_mitchell_authority_witness_test.dag | 5 +- .../bmc_wif_delegation_chain_witness_test.dag | 10 +- .../check_coverage_admission_witness_test.dag | 5 +- .../commit_check_demand_witness_test.dag | 35 ++++- .../cpu_l1_cache_geometry_witness_test.dag | 5 +- .../cpu_l2_l3_cache_geometry_witness_test.dag | 15 +- ...deps_git_diff_name_status_witness_test.dag | 133 +++++++++++++----- .../floor/floor_preparation_witness_test.dag | 15 +- ...interconnect_and_sm_cache_witness_test.dag | 5 +- .../claim/grounded_principal_witness_test.dag | 10 +- .../ilm4926_designation_witness_test.dag | 10 +- ...shape_catalog_integration_witness_test.dag | 42 ++++-- dag/test/claim/machine_shape_witness_test.dag | 25 +++- ...t_collins_population_role_witness_test.dag | 10 +- ...roadmap_dispatch_actuator_witness_test.dag | 25 +++- ...oadmap_execution_contract_witness_test.dag | 5 +- ...dmap_verification_receipt_witness_test.dag | 9 +- .../claim/secret_rotation_witness_test.dag | 10 +- ...rk_serving_converge_slice_witness_test.dag | 5 +- .../srv3_install_media_fetch_witness_test.dag | 10 +- ...ilscale_acl_phase2_design_witness_test.dag | 15 +- ...tmux_session_list_outcome_witness_test.dag | 40 ++++-- 34 files changed, 552 insertions(+), 193 deletions(-) diff --git a/dag/extdeps/bmc/pid_control_decode.dag b/dag/extdeps/bmc/pid_control_decode.dag index 01058c465fb..9bba86df80c 100644 --- a/dag/extdeps/bmc/pid_control_decode.dag +++ b/dag/extdeps/bmc/pid_control_decode.dag @@ -375,11 +375,14 @@ type CurveDecode fn curve_join(path: DocumentPath, readings: List, outputs: List) -> CurveDecode { let unpaired_reading = filter(readings, r => indexed_decimal_entries_with(entries: outputs, index: r.index).length() == 0) let unpaired_output = filter(outputs, o => indexed_decimal_entries_with(entries: readings, index: o.index).length() == 0) - if unpaired_reading.length() > 0 { - CurveRefused { refusal: CurveReadingWithoutOutput { path: path, index: unpaired_reading.first().index } } - } else if unpaired_output.length() > 0 { - CurveRefused { refusal: CurveOutputWithoutReading { path: path, index: unpaired_output.first().index } } - } else { + match unpaired_reading.first() { + Present { value: stray_reading } => + CurveRefused { refusal: CurveReadingWithoutOutput { path: path, index: stray_reading.index } } + Absent => + match unpaired_output.first() { + Present { value: stray_output } => + CurveRefused { refusal: CurveOutputWithoutReading { path: path, index: stray_output.index } } + Absent => CurveDecoded { points: sort_by( map(readings, r => @@ -392,6 +395,7 @@ fn curve_join(path: DocumentPath, readings: List, outputs: List< point => point.index, ), } + } } } diff --git a/dag/extdeps/bmc/types.dag b/dag/extdeps/bmc/types.dag index 6b7be354b77..48008e14756 100644 --- a/dag/extdeps/bmc/types.dag +++ b/dag/extdeps/bmc/types.dag @@ -180,7 +180,10 @@ fn bmc_redfish_surface_for( if count == 0 { SurfaceShapeUncatalogued { firmware: firmware } } else if count == 1 { - SurfaceShapeKnown { shape: matches.first().shape } + match matches.first() { + Present { value: only } => SurfaceShapeKnown { shape: only.shape } + Absent => SurfaceShapeUncatalogued { firmware: firmware } + } } else { SurfaceShapeAmbiguous { firmware: firmware, matches: count } } diff --git a/dag/extdeps/languages/json/member_conservation.dag b/dag/extdeps/languages/json/member_conservation.dag index 13760b16590..93be7390b1a 100644 --- a/dag/extdeps/languages/json/member_conservation.dag +++ b/dag/extdeps/languages/json/member_conservation.dag @@ -198,13 +198,14 @@ fn first_path_mismatch(partition_path: NonEmptyStr, occurrences: List ConservationVerdict { let stray_source = first_path_mismatch(partition_path: partition.object_path, occurrences: partition.source) - if stray_source.length() > 0 { - PartitionSourcePathMismatch { - partition_path: partition.object_path, - occurrence_path: stray_source.first().identity.object_path, - ordinal: stray_source.first().identity.ordinal, - } - } else { + match stray_source.first() { + Present { value: stray } => + PartitionSourcePathMismatch { + partition_path: partition.object_path, + occurrence_path: stray.identity.object_path, + ordinal: stray.identity.ordinal, + } + Absent => let lost_or_repeated = fold( partition.source, init: MembersConserved { member_count: partition.source.length() }, @@ -370,13 +371,14 @@ fn element_conservation_verdict(partition: ArrayElementPartition) -> Conservatio array_path: partition.array_path, occurrences: partition.source, ) - if stray_source.length() > 0 { - PartitionSourcePathMismatch { - partition_path: partition.array_path, - occurrence_path: stray_source.first().identity.array_path, - ordinal: stray_source.first().identity.index, - } - } else { + match stray_source.first() { + Present { value: stray } => + PartitionSourcePathMismatch { + partition_path: partition.array_path, + occurrence_path: stray.identity.array_path, + ordinal: stray.identity.index, + } + Absent => let lost_or_repeated = fold( partition.source, init: MembersConserved { member_count: partition.source.length() }, diff --git a/dag/extdeps/sudo/nopasswd_execute_probe_check_op.dag b/dag/extdeps/sudo/nopasswd_execute_probe_check_op.dag index 86207443ee2..ef69656b397 100644 --- a/dag/extdeps/sudo/nopasswd_execute_probe_check_op.dag +++ b/dag/extdeps/sudo/nopasswd_execute_probe_check_op.dag @@ -38,7 +38,10 @@ fn sudo_nopasswd_grant_list_line_admits_probe( if length(xs: parts) != 2 { false } else { - parts.skip(n: 1).first().trim() == probe as String + match parts.skip(n: 1).first() { + Present { value: tail } => tail.trim() == probe as String + Absent => false + } } } diff --git a/dag/extdeps/tailscale/acl.dag b/dag/extdeps/tailscale/acl.dag index ffa2c654ef8..350297766c9 100644 --- a/dag/extdeps/tailscale/acl.dag +++ b/dag/extdeps/tailscale/acl.dag @@ -260,10 +260,14 @@ fn tag_dashboard_admin_plane_holds(policy: TailscaleAclPolicy) -> Bool { fn dashboard_cutover_ip_is_transient_in_tag_ci_grant(policy: TailscaleAclPolicy) -> Bool { let ci_grants = policy.grants |> filter(g => grant_src_is_tag_ci(g: g)) (length(ci_grants) == 1) - && selector_list_contains( - items: ci_grants.first().dst, - needle: sel_ipv4(address: dashboard_cutover_mac_ip) - ) + && match ci_grants.first() { + Present { value: only } => + selector_list_contains( + items: only.dst, + needle: sel_ipv4(address: dashboard_cutover_mac_ip) + ) + Absent => false + } } fn ssh_rule_matches( diff --git a/dag/gunbc/bmc/bmc_fan_program_decode_witness.dag b/dag/gunbc/bmc/bmc_fan_program_decode_witness.dag index f2a7c83d13f..c0934564f1d 100644 --- a/dag/gunbc/bmc/bmc_fan_program_decode_witness.dag +++ b/dag/gunbc/bmc/bmc_fan_program_decode_witness.dag @@ -78,68 +78,115 @@ fn both_hosts_decode_completely() -> Bool { // ONE DEMAND, TWO ACTUATORS -- the structural claim of the model, read off the deployed document // rather than asserted about it. fn the_shape_is_one_demand_feeding_two_actuators() -> Bool { - let program = srv3_programs().first() - thermal_controllers(program: program).length() == 1 - && flat_map(program.zones, zone => zone.actuator_controllers).length() == 2 + match srv3_programs().first() { + Present { value: program } => + thermal_controllers(program: program).length() == 1 + && flat_map(program.zones, zone => zone.actuator_controllers).length() == 2 + Absent => false + } } // THE TWO ACTUATORS DIFFER EXACTLY WHERE THE ACOUSTIC OUTCOME LIVES: the feed-forward offset and // the output floor. Everything else about them is the same. These are the numbers that made the // fleet quiet, so they are pinned by value. fn the_actuators_carry_the_offsets_that_made_it_quiet() -> Bool { - let program = srv3_programs().first() - let fan1 = actuator_named(program: program, name: "FAN1") - let chassis = actuator_named(program: program, name: "CHASSIS") - fan1.length() == 1 - && chassis.length() == 1 - && exact_decimal_wire(d: fan1.first().feed_forward.offset.magnitude) == "-8" - && exact_decimal_wire(d: fan1.first().output_limits.minimum.magnitude) == "3" - && exact_decimal_wire(d: chassis.first().feed_forward.offset.magnitude) == "35" - && exact_decimal_wire(d: chassis.first().output_limits.minimum.magnitude) == "40" - && exact_decimal_wire(d: chassis.first().slew.negative_coefficient) == "-3" + match srv3_programs().first() { + Present { value: program } => + let fan1 = actuator_named(program: program, name: "FAN1") + let chassis = actuator_named(program: program, name: "CHASSIS") + fan1.length() == 1 + && chassis.length() == 1 + && match fan1.first() { + Present { value: a } => + exact_decimal_wire(d: a.feed_forward.offset.magnitude) == "-8" + && exact_decimal_wire(d: a.output_limits.minimum.magnitude) == "3" + Absent => false + } + && match chassis.first() { + Present { value: a } => + exact_decimal_wire(d: a.feed_forward.offset.magnitude) == "35" + && exact_decimal_wire(d: a.output_limits.minimum.magnitude) == "40" + && exact_decimal_wire(d: a.slew.negative_coefficient) == "-3" + Absent => false + } + Absent => false + } } // THE CHASSIS ACTUATOR DRIVES THREE FANS AND FAN1 DRIVES ONE, which is the wiring fact the // document states and the reason one offset cannot serve both. fn the_actuator_endpoint_populations_differ() -> Bool { - let program = srv3_programs().first() - actuator_named(program: program, name: "FAN1").first().inputs.length() == 1 - && actuator_named(program: program, name: "CHASSIS").first().inputs.length() == 3 + match srv3_programs().first() { + Present { value: program } => + match actuator_named(program: program, name: "FAN1").first() { + Present { value: fan1 } => fan1.inputs.length() == 1 + Absent => false + } + && match actuator_named(program: program, name: "CHASSIS").first() { + Present { value: chassis } => chassis.inputs.length() == 3 + Absent => false + } + Absent => false + } } fn the_deployed_curve_survives_the_whole_program_decode() -> Bool { - let program = srv3_programs().first() - let demand = thermal_controllers(program: program).first() - demand.curve.length() == 9 - && demand.name == "TEMP_SOC" - && exact_decimal_wire(d: demand.failsafe_output.magnitude) == "75" + match srv3_programs().first() { + Present { value: program } => + match thermal_controllers(program: program).first() { + Present { value: demand } => + demand.curve.length() == 9 + && demand.name == "TEMP_SOC" + && exact_decimal_wire(d: demand.failsafe_output.magnitude) == "75" + Absent => false + } + Absent => false + } } // THE TWO HOSTS AGREE ON THE COEFFICIENT THEY SPELL DIFFERENTLY. srv3 writes minThermalOutput as // 30.0 and srv4 writes it as 30; a decoder comparing lexemes would report two different programs. fn the_two_hosts_agree_on_the_coefficient_they_spell_differently() -> Bool { - let srv3_zone = srv3_programs().first().zones.first() - let srv4_zone = srv4_programs().first().zones.first() - exact_decimal_wire(d: srv3_zone.minimum_thermal_output.magnitude) - == exact_decimal_wire(d: srv4_zone.minimum_thermal_output.magnitude) + match srv3_programs().first() { + Present { value: srv3 } => + match srv4_programs().first() { + Present { value: srv4 } => + match srv3.zones.first() { + Present { value: srv3_zone } => + match srv4.zones.first() { + Present { value: srv4_zone } => + exact_decimal_wire(d: srv3_zone.minimum_thermal_output.magnitude) + == exact_decimal_wire(d: srv4_zone.minimum_thermal_output.magnitude) + Absent => false + } + Absent => false + } + Absent => false + } + Absent => false + } } // THE THREE-STATE D-BUS BOUNDS FIELD IS EXERCISED BY THE REAL DOCUMENT, not only by a fixture: at // least one captured sensor entry does not state it, which is why the third state exists. fn a_captured_sensor_leaves_the_dbus_bounds_unstated() -> Bool { - let program = srv4_programs().first() + match srv4_programs().first() { + Present { value: program } => filter(program.sensors, sensor => match sensor.dbus_bounds { DbusBoundsUnstated => true DbusBoundsIgnored => false DbusBoundsUsed => false }).length() > 0 + Absent => false + } } // A TEMPERATURE SENSOR HAS NOTHING TO DRIVE AND A FAN SENSOR DOES, which the model carries as a // coproduct rather than an absent string. fn writability_follows_the_sensor_role() -> Bool { - let program = srv3_programs().first() + match srv3_programs().first() { + Present { value: program } => let temps = filter(program.sensors, sensor => match sensor.role { TemperatureSensor => true FanTachometerSensor => false }) let fans = filter(program.sensors, sensor => @@ -148,6 +195,8 @@ fn writability_follows_the_sensor_role() -> Bool { && fans.length() > 0 && all(temps, sensor => match sensor.actuation { SensorReadOnly => true SensorDrivesOutput { write_path } => false }) && all(fans, sensor => match sensor.actuation { SensorDrivesOutput { write_path } => true SensorReadOnly => false }) + Absent => false + } } fn bmc_fan_program_decode_witness() -> ProcessExit { diff --git a/dag/gunbc/commit_workflow.dag b/dag/gunbc/commit_workflow.dag index a4cd8ab06e8..a5cc13ebdcf 100644 --- a/dag/gunbc/commit_workflow.dag +++ b/dag/gunbc/commit_workflow.dag @@ -861,14 +861,20 @@ fn commit_writer_blob_coverage_refusals( } else if count(matching_index) > 1 { [] } else { - let index_entry = matching_index.first() - if git_object_id_eq(left: index_entry.oid, right: blob.indexed_oid) { - [] - } else { - [CommitWriterIndexBlobIdentityMismatchRefusal { + match matching_index.first() { + Present { value: index_entry } => + if git_object_id_eq(left: index_entry.oid, right: blob.indexed_oid) { + [] + } else { + [CommitWriterIndexBlobIdentityMismatchRefusal { + path: blob.path, + index_oid: index_entry.oid, + observed_index_oid: blob.indexed_oid, + }] + } + Absent => [CommitWriterExtraBlobObservationRefusal { path: blob.path, - index_oid: index_entry.oid, - observed_index_oid: blob.indexed_oid, + indexed_oid: blob.indexed_oid, }] } }, @@ -2500,12 +2506,14 @@ fn demand_check_count(check: CommitCheckKind, rows: List) -> DuplicateCommitCheck { - if roster.count() == 0 { - NoDuplicateCommitCheck - } else if commit_check_count(check: roster.first().check, roster: roster) > 1 { - DuplicateCommitCheckFound { check: roster.first().check } - } else { - first_duplicate_commit_check(roster: roster.skip(n: 1)) + match roster.first() { + Absent => NoDuplicateCommitCheck + Present { value: head } => + if commit_check_count(check: head.check, roster: roster) > 1 { + DuplicateCommitCheckFound { check: head.check } + } else { + first_duplicate_commit_check(roster: roster.skip(n: 1)) + } } } @@ -2729,10 +2737,10 @@ fn project_enrollment_witness_demands_unchecked( roster: List, discoveries: List, ) -> EnrollmentDemandOutcome { - if roster.count() == 0 { - DemandQualified { rows: [] } - } else { - let check = roster.first().check + match roster.first() { + Absent => DemandQualified { rows: [] } + Present { value: head } => + let check = head.check match project_one_enrollment_witness_demand( check: check, discoveries: discoveries @@ -2798,9 +2806,16 @@ fn project_enrollment_witness_demands( if demand_rows_match_roster(roster: roster, rows: rows) { DemandQualified { rows: rows } } else { - DemandIncomplete { - check: roster.first().check, - cause: "projected demand check set does not equal input roster" as NonEmptyStr + match roster.first() { + Present { value: head } => + DemandIncomplete { + check: head.check, + cause: "projected demand check set does not equal input roster" as NonEmptyStr + } + Absent => + DemandRosterAmbiguous { + cause: "empty commit check roster names no check for an incomplete projection" as NonEmptyStr + } } } } diff --git a/dag/gunbc/pid_control_curve_decode_witness.dag b/dag/gunbc/pid_control_curve_decode_witness.dag index 3add4f46a5b..d53737d8e85 100644 --- a/dag/gunbc/pid_control_curve_decode_witness.dag +++ b/dag/gunbc/pid_control_curve_decode_witness.dag @@ -53,8 +53,12 @@ fn the_deployed_curve_decodes_paired() -> Bool { let third = point_at(points: points, index: 3) points.length() == 9 && third.length() == 1 - && exact_decimal_wire(d: measure_count(m: third.first().reading)) == "66" - && exact_decimal_wire(d: third.first().output.magnitude) == "44" + && match third.first() { + Present { value: p } => + exact_decimal_wire(d: measure_count(m: p.reading)) == "66" + && exact_decimal_wire(d: p.output.magnitude) == "44" + Absent => false + } } // PAIRING IS BY KEY IDENTITY, NOT BY POSITION, and this is the discriminator that proves it: the @@ -69,9 +73,15 @@ fn pairing_survives_a_reordered_output_object() -> Bool { let zeroth = point_at(points: points, index: 0) points.length() == 4 && third.length() == 1 - && exact_decimal_wire(d: third.first().output.magnitude) == "44" + && match third.first() { + Present { value: p } => exact_decimal_wire(d: p.output.magnitude) == "44" + Absent => false + } && zeroth.length() == 1 - && exact_decimal_wire(d: zeroth.first().output.magnitude) == "22" + && match zeroth.first() { + Present { value: p } => exact_decimal_wire(d: p.output.magnitude) == "22" + Absent => false + } } // A NON-CANONICAL KEY IS NOT AN INDEX THE DAEMON ASKS FOR. phosphor-pid-control requests the exact @@ -111,9 +121,16 @@ data reordered_reading_curve_text: String = "\{\"reading\": \{\"2\": 62, \"0\": fn member_order_does_not_change_the_semantic_curve() -> Bool { let points = decoded_points(text: reordered_reading_curve_text) points.length() == 3 - && points.first().index == 0 - && exact_decimal_wire(d: measure_count(m: points.first().reading)) == "40" - && exact_decimal_wire(d: measure_count(m: points.skip(n: 2).first().reading)) == "62" + && match points.first() { + Present { value: p } => + p.index == 0 + && exact_decimal_wire(d: measure_count(m: p.reading)) == "40" + Absent => false + } + && match points.skip(n: 2).first() { + Present { value: p } => exact_decimal_wire(d: measure_count(m: p.reading)) == "62" + Absent => false + } } // A KEY THAT IS NOT AN INDEX AT ALL REFUSES RATHER THAN BEING SKIPPED. A skipped key is a curve diff --git a/dag/gunbc/product/pcb/copper.dag b/dag/gunbc/product/pcb/copper.dag index d7f76ff151e..d8aad6d546c 100644 --- a/dag/gunbc/product/pcb/copper.dag +++ b/dag/gunbc/product/pcb/copper.dag @@ -78,11 +78,15 @@ fn select_copper_layer(a: AdmittedStackup, ordinal: Int) -> LayerSelection { if ordinal < 0 || ordinal >= n { LayerOutsideStackup { requested: ordinal, layer_count: n } } else { - mint_layer_ref( - stackup_authority: admitted_stackup_authority(a: a), - ordinal: ordinal, - function: nth_layer_function(layers: layers, ordinal: ordinal), - ) + match nth_layer_function(layers: layers, ordinal: ordinal) { + Present { value: function } => + mint_layer_ref( + stackup_authority: admitted_stackup_authority(a: a), + ordinal: ordinal, + function: function, + ) + Absent => LayerOutsideStackup { requested: ordinal, layer_count: n } + } } } @@ -108,8 +112,11 @@ fn scan_for_ordinal(layers: List, ordinal: Int) -> LayerScan { ) } -fn nth_layer_function(layers: List, ordinal: Int) -> CopperLayerFunction { - scan_for_ordinal(layers: layers, ordinal: ordinal).picked.first().function +fn nth_layer_function(layers: List, ordinal: Int) -> Optional { + match scan_for_ordinal(layers: layers, ordinal: ordinal).picked.first() { + Present { value: picked } => Present { value: picked.function } + Absent => Absent + } } fn copper_layer_ordinal(r: CopperLayerRef) -> Int { diff --git a/dag/gunbc/rung_drop.dag b/dag/gunbc/rung_drop.dag index 476361d492c..5b2061aec9a 100644 --- a/dag/gunbc/rung_drop.dag +++ b/dag/gunbc/rung_drop.dag @@ -100,7 +100,10 @@ data direct_call_arg_seam_v2_exemption: RungDrop = RungDrop { authored: "**ONE OF THE TWO DIRECT-CALL ARGUMENT JUDGMENTS IS SWITCHED OFF FOR EVERY `v2.*` MODULE, AND THIS ROW DECLARES THAT RUNG (2026-09-01).** The suppressed arm is `arg_compat_diags`; the inhabitance arm beside it is ungated and still runs there. That split is measured below, not assumed, and the loose reading -- that argument checking is off in `v2.*` -- is what this row exists to stop being repeated. `v1.compiler.infer` `module_skips_direct_call_arg_check` returns true for every module whose declared name begins with `v2.`, so at a direct call inside such a module the argument-position TYPE-COMPAT judgment does not run. It is ONE of two arms and the measurement below says which: the ungated inhabitance arm still runs there, so this is a narrowed judgment, not an absent one. The predicate is not new and its cost is not disputed: `gunbc.doc_graph_roots` already names it as the one in-tree violation of the no-escape-hatch clause, in its own words that the compiler being bootstrapped is authored under weaker checks than ordinary source. What has never existed is this row. PREVIOUS RUNG AT THIS ROW'S OWN SUBJECT GRAIN: NONE STOOD, and saying otherwise would be the cross-path inflation DESIGN 4b(1) forbids (codex review 58154, which caught it here). The subject is the compat arm INSIDE `v2.*`. That arm has never executed there -- the exemption predates this row -- so there is no rung for this row to have lowered and none is claimed. What the fixture establishes is a DIFFERENT path: that the suppressed check operates OUTSIDE the exemption, on the same tree and the same binary, which is what makes the exemption a real loss of an available guarantee rather than a check nobody has. Those two facts must not be averaged: `mechanically preventable outside v2.*` and `never executed inside v2.*` are separate paths, and a class's rung is the MINIMUM across its in-scope paths. This row therefore declares a STANDING ABSENCE at its subject grain rather than a regression, and it is filed as a drop because the obligation 4b(3) attaches -- population, reason, and a trigger that can retire it -- is the obligation an undeclared permanent exemption was escaping. TEMPORARY RUNG for the compat arm inside `v2.*`: mitigatable where the emitted-Rust self-host closure covers the module, because a wrong argument surviving acceptance becomes a rustc type error in the emitted crate -- refused late, in the wrong compiler, in the wrong phase -- and UNGUARDED on the source-acceptance path for every `v2.*` module that closure does not reach. Those are two paths and the row reports the MINIMUM, so the temporary rung for this subject is UNGUARDED, not mitigatable; the mitigated path is named because it bounds where the loss is observable at all, never to raise the reported rung. The ungated inhabitance arm keeps its rung throughout and is NOT part of this drop. REASON: the exemption's stated justification is representation-gap false positives at the argument seam — the same four classes the conformance lane grounds (brand aliases, optionality's two representations, anonymous record literals, expansion depth) — plus an unlocated historical claim of 104 TypeMismatch false positives that two audits have failed to find a receipt for. Its deletion is gunbc#8924, which is COMPLETE and MEASURED and CLOSED, held because its one missing precondition is a resolve-layer seam whose repair was DECLINED at the owning layer on 2026-08-22 with no owner and no schedule. THE POPULATION IS RESTATED HERE AND IT IS NOT THE ONE THE HOLD WAS PRICED AGAINST. The reopening condition recorded on `gunbc.doc_graph_roots` bounds the blast radius at 9 declarations under `src/v2/extdeps/formatters/`. The predicate's actual population is EVERY MODULE WHOSE DECLARED NAME BEGINS WITH `v2.` — the instrument is the predicate itself, re-derivable by matching `^module v2.` against the corpus's module declarations, and the count is deliberately not transcribed here. That population includes the SELF-HOST EMISSION CLOSURE. WHAT THE EXEMPTION ACTUALLY SUPPRESSES, MEASURED AT DIAGNOSTIC IDENTITY RATHER THAN ARGUED FROM THE PREDICATE. The instrument is one fixture authored twice, byte-identical but for its module line and a renaming of every declaration so nothing resolves across the pair, run through a gunbc built from this tree. Three arms each: a local alias parameter (`type Wrapped = FreeMonoid` declared in the calling module), a cross-module declaration-path parameter (`v2.std.text.String`), and an `Int` parameter as control, each receiving a kernel `String` actual. NON-`v2.` MODULE: the local-alias arm is ADMITTED with no diagnostic; the declaration-path arm REFUSES with `value does not inhabit its declared type at the direct call argument`, declared `Node(std.algebra.FreeMonoid)` produced `Primitive(String)`; the control REFUSES TWICE, once with `type mismatch: expected Primitive(Int), got Primitive(String)` and once with the inhabitance message. `v2.*` MODULE: the local-alias arm is ADMITTED; the declaration-path arm REFUSES with the SAME inhabitance message; the control refuses with the inhabitance message ONLY. EXACTLY ONE DIAGNOSTIC IDENTITY DISAPPEARS across the pair -- the `type mismatch` row -- and every other identity is preserved. That is the drop, measured: this exemption suppresses `arg_compat_diags` and NOTHING ELSE. THE SCOPE OF THE LOSS IS THEREFORE NARROWER THAN THE PREDICATE NAME SUGGESTS, and stating it narrowly is the point of measuring it. `v1.compiler.infer` computes `arg_compat_diags` under this gate and calls `direct_call_argument_inhabitance_diags` immediately after with NO gate, both feeding one `concat`, so the INHABITANCE judgment runs inside `v2.*` exactly as it runs everywhere else. A reader who takes the argument judgment to be off in `v2.*` -- as the first draft of this row did -- will misattribute every surviving refusal and every surviving admission. CONSEQUENTLY THIS ROW CLAIMS NO SELF-HOST HARM AND NAMES NONE. The specimen anyone reaches for first is `src/v2/std/integer.dag` calling `v2.std.text` `string_head`, whose emitted Rust rustc refuses; that call is NOT hidden by this exemption, because the surviving inhabitance arm is the one that judges its shape and the fixture shows the compat arm would not have refused it either. That shape belongs to a DIFFERENT and already-standing declaration, `text_boundary_identity_wall`. Citing it here would be an unbacked execution claim. THE CLAIM IS NOT THAT THE BLAST RADIUS NOW EXCEEDS 9. It is that THE SUBJECT CHANGED and the old bound no longer describes what is being bought, which is a reason to re-measure; a declared drop whose population is measured against a subject it no longer covers is the drop lying about its own size. ONE CONSEQUENCE FOR ANY READER OF A GREEN `v2.*` CALL SITE, stated precisely because the loose version of it is the easiest false inference available here and this row asserted the loose version first: a `v2.*` call site accepted WITHOUT a compat diagnostic says nothing, because that arm cannot speak there; a `v2.*` call site accepted with no INHABITANCE diagnostic says exactly what it says everywhere else, because that arm is ungated. A discriminating red for the SUPPRESSED arm is therefore only authorable in a non-`v2.` module -- not because the wall is absent in `v2.*`, but because only one of its two arms is. RESTORATION TRIGGER, named at capability grain: the resolve-layer seam capability that makes `arg_compat_diags` RUNNABLE on `v2.*` modules -- the argument-position conformance relation grounding the representation gaps that motivated the exemption (brand aliases, optionality's two representations, anonymous record literals, expansion depth), so that the compat arm refuses genuinely wrong argument bindings inside `v2.*` without fabricating a refusal against those four grounded classes. That capability is SUFFICIENT only under a two-direction A/B measured the way this row's own population was: the twin fixture re-run, with the `type mismatch` identity PRESENT on both the non-`v2.` and the `v2.*` side, and the corpus's existing `v2.*` call sites still ADMITTED. gunbc#8924 MERGING IS NOT THIS TRIGGER, and neither is any measurement that only re-prices the hold; the trigger is the seam being repaired at its owning layer, which was DECLINED on 2026-08-22 and is UNOWNED as of this declaration -- a trigger nobody is holding is still a trigger, and a reader must be able to see that nobody is holding it." } +data optional_into_declared_nonoptional_parameter: RungDrop = RungDrop { identity: "optional_into_declared_nonoptional_parameter" as NonEmptyStr, subject: "An Optional actual flowing into a parameter declared non-optional, at the call seam, in BOTH the interpreter and the Rust emission arm", declared: "2026-09-01", standing: Standing, authored: "**AN OPTIONAL VALUE REACHING A PARAMETER DECLARED NON-OPTIONAL IS UNWRAPPED RATHER THAN REFUSED, AND THIS ROW DECLARES THAT RUNG (2026-09-01).** PREVIOUS RUNG: none stood at this subject grain, and claiming one would be the cross-path inflation DESIGN 4b(1) forbids. The coercion is not new and is not this PR's: the emitted Rust arm carries the literal `fail-closed: an optional value flowed into non-optional parameter` on main in six places across five emitted files, so the SILENT-UNWRAP half of the behaviour predates every change on gunbc#9785. What gunbc#9785 did was give the interpreter arm the SAME seam, so that the two arms answer one question the same way -- and in doing so it made a corpus-wide coercion VISIBLE at one site instead of invisible at two. TEMPORARY RUNG: mitigatable. The `Absent` case REFUSES with a typed, located `call-contract-mismatch` naming the parameter; only the `Present` case is coerced, and it is coerced by unwrapping to the payload, which is the value the declared parameter type names. So no `Absent` reaches a non-optional body, and no fabricated substitute is manufactured for one. What is lost is the DISTINCTION: a caller that meant to pass the Optional itself, and a caller that meant to pass its payload, are accepted identically, and the acceptance happens at RUNTIME from inside the callee rather than at the compile seam. REASON: nothing in the compile seam judges the optionality of a direct-call argument against its declared parameter type. `std.algebra` declares `first`, `last`, `get`, `lookup` and `map_get` with `OptionalOf { inner: ReceiverElement }`, so every one of their call sites produces an Optional; the corpus was authored against the interpreter's pre-repair behaviour, in which those spellings returned the bare element. Refusing at the seam without first migrating the sites would stop the line on the whole corpus at once, which is why the migration and the refusal are separate lanes and this row stands between them. POPULATION, BOUNDED AND STATED AT ITS OWN GRAIN: every direct call whose actual is an Optional and whose declared parameter is not, reachable today only from the five `std.algebra` spellings above; the instrument is the seam itself once it refuses, and the count is deliberately not transcribed here. The FIELD-ACCESS half of that population -- `.first().field`, which is the shape that reads an Optional's payload as a record -- is NOT covered by this row: it is migrated on gunbc#9785 and refuses loudly at typecheck, and a reader must not take this row as cover for it. RESTORATION TRIGGER, named at capability grain: the compile seam REFUSES an Optional flowing into a parameter declared non-optional, at both arms, with a typed and located diagnostic naming the parameter and both types. That capability is SUFFICIENT only under a discriminating pair executed on the real acceptance path: a fixture passing `xs.first()` to a parameter declared as the element type is REFUSED, and the same fixture passing the same call to a parameter declared `Optional` is ACCEPTED. Neither the migration of any number of call sites nor a lens counting them is this trigger -- a site census retires the corpus, never the coercion, and the coercion is what this row declares." } + data rung_drop_roster: List = [ + optional_into_declared_nonoptional_parameter, emitted_bytes_witness_required_lane, direct_call_arg_seam_v2_exemption, floor_cut, diff --git a/dag/gunbc/spark/serving_converge_realize.dag b/dag/gunbc/spark/serving_converge_realize.dag index abd10bdc01d..1c0fcf46f21 100644 --- a/dag/gunbc/spark/serving_converge_realize.dag +++ b/dag/gunbc/spark/serving_converge_realize.dag @@ -805,8 +805,12 @@ fn spark_serving_enable_linger_argv_matches_expected(argv: List) -> Bool ) { SparkServingInstallRemoteCommandsPresent { steps: expected } => if expected.length() == 1 { - match remote_operation_argv(operation: expected.first().operation) { - Present { value: words } => join(argv, "\0") == join(words, "\0") + match expected.first() { + Present { value: only } => + match remote_operation_argv(operation: only.operation) { + Present { value: words } => join(argv, "\0") == join(words, "\0") + Absent => false + } Absent => false } } else { diff --git a/dag/gunbc/tailscale_acl_phase2_credential.dag b/dag/gunbc/tailscale_acl_phase2_credential.dag index 73967537311..92fe2e83993 100644 --- a/dag/gunbc/tailscale_acl_phase2_credential.dag +++ b/dag/gunbc/tailscale_acl_phase2_credential.dag @@ -112,11 +112,15 @@ fn operator_bindings_admit_bmc_assimilator_sa(bindings: List) -> Bool { (length(bindings) == 1) - && bindings.first().member == operator_principal_member() - && bindings.first().role == role_secretmanager_secret_accessor - && bindings.first().project == bmc_secrets_project_id - && bindings.first().secret == (tailscale_acl_ephemeral_token_secret_id as String) - && starts_with(s: bindings.first().member, prefix: "user:") + && match bindings.first() { + Present { value: only } => + only.member == operator_principal_member() + && only.role == role_secretmanager_secret_accessor + && only.project == bmc_secrets_project_id + && only.secret == (tailscale_acl_ephemeral_token_secret_id as String) + && starts_with(s: only.member, prefix: "user:") + Absent => false + } } type TailscaleAclOperatorEphemeralCredentialOutcome diff --git a/dag/test/claim/altra_platform_and_mt_mitchell_authority_witness_test.dag b/dag/test/claim/altra_platform_and_mt_mitchell_authority_witness_test.dag index 9a4c45c0d5f..10397d17ed4 100644 --- a/dag/test/claim/altra_platform_and_mt_mitchell_authority_witness_test.dag +++ b/dag/test/claim/altra_platform_and_mt_mitchell_authority_witness_test.dag @@ -264,7 +264,10 @@ test fn w_mt_mitchell_revision_table_ends_at_the_modelled_revision() -> Bool { && latest.publication_date.year == 2022 && latest.publication_date.month == 9 && latest.publication_date.day == 9 - && mt_mitchell_revision_table.first().revision == "0.50" + && match mt_mitchell_revision_table.first() { + Present { value: h0 } => h0.revision == "0.50" + Absent => false + } } test fn w_mt_mitchell_architecture_options_divide_into_whole_dimms_per_channel() -> Bool { diff --git a/dag/test/claim/bmc/bmc_wif_delegation_chain_witness_test.dag b/dag/test/claim/bmc/bmc_wif_delegation_chain_witness_test.dag index 10b243d45d4..7062caa5feb 100644 --- a/dag/test/claim/bmc/bmc_wif_delegation_chain_witness_test.dag +++ b/dag/test/claim/bmc/bmc_wif_delegation_chain_witness_test.dag @@ -53,7 +53,10 @@ test fn bootstrap_grants_zero_bindings_for_bmc_secret() -> Bool { && grant_is_signed(grant: bmc_wif_canary_grant) && bmc_wif_signed_grants().length() == 1 && bmc_wif_bootstrap_bindings().length() == 1 - && bmc_wif_bootstrap_bindings().first().members.length() == 1 + && match bmc_wif_bootstrap_bindings().first() { + Present { value: h0 } => h0.members.length() == 1 + Absent => false + } } test fn unsigned_grant_row_count_matches_zero_derived_bindings() -> Bool { @@ -112,7 +115,10 @@ test fn secret_ref_login_handler_added_beside_factory_login_not_over_it() -> Boo test fn bmc_smoke_workflow_scopes_id_token_write_to_its_single_designated_job() -> Bool { bmc_token_smoke_workflow.jobs.length() == 1 - && bmc_token_smoke_workflow.jobs.first().id == bmc_token_smoke_job().id + && match jobs.first() { + Present { value: h0 } => bmc_token_smoke_workflow.h0.id == bmc_token_smoke_job().id + Absent => false + } && match bmc_token_smoke_workflow.permissions { Present { value: perms } => match perms.id_token { Present { value: _ } => true, Absent => false } Absent => false diff --git a/dag/test/claim/check_coverage_admission_witness_test.dag b/dag/test/claim/check_coverage_admission_witness_test.dag index 70db4cc5446..f49eef18845 100644 --- a/dag/test/claim/check_coverage_admission_witness_test.dag +++ b/dag/test/claim/check_coverage_admission_witness_test.dag @@ -89,7 +89,10 @@ test fn stopped_coverage_preserves_unexecuted_population_and_cause() -> Bool { head == required_head && roster == required_roster && length(completed) == 1 - && completed.first().gate == gate_a + && match completed.first() { + Present { value: h0 } => h0.gate == gate_a + Absent => false + } && length(unexecuted) == 1 && unexecuted.first() == gate_b && cause == "batch-1 generated-artifact drift stopped dependent witness batches" diff --git a/dag/test/claim/commit_check_demand_witness_test.dag b/dag/test/claim/commit_check_demand_witness_test.dag index 8d1553ccb2c..725a5c303c7 100644 --- a/dag/test/claim/commit_check_demand_witness_test.dag +++ b/dag/test/claim/commit_check_demand_witness_test.dag @@ -358,12 +358,21 @@ test fn commit_check_demand_named_multi_function_is_exact_and_permutation_invari DemandQualified { rows: right } => left.count() == 1 && right.count() == 1 - && left.first().declarations.count() == 2 + && match left.first() { + Present { value: h0 } => h0.declarations.count() == 2 + Absent => false + } && nth_declaration_name(rows: left, row: 0, declaration: 0) == "beta_holds" && nth_declaration_name(rows: left, row: 0, declaration: 1) == "alpha_holds" && declaration_ref_lists_eq( - left: left.first().declarations, - right: right.first().declarations + match left.first() { + Present { value: h0 } => left: h0.declarations, + Absent => false + } + match right.first() { + Present { value: h0 } => right: h0.declarations + Absent => false + } ) DemandMissing { check: _, cause: _ } => false DemandAmbiguous { check: _, cause: _ } => false @@ -387,10 +396,22 @@ test fn commit_check_demand_gate_and_fmt_rows_stay_typed_and_empty() -> Bool { match project_enrollment_witness_demands_unchecked(roster: roster, discoveries: []) { DemandQualified { rows } => rows.count() == 2 - && rows.first().declarations.count() == 0 - && rows.skip(n: 1).first().declarations.count() == 0 - && commit_check_kind_eq(a: rows.first().check, b: gate) - && commit_check_kind_eq(a: rows.skip(n: 1).first().check, b: fmt) + && match rows.first() { + Present { value: h0 } => h0.declarations.count() == 0 + Absent => false + } + && match rows.skip(n: 1).first() { + Present { value: h0 } => h0.declarations.count() == 0 + Absent => false + } + && match rows.first() { + Present { value: h0 } => commit_check_kind_eq(a: h0.check, b: gate) + Absent => false + } + && match rows.skip(n: 1).first() { + Present { value: h0 } => commit_check_kind_eq(a: h0.check, b: fmt) + Absent => false + } DemandMissing { check: _, cause: _ } => false DemandAmbiguous { check: _, cause: _ } => false DemandIncomplete { check: _, cause: _ } => false diff --git a/dag/test/claim/cpu_l1_cache_geometry_witness_test.dag b/dag/test/claim/cpu_l1_cache_geometry_witness_test.dag index dcf553c4c83..054b480b46d 100644 --- a/dag/test/claim/cpu_l1_cache_geometry_witness_test.dag +++ b/dag/test/claim/cpu_l1_cache_geometry_witness_test.dag @@ -32,6 +32,9 @@ test fn w_ampere_catalog_rows_share_cited_l1_geometry() -> Bool { test fn w_ampere_l1_citations_cover_all_fields() -> Bool { let citations = altra_max_m12830_catalog.l1_cache_citations list_length(items: citations) == 3 - && citations.first().authority == ampere_altra_rev_a1_datasheet_authority + && match citations.first() { + Present { value: h0 } => h0.authority == ampere_altra_rev_a1_datasheet_authority + Absent => false + } } diff --git a/dag/test/claim/cpu_l2_l3_cache_geometry_witness_test.dag b/dag/test/claim/cpu_l2_l3_cache_geometry_witness_test.dag index 7e03f91cf6e..978bb0b5d75 100644 --- a/dag/test/claim/cpu_l2_l3_cache_geometry_witness_test.dag +++ b/dag/test/claim/cpu_l2_l3_cache_geometry_witness_test.dag @@ -38,7 +38,10 @@ test fn w_ampere_catalog_rows_share_cited_l2_geometry() -> Bool { test fn w_ampere_l2_citations_cover_all_fields() -> Bool { let citations = ampere_altra_l2_cache_citations list_length(items: citations) == 2 - && citations.first().authority == ampere_altra_rev_a1_datasheet_authority + && match citations.first() { + Present { value: h0 } => h0.authority == ampere_altra_rev_a1_datasheet_authority + Absent => false + } } test fn w_ampere_l3_geometry_diverges_by_sku() -> Bool { @@ -53,7 +56,13 @@ test fn w_ampere_l3_geometry_diverges_by_sku() -> Bool { test fn w_ampere_l3_citations_diverge_by_family_datasheet() -> Bool { list_length(items: altra_max_m12830_catalog.l3_cache_citations) == 2 && list_length(items: altra_q6430_catalog.l3_cache_citations) == 2 - && altra_max_m12830_catalog.l3_cache_citations.first().authority == ampere_altra_rev_a1_datasheet_authority - && altra_q6430_catalog.l3_cache_citations.first().authority == ampere_altra_classic_rev_a1_datasheet_authority + && match l3_cache_citations.first() { + Present { value: h0 } => altra_max_m12830_catalog.h0.authority == ampere_altra_rev_a1_datasheet_authority + Absent => false + } + && match l3_cache_citations.first() { + Present { value: h0 } => altra_q6430_catalog.h0.authority == ampere_altra_classic_rev_a1_datasheet_authority + Absent => false + } } diff --git a/dag/test/claim/extdeps_git_diff_name_status_witness_test.dag b/dag/test/claim/extdeps_git_diff_name_status_witness_test.dag index 6116838e4be..eb1a6851826 100644 --- a/dag/test/claim/extdeps_git_diff_name_status_witness_test.dag +++ b/dag/test/claim/extdeps_git_diff_name_status_witness_test.dag @@ -32,14 +32,23 @@ test fn w_modified_single_entry() -> Bool { let raw = join_name_status_tokens(["M", "file.txt"]) let entries = well_formed_entries(raw: raw) entries.length() == 1 - && entries.first().path == "file.txt" - && match entries.first().old_path { - Absent => true - Present { value: _ } => false + && match entries.first() { + Present { value: h0 } => h0.path == "file.txt" + Absent => false + } + && match entries.first() { + Present { value: h0 } => match h0.old_path { + Absent => true + Present { value: _ } => false + } + Absent => false } - && match entries.first().status { - DiffModifiedStatus => true - _ => false + && match entries.first() { + Present { value: h0 } => match h0.status { + DiffModifiedStatus => true + _ => false + } + Absent => false } } @@ -47,9 +56,12 @@ test fn w_added_entry() -> Bool { let raw = join_name_status_tokens(["A", "new_file.txt"]) let entries = well_formed_entries(raw: raw) entries.length() == 1 - && match entries.first().status { - DiffAddedStatus => true - _ => false + && match entries.first() { + Present { value: h0 } => match h0.status { + DiffAddedStatus => true + _ => false + } + Absent => false } } @@ -57,10 +69,16 @@ test fn w_deleted_entry() -> Bool { let raw = join_name_status_tokens(["D", "gone.txt"]) let entries = well_formed_entries(raw: raw) entries.length() == 1 - && entries.first().path == "gone.txt" - && match entries.first().status { - DiffDeletedStatus => true - _ => false + && match entries.first() { + Present { value: h0 } => h0.path == "gone.txt" + Absent => false + } + && match entries.first() { + Present { value: h0 } => match h0.status { + DiffDeletedStatus => true + _ => false + } + Absent => false } } @@ -68,14 +86,23 @@ test fn w_renamed_entry_carries_similarity_and_old_path() -> Bool { let raw = join_name_status_tokens(["R100", "old_name.txt", "new_name.txt"]) let entries = well_formed_entries(raw: raw) entries.length() == 1 - && entries.first().path == "new_name.txt" - && match entries.first().old_path { - Present { value: old } => old == "old_name.txt" + && match entries.first() { + Present { value: h0 } => h0.path == "new_name.txt" + Absent => false + } + && match entries.first() { + Present { value: h0 } => match h0.old_path { + Present { value: old } => old == "old_name.txt" + Absent => false + } Absent => false } - && match entries.first().status { - DiffRenamedStatus { similarity: sim } => percent_count(p: sim) == 100 - _ => false + && match entries.first() { + Present { value: h0 } => match h0.status { + DiffRenamedStatus { similarity: sim } => percent_count(p: sim) == 100 + _ => false + } + Absent => false } } @@ -83,14 +110,23 @@ test fn w_copied_entry_carries_similarity_and_old_path() -> Bool { let raw = join_name_status_tokens(["C87", "source.txt", "copy.txt"]) let entries = well_formed_entries(raw: raw) entries.length() == 1 - && entries.first().path == "copy.txt" - && match entries.first().old_path { - Present { value: old } => old == "source.txt" + && match entries.first() { + Present { value: h0 } => h0.path == "copy.txt" Absent => false } - && match entries.first().status { - DiffCopiedStatus { similarity: sim } => percent_count(p: sim) == 87 - _ => false + && match entries.first() { + Present { value: h0 } => match h0.old_path { + Present { value: old } => old == "source.txt" + Absent => false + } + Absent => false + } + && match entries.first() { + Present { value: h0 } => match h0.status { + DiffCopiedStatus { similarity: sim } => percent_count(p: sim) == 87 + _ => false + } + Absent => false } } @@ -103,12 +139,27 @@ test fn w_mixed_multi_entry_blob_preserves_order_and_arity() -> Bool { ]) let entries = well_formed_entries(raw: raw) entries.length() == 4 - && entries.first().path == "a.txt" - && entries.skip(n: 1).first().path == "b.txt" - && entries.skip(n: 2).first().path == "c.txt" - && entries.skip(n: 3).first().path == "new_d.txt" - && match entries.skip(n: 3).first().old_path { - Present { value: old } => old == "old_d.txt" + && match entries.first() { + Present { value: h0 } => h0.path == "a.txt" + Absent => false + } + && match entries.skip(n: 1).first() { + Present { value: h0 } => h0.path == "b.txt" + Absent => false + } + && match entries.skip(n: 2).first() { + Present { value: h0 } => h0.path == "c.txt" + Absent => false + } + && match entries.skip(n: 3).first() { + Present { value: h0 } => h0.path == "new_d.txt" + Absent => false + } + && match entries.skip(n: 3).first() { + Present { value: h0 } => match h0.old_path { + Present { value: old } => old == "old_d.txt" + Absent => false + } Absent => false } } @@ -117,9 +168,12 @@ test fn w_unrecognized_status_letter_is_typed_not_absorbed() -> Bool { let raw = join_name_status_tokens(["X", "weird.txt"]) let entries = well_formed_entries(raw: raw) entries.length() == 1 - && match entries.first().status { - DiffUnknownGitStatus { raw: token } => token == "X" - _ => false + && match entries.first() { + Present { value: h0 } => match h0.status { + DiffUnknownGitStatus { raw: token } => token == "X" + _ => false + } + Absent => false } && git_diff_status_entries_any_unknown(entries: entries) } @@ -127,9 +181,12 @@ test fn w_unrecognized_status_letter_is_typed_not_absorbed() -> Bool { test fn w_malformed_rename_score_is_typed_unknown_not_absorbed() -> Bool { let raw = join_name_status_tokens(["Rxx", "old.txt", "new.txt"]) let entries = well_formed_entries(raw: raw) - match entries.first().status { - DiffUnknownGitStatus { raw: token } => token == "Rxx" - _ => false + match entries.first() { + Present { value: h0 } => match h0.status { + DiffUnknownGitStatus { raw: token } => token == "Rxx" + _ => false + } + Absent => false } && git_diff_status_entries_any_unknown(entries: entries) } diff --git a/dag/test/claim/floor/floor_preparation_witness_test.dag b/dag/test/claim/floor/floor_preparation_witness_test.dag index d2e26faead1..49c8078bcdd 100644 --- a/dag/test/claim/floor/floor_preparation_witness_test.dag +++ b/dag/test/claim/floor/floor_preparation_witness_test.dag @@ -162,7 +162,10 @@ test fn exact_prepared_artifact_set_serves() -> Bool { PreparedFloorSubjectReady { identity: identity, entries: entries } => identity == prepared_claim_subject_identity(request: request) && entries.count() == 2 - && entries.first().witness.entry == witness_a().entry + && match entries.first() { + Present { value: h0 } => h0.witness.entry == witness_a().entry + Absent => false + } PreparedFloorSubjectRefused { cause: _ } => false } } @@ -174,7 +177,10 @@ test fn missing_prepared_identity_refuses() -> Bool { ) { PreparedFloorSubjectRefused { cause: PreparedFloorMissingProbeIdentities { missing: missing } - } => missing.count() == 1 && missing.first().entry == witness_b().entry + match missing.first() { + Present { value: h0 } => } => missing.count() == 1 && h0.entry == witness_b().entry + Absent => false + } _ => false } } @@ -248,7 +254,10 @@ test fn unexpected_probe_identity_refuses() -> Bool { ) { PreparedFloorSubjectRefused { cause: PreparedFloorUnexpectedProbeIdentities { unexpected: unexpected } - } => unexpected.count() == 1 && unexpected.first().entry == witness_extra().entry + match unexpected.first() { + Present { value: h0 } => } => unexpected.count() == 1 && h0.entry == witness_extra().entry + Absent => false + } _ => false } } diff --git a/dag/test/claim/gpu_interconnect_and_sm_cache_witness_test.dag b/dag/test/claim/gpu_interconnect_and_sm_cache_witness_test.dag index 843859cc7e9..13229c2fdce 100644 --- a/dag/test/claim/gpu_interconnect_and_sm_cache_witness_test.dag +++ b/dag/test/claim/gpu_interconnect_and_sm_cache_witness_test.dag @@ -45,7 +45,10 @@ test fn w_rtx_5090_sm_cache_geometry_matches_cuda_docs() -> Bool { test fn w_rtx_5090_sm_cache_citations_cover_both_fields() -> Bool { let citations = rtx_5090_sm_cache_citations list_length(items: citations) == 2 - && citations.first().authority == cuda_compute_capabilities_authority + && match citations.first() { + Present { value: h0 } => h0.authority == cuda_compute_capabilities_authority + Absent => false + } } test fn w_apple_m5_gpu_leaves_unified_soc_facts_unfabricated() -> Bool { diff --git a/dag/test/claim/grounded_principal_witness_test.dag b/dag/test/claim/grounded_principal_witness_test.dag index 6ecc56fc460..86b63b74935 100644 --- a/dag/test/claim/grounded_principal_witness_test.dag +++ b/dag/test/claim/grounded_principal_witness_test.dag @@ -60,7 +60,10 @@ test fn witness_ci_runner_sudo_grants_derive_privileged_commands() -> Bool { length(grounded) == 6 && length(composed) == 6 && composed == grounded - && grounded.first().grant.command_path == deploy_access_apt_get_binary_path + && match grounded.first() { + Present { value: h0 } => h0.grant.command_path == deploy_access_apt_get_binary_path + Absent => false + } && !any(grounded, c => c.grant.command_path == "/usr/bin/bash") && any( grounded, @@ -87,7 +90,10 @@ test fn witness_gha_runner_context_is_not_os_account() -> Bool { } test fn witness_operator_secret_binding_uses_fleet_gcp_member() -> Bool { - operator_tailscale_acl_secret_bindings.first().member == fleet_operator_gcp_iam_member() + match operator_tailscale_acl_secret_bindings.first() { + Present { value: h0 } => h0.member == fleet_operator_gcp_iam_member() + Absent => false + } && operator_principal_member() == fleet_operator_gcp_iam_member() } diff --git a/dag/test/claim/ilm4926_designation_witness_test.dag b/dag/test/claim/ilm4926_designation_witness_test.dag index 31acc329976..bce503ac057 100644 --- a/dag/test/claim/ilm4926_designation_witness_test.dag +++ b/dag/test/claim/ilm4926_designation_witness_test.dag @@ -90,8 +90,14 @@ test fn w_each_attestation_carries_its_own_authority_and_wording() -> Bool { list_length(items: ampere_max) == 1 && list_length(items: ampere_classic) == 1 && list_length(items: ocp) == 1 - && ampere_max.first().wording == "device seated in ILM4926 socket" - && ocp.first().wording == "ILM4926" + && match ampere_max.first() { + Present { value: h0 } => h0.wording == "device seated in ILM4926 socket" + Absent => false + } + && match ocp.first() { + Present { value: h0 } => h0.wording == "ILM4926" + Absent => false + } } // THE OPEN QUESTION IS MODELLED, NOT ASSUMED AWAY. No cited document expands diff --git a/dag/test/claim/machine_shape_catalog_integration_witness_test.dag b/dag/test/claim/machine_shape_catalog_integration_witness_test.dag index e5ddb9c61ea..559814028e0 100644 --- a/dag/test/claim/machine_shape_catalog_integration_witness_test.dag +++ b/dag/test/claim/machine_shape_catalog_integration_witness_test.dag @@ -44,18 +44,42 @@ fn cpu_catalog_shape() -> MachineShape { } test fn w_cpu_cache_levels_integrated_into_memory_shape() -> Bool { - let memory = cpu_catalog_shape().domains.first().memory + match domains.first() { + Present { value: h0 } => let memory = cpu_catalog_shape().h0.memory + Absent => false + } list_length(items: memory) == 3 - && memory.first().id == ampere_l1_level - && byte_size_count(b: memory.first().capacity) == ampere_l1_bytes_per_core - && match memory.first().transfer_grain { - Present { value: grain } => byte_size_count(b: grain) == ampere_cache_line_bytes + && match memory.first() { + Present { value: h0 } => h0.id == ampere_l1_level + Absent => false + } + && match memory.first() { + Present { value: h0 } => byte_size_count(b: h0.capacity) == ampere_l1_bytes_per_core + Absent => false + } + && match memory.first() { + Present { value: h0 } => match h0.transfer_grain { + Present { value: grain } => byte_size_count(b: grain) == ampere_cache_line_bytes + Absent => false + } + Absent => false + } + && match memory.skip(n: 1).first() { + Present { value: h0 } => h0.id == ampere_l2_level + Absent => false + } + && match memory.skip(n: 1).first() { + Present { value: h0 } => byte_size_count(b: h0.capacity) == ampere_l2_bytes_per_core + Absent => false + } + && match memory.skip(n: 2).first() { + Present { value: h0 } => h0.id == ampere_l3_level + Absent => false + } + && match memory.skip(n: 2).first() { + Present { value: h0 } => byte_size_count(b: h0.capacity) == ampere_l3_bytes Absent => false } - && memory.skip(n: 1).first().id == ampere_l2_level - && byte_size_count(b: memory.skip(n: 1).first().capacity) == ampere_l2_bytes_per_core - && memory.skip(n: 2).first().id == ampere_l3_level - && byte_size_count(b: memory.skip(n: 2).first().capacity) == ampere_l3_bytes } test fn w_cpu_catalog_lane_count_matches_threads() -> Bool { diff --git a/dag/test/claim/machine_shape_witness_test.dag b/dag/test/claim/machine_shape_witness_test.dag index 85a5cb0d33d..29569527c30 100644 --- a/dag/test/claim/machine_shape_witness_test.dag +++ b/dag/test/claim/machine_shape_witness_test.dag @@ -73,7 +73,10 @@ fn shape_from_rtx5090_catalog() -> MachineShape { test fn catalog_derivation_matches_row() -> Bool { let shape = shape_from_rtx5090_catalog() - let memory = shape.domains.first().memory + match domains.first() { + Present { value: h0 } => let memory = shape.h0.memory + Absent => false + } let device = memory.skip(n: list_length(items: memory) - 1).first() match machine_shape_domain_lane_count(shape: shape) { Present { value: lanes } => @@ -106,14 +109,26 @@ fn shape_from_m5_catalog() -> MachineShape { } test fn gpu_sm_cache_levels_integrated_when_cited() -> Bool { - let memory = shape_from_rtx5090_catalog().domains.first().memory + match domains.first() { + Present { value: h0 } => let memory = shape_from_rtx5090_catalog().h0.memory + Absent => false + } list_length(items: memory) == 3 - && byte_size_count(b: memory.first().capacity) == rtx5090_sm_register_bytes - && byte_size_count(b: memory.skip(n: 1).first().capacity) == rtx5090_sm_shared_bytes + && match memory.first() { + Present { value: h0 } => byte_size_count(b: h0.capacity) == rtx5090_sm_register_bytes + Absent => false + } + && match memory.skip(n: 1).first() { + Present { value: h0 } => byte_size_count(b: h0.capacity) == rtx5090_sm_shared_bytes + Absent => false + } } test fn gpu_sm_cache_absent_when_ungranted() -> Bool { - list_length(items: shape_from_m5_catalog().domains.first().memory) == 1 + match domains.first() { + Present { value: h0 } => list_length(items: shape_from_m5_catalog().h0.memory) == 1 + Absent => false + } } test fn energy_derived_not_stored() -> Bool { diff --git a/dag/test/claim/mt_collins_population_role_witness_test.dag b/dag/test/claim/mt_collins_population_role_witness_test.dag index cdc0699d8dc..52255164b94 100644 --- a/dag/test/claim/mt_collins_population_role_witness_test.dag +++ b/dag/test/claim/mt_collins_population_role_witness_test.dag @@ -81,8 +81,14 @@ test fn w_the_source_pairs_all_resolve() -> Bool { match mt_collins_channel_population_roles { PopulationRolesResolved { roles: rs } => list_length(items: rs) == 16 - && rs.first().connector_used_at_one_dpc == 1 - && rs.first().additional_connector_at_two_dpc == 2 + && match rs.first() { + Present { value: h0 } => h0.connector_used_at_one_dpc == 1 + Absent => false + } + && match rs.first() { + Present { value: h0 } => h0.additional_connector_at_two_dpc == 2 + Absent => false + } && rs.last().connector_used_at_one_dpc == 25 && rs.last().additional_connector_at_two_dpc == 26 && fold(rs, init: true, f: fn(acc, r) { diff --git a/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag b/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag index 2ebc54dc4ae..cfe09d59f7a 100644 --- a/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag +++ b/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag @@ -481,9 +481,18 @@ test fn witness_tmux_attempt_panes_parse_alive_and_exit() -> Bool { ) { AttemptPanesParsed { panes } => count(panes) == 2 - && !panes.first().dead - && panes.skip(n: 1).first().dead - && panes.skip(n: 1).first().exit_code == Present { value: 7 } + && match panes.first() { + Present { value: h0 } => !h0.dead + Absent => false + } + && match panes.skip(n: 1).first() { + Present { value: h0 } => h0.dead + Absent => false + } + && match panes.skip(n: 1).first() { + Present { value: h0 } => h0.exit_code == Present { value: 7 } + Absent => false + } AttemptPanesParseRefused { line_number: _, raw: _, reason: _ } => false } } @@ -565,8 +574,14 @@ test fn witness_tmux_ls_parse_filters_prefix() -> Bool { TmuxLsParsed { entries } => { let filtered = filter_dispatch_tmux_sessions(entries: entries) count(filtered) == 1 - && filtered.first().session_name == "gunbc-dispatch-node-4" - && filtered.first().windows == 2 + && match filtered.first() { + Present { value: h0 } => h0.session_name == "gunbc-dispatch-node-4" + Absent => false + } + && match filtered.first() { + Present { value: h0 } => h0.windows == 2 + Absent => false + } } TmuxLsParseRefused { line_number: _, raw: _, reason: _ } => false } diff --git a/dag/test/claim/roadmap/roadmap_execution_contract_witness_test.dag b/dag/test/claim/roadmap/roadmap_execution_contract_witness_test.dag index 56b4b525c85..90e29c6ff11 100644 --- a/dag/test/claim/roadmap/roadmap_execution_contract_witness_test.dag +++ b/dag/test/claim/roadmap/roadmap_execution_contract_witness_test.dag @@ -51,7 +51,10 @@ test fn dag_claim_derives_only_the_claim_runner() -> Bool { ), ) count(capabilities) == 1 - && execution_capability_label(capability: capabilities.first().value) == "gunbc-claim-runner" + && match capabilities.first() { + Present { value: h0 } => execution_capability_label(capability: h0.value) == "gunbc-claim-runner" + Absent => false + } } test fn cargo_test_derives_cargo_and_rustc_once() -> Bool { diff --git a/dag/test/claim/roadmap/roadmap_verification_receipt_witness_test.dag b/dag/test/claim/roadmap/roadmap_verification_receipt_witness_test.dag index dd132566be5..f643dc1e7af 100644 --- a/dag/test/claim/roadmap/roadmap_verification_receipt_witness_test.dag +++ b/dag/test/claim/roadmap/roadmap_verification_receipt_witness_test.dag @@ -106,7 +106,14 @@ test fn complete_exact_head_receipt_constructs_and_matches() -> Bool { ) { VerificationEvidenceForSubject { receipt: found } => count(found.executions) == 1 - && found.executions.first().command.args.first() == "run" + && match found.executions.first() { + Present { value: h0 } => + match h0.command.args.first() { + Present { value: a0 } => a0 == "run" + Absent => false + } + Absent => false + } && verification_evidence_moves_lamp( evidence: VerificationEvidenceForSubject { receipt: found }, ) diff --git a/dag/test/claim/secret_rotation_witness_test.dag b/dag/test/claim/secret_rotation_witness_test.dag index 30f8cbfc392..972994ee8fe 100644 --- a/dag/test/claim/secret_rotation_witness_test.dag +++ b/dag/test/claim/secret_rotation_witness_test.dag @@ -549,7 +549,10 @@ test fn retirement_targets_every_enabled_version_except_the_added_one() -> Bool ]), ) { RetirementAuthorized { targets: t, destroy_permitted: d } => - (t |> count) == 2 && !d && t.first().etag == concat("etag-", v1_name) + match t.first() { + Present { value: h0 } => (t |> count) == 2 && !d && h0.etag == concat("etag-", v1_name) + Absent => false + } RetirementRefused { cause: _ } => false } } @@ -634,7 +637,10 @@ test fn retirement_advances_when_the_prior_version_carries_an_etag() -> Bool { ]), ) { RetirementAuthorized { targets: t, destroy_permitted: _ } => - (t |> count) == 1 && t.first().etag == concat("etag-", v2_name) + match t.first() { + Present { value: h0 } => (t |> count) == 1 && h0.etag == concat("etag-", v2_name) + Absent => false + } RetirementRefused { cause: _ } => false } } diff --git a/dag/test/claim/spark/spark_serving_converge_slice_witness_test.dag b/dag/test/claim/spark/spark_serving_converge_slice_witness_test.dag index a8ed2df409e..ddcd8305dcb 100644 --- a/dag/test/claim/spark/spark_serving_converge_slice_witness_test.dag +++ b/dag/test/claim/spark/spark_serving_converge_slice_witness_test.dag @@ -160,7 +160,10 @@ test fn two_install_effects_realize_as_two_commands_not_one_fused_argv() -> Bool ) { SparkServingInstallRemoteCommandsPresent { steps: sts } => remote_operations_argv_only(operations: map(sts, st => st.operation)).length() == 2 - && remote_operations_argv_only(operations: map(sts, st => st.operation)).first().length() == 5 + && match remote_operations_argv_only(operations: map(sts, st => st.operation)).first() { + Present { value: h0 } => h0.length() == 5 + Absent => false + } && spark_serving_enable_linger_argv_matches_expected(argv: remote_operations_argv_only(operations: map(sts, st => st.operation)).first()) SparkServingInstallRemoteCommandsRefused { reason: _ } => false } diff --git a/dag/test/claim/srv3/srv3_install_media_fetch_witness_test.dag b/dag/test/claim/srv3/srv3_install_media_fetch_witness_test.dag index 444c1ade0bf..47b6f445bc6 100644 --- a/dag/test/claim/srv3/srv3_install_media_fetch_witness_test.dag +++ b/dag/test/claim/srv3/srv3_install_media_fetch_witness_test.dag @@ -51,8 +51,14 @@ test fn install_media_fetch_absent_plans_mirror_order() -> Bool { FetchRequired { install_path: p, mirror_order: mirrors } => p == srv3_ubuntu_install_media_install_path && mirrors == ubuntu_install_media_mirror_rows - && mirrors.first().host == ReleasesUbuntuCom - && mirrors.skip(n: 1).first().host == CdimageUbuntuCom + && match mirrors.first() { + Present { value: h0 } => h0.host == ReleasesUbuntuCom + Absent => false + } + && match mirrors.skip(n: 1).first() { + Present { value: h0 } => h0.host == CdimageUbuntuCom + Absent => false + } _ => false } } diff --git a/dag/test/claim/tailscale_acl_phase2_design_witness_test.dag b/dag/test/claim/tailscale_acl_phase2_design_witness_test.dag index 45705eadc8e..65baacc7731 100644 --- a/dag/test/claim/tailscale_acl_phase2_design_witness_test.dag +++ b/dag/test/claim/tailscale_acl_phase2_design_witness_test.dag @@ -3,13 +3,22 @@ module test.claim.tailscale_acl_phase2_design_witness data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly test fn witness_secret_homed_in_gunbai_secrets() -> Bool { - operator_tailscale_acl_secret_bindings.first().project == bmc_secrets_project_id - && operator_tailscale_acl_secret_bindings.first().secret == (tailscale_acl_ephemeral_token_secret_id as String) + match operator_tailscale_acl_secret_bindings.first() { + Present { value: h0 } => h0.project == bmc_secrets_project_id + Absent => false + } + && match operator_tailscale_acl_secret_bindings.first() { + Present { value: h0 } => h0.secret == (tailscale_acl_ephemeral_token_secret_id as String) + Absent => false + } } test fn witness_operator_user_principal_accessor_only() -> Bool { operator_bindings_are_user_principal_accessor_only(bindings: operator_tailscale_acl_secret_bindings) - && operator_tailscale_acl_secret_bindings.first().role == role_secretmanager_secret_accessor + && match operator_tailscale_acl_secret_bindings.first() { + Present { value: h0 } => h0.role == role_secretmanager_secret_accessor + Absent => false + } && operator_principal_member() == fleet_operator_gcp_iam_member() && string_contains(s: operator_principal_member(), pattern: tailscale_acl_operator_email() as String) && tailscale_acl_operator_email() == fleet_operator_email diff --git a/dag/test/claim/tmux_session_list_outcome_witness_test.dag b/dag/test/claim/tmux_session_list_outcome_witness_test.dag index ae28893a577..eeaaf154b0f 100644 --- a/dag/test/claim/tmux_session_list_outcome_witness_test.dag +++ b/dag/test/claim/tmux_session_list_outcome_witness_test.dag @@ -83,10 +83,22 @@ test fn witness_exit_ok_parses_sessions() -> Bool { match classify_tmux_session_list(exit_ok: true, stdout: output, stderr: "") { TmuxSessionsListed { entries } => count(entries) == 2 - && entries.first().session_name == "alpha" - && entries.first().windows == 1 - && entries.skip(n: 1).first().session_name == "beta" - && entries.skip(n: 1).first().windows == 2 + && match entries.first() { + Present { value: h0 } => h0.session_name == "alpha" + Absent => false + } + && match entries.first() { + Present { value: h0 } => h0.windows == 1 + Absent => false + } + && match entries.skip(n: 1).first() { + Present { value: h0 } => h0.session_name == "beta" + Absent => false + } + && match entries.skip(n: 1).first() { + Present { value: h0 } => h0.windows == 2 + Absent => false + } TmuxNoServer { evidence: _ } => false TmuxListUnknowable { stderr: _ } => false TmuxListStdoutRefused { line_number: _, raw: _, reason: _ } => false @@ -98,7 +110,10 @@ test fn witness_ls_output_parse_matches_classifier() -> Bool { match parse_tmux_ls_output(output: output) { TmuxLsParsed { entries } => count(entries) == 1 - && entries.first().session_name == "gunbc-dispatch-node-4" + && match entries.first() { + Present { value: h0 } => h0.session_name == "gunbc-dispatch-node-4" + Absent => false + } TmuxLsParseRefused { line_number: _, raw: _, reason: _ } => false } } @@ -161,9 +176,18 @@ test fn witness_attempt_panes_parse_alive_and_exit() -> Bool { ) { AttemptPanesParsed { panes } => count(panes) == 2 - && !panes.first().dead - && panes.skip(n: 1).first().dead - && panes.skip(n: 1).first().exit_code == Present { value: 7 } + && match panes.first() { + Present { value: h0 } => !h0.dead + Absent => false + } + && match panes.skip(n: 1).first() { + Present { value: h0 } => h0.dead + Absent => false + } + && match panes.skip(n: 1).first() { + Present { value: h0 } => h0.exit_code == Present { value: 7 } + Absent => false + } AttemptPanesParseRefused { line_number: _, raw: _, reason: _ } => false } } From 430f8f05fe43c7fa4d64ab3bfc3075291dc2ba87 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 18:37:33 +0000 Subject: [PATCH 24/28] Repair two codemod-mangled witness sites, and project the declared row The mechanical first() migration rewrote three sites whose conjunct spanned a match arm header or a call's argument list, splicing a match into positions the grammar does not admit. CI's parse phase caught two files; the local corpus parse caught a third that CI never reached, because the first refusal stopped the phase. All three are re-derived by hand from the pre-migration source and carry the same Absent => false verdict as their neighbours. The instrument that should have run before the push is target/release/ v1_src_dag_parse: it parses all 4483 corpus files in seconds and names the file, line and column. Pushing a corpus-wide mechanical rewrite without it spent a 27-minute CI lane to learn what a local second would have said. Also regenerates DESIGN.md and docs/design-ledgers.md for the rung-drop row declared in the previous commit. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- DESIGN.md | 1 + .../commit_check_demand_witness_test.dag | 22 ++++++++++--------- .../floor/floor_preparation_witness_test.dag | 18 ++++++++------- docs/design-ledgers.md | 4 ++++ 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index af27df7aef4..30389c8116e 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -116,6 +116,7 @@ At a service boundary, rung honesty has a commercial consequence: a dimension ma Every newly discovered error class — incident, review finding, runtime exception, falsifier divergence — files or updates one row: invalid state, harm, distinguishing facts, rung found at, ceiling with reason, next trigger. Declared drops are rostered in full — previous rung, temporary rung, reason, population, restoration trigger — in [docs/design-ledgers.md](docs/design-ledgers.md), authority `gunbc.rung_drop`. A drop is retired BY ITS TRIGGER AND BY NOTHING ELSE, so the trigger is the whole check. The ones standing today: +- **An Optional actual flowing into a parameter declared non-optional, at the call seam, in BOTH the interpreter and the Rust emission arm** — declared 2026-09-01 - **Emitted-bytes fixture witnesses in a required lane** — declared 2026-09-01 - **Direct-call argument TYPE-COMPAT judgment inside v2.* modules (one of two arms; inhabitance still runs)** — declared 2026-09-01 - **CI required-run composition** — declared 2026-08-15 diff --git a/dag/test/claim/commit_check_demand_witness_test.dag b/dag/test/claim/commit_check_demand_witness_test.dag index 725a5c303c7..38299575f9d 100644 --- a/dag/test/claim/commit_check_demand_witness_test.dag +++ b/dag/test/claim/commit_check_demand_witness_test.dag @@ -364,16 +364,18 @@ test fn commit_check_demand_named_multi_function_is_exact_and_permutation_invari } && nth_declaration_name(rows: left, row: 0, declaration: 0) == "beta_holds" && nth_declaration_name(rows: left, row: 0, declaration: 1) == "alpha_holds" - && declaration_ref_lists_eq( - match left.first() { - Present { value: h0 } => left: h0.declarations, - Absent => false - } - match right.first() { - Present { value: h0 } => right: h0.declarations - Absent => false - } - ) + && match left.first() { + Present { value: l0 } => + match right.first() { + Present { value: r0 } => + declaration_ref_lists_eq( + left: l0.declarations, + right: r0.declarations + ) + Absent => false + } + Absent => false + } DemandMissing { check: _, cause: _ } => false DemandAmbiguous { check: _, cause: _ } => false DemandIncomplete { check: _, cause: _ } => false diff --git a/dag/test/claim/floor/floor_preparation_witness_test.dag b/dag/test/claim/floor/floor_preparation_witness_test.dag index 49c8078bcdd..62e37c25e36 100644 --- a/dag/test/claim/floor/floor_preparation_witness_test.dag +++ b/dag/test/claim/floor/floor_preparation_witness_test.dag @@ -177,10 +177,11 @@ test fn missing_prepared_identity_refuses() -> Bool { ) { PreparedFloorSubjectRefused { cause: PreparedFloorMissingProbeIdentities { missing: missing } - match missing.first() { - Present { value: h0 } => } => missing.count() == 1 && h0.entry == witness_b().entry - Absent => false - } + } => missing.count() == 1 + && match missing.first() { + Present { value: h0 } => h0.entry == witness_b().entry + Absent => false + } _ => false } } @@ -254,10 +255,11 @@ test fn unexpected_probe_identity_refuses() -> Bool { ) { PreparedFloorSubjectRefused { cause: PreparedFloorUnexpectedProbeIdentities { unexpected: unexpected } - match unexpected.first() { - Present { value: h0 } => } => unexpected.count() == 1 && h0.entry == witness_extra().entry - Absent => false - } + } => unexpected.count() == 1 + && match unexpected.first() { + Present { value: h0 } => h0.entry == witness_extra().entry + Absent => false + } _ => false } } diff --git a/docs/design-ledgers.md b/docs/design-ledgers.md index f405d49a301..0371993bea7 100644 --- a/docs/design-ledgers.md +++ b/docs/design-ledgers.md @@ -57,6 +57,10 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit Each row declares a safety guarantee that was lowered: what stood before, what stands now, why, over what population, and the trigger that restores it. A drop is retired by its trigger and by nothing else. +### An Optional actual flowing into a parameter declared non-optional, at the call seam, in BOTH the interpreter and the Rust emission arm — declared 2026-09-01 + +**AN OPTIONAL VALUE REACHING A PARAMETER DECLARED NON-OPTIONAL IS UNWRAPPED RATHER THAN REFUSED, AND THIS ROW DECLARES THAT RUNG (2026-09-01).** PREVIOUS RUNG: none stood at this subject grain, and claiming one would be the cross-path inflation DESIGN 4b(1) forbids. The coercion is not new and is not this PR's: the emitted Rust arm carries the literal `fail-closed: an optional value flowed into non-optional parameter` on main in six places across five emitted files, so the SILENT-UNWRAP half of the behaviour predates every change on gunbc#9785. What gunbc#9785 did was give the interpreter arm the SAME seam, so that the two arms answer one question the same way -- and in doing so it made a corpus-wide coercion VISIBLE at one site instead of invisible at two. TEMPORARY RUNG: mitigatable. The `Absent` case REFUSES with a typed, located `call-contract-mismatch` naming the parameter; only the `Present` case is coerced, and it is coerced by unwrapping to the payload, which is the value the declared parameter type names. So no `Absent` reaches a non-optional body, and no fabricated substitute is manufactured for one. What is lost is the DISTINCTION: a caller that meant to pass the Optional itself, and a caller that meant to pass its payload, are accepted identically, and the acceptance happens at RUNTIME from inside the callee rather than at the compile seam. REASON: nothing in the compile seam judges the optionality of a direct-call argument against its declared parameter type. `std.algebra` declares `first`, `last`, `get`, `lookup` and `map_get` with `OptionalOf { inner: ReceiverElement }`, so every one of their call sites produces an Optional; the corpus was authored against the interpreter's pre-repair behaviour, in which those spellings returned the bare element. Refusing at the seam without first migrating the sites would stop the line on the whole corpus at once, which is why the migration and the refusal are separate lanes and this row stands between them. POPULATION, BOUNDED AND STATED AT ITS OWN GRAIN: every direct call whose actual is an Optional and whose declared parameter is not, reachable today only from the five `std.algebra` spellings above; the instrument is the seam itself once it refuses, and the count is deliberately not transcribed here. The FIELD-ACCESS half of that population -- `.first().field`, which is the shape that reads an Optional's payload as a record -- is NOT covered by this row: it is migrated on gunbc#9785 and refuses loudly at typecheck, and a reader must not take this row as cover for it. RESTORATION TRIGGER, named at capability grain: the compile seam REFUSES an Optional flowing into a parameter declared non-optional, at both arms, with a typed and located diagnostic naming the parameter and both types. That capability is SUFFICIENT only under a discriminating pair executed on the real acceptance path: a fixture passing `xs.first()` to a parameter declared as the element type is REFUSED, and the same fixture passing the same call to a parameter declared `Optional` is ACCEPTED. Neither the migration of any number of call sites nor a lens counting them is this trigger -- a site census retires the corpus, never the coercion, and the coercion is what this row declares. + ### Emitted-bytes fixture witnesses in a required lane — declared 2026-09-01 Witness enrollment — **RUNG DROP, DECLARED (2026-09-01, gunbc#9850 witness enrollment).** PREVIOUS RUNG: none to lower — this declares that the mechanically-preventable rung for the emission-follows-resolution class (the 139-row use-line shadow repaired in gunbc#9850) is held by a test OUTSIDE the required aggregate. TEMPORARY RUNG: the discriminating RED and positive controls are enrolled as `emit_import_lines_follow_resolved_binding_identity` in `v1.compiler.compiler_tests_rust`, emitted into `compiler_tests.rs` and executed by `repo_self_test_command` (`cargo test --release -p v1-compiler --lib`) in the `rust-unit-tests` job of `gunbc.witness_floor_workflow` — which runs on every push and pull request but is NOT a `needs` of the required aggregate, so a regression reddens a visible job without blocking the merge. REASON: no substrate-visible surface exposes EMITTED BYTES to a `dag/test/claim` witness (the host census builtin carries diagnostics only), so the assertion is inexpressible in the required floor today; promoting `rust-unit-tests` to required was measured 2026-09-01 at 23/25 recent completions green with both failures runner-environment faults (shared-runner `$HOME`/cargo-shim class) — an ~8% infra-noise rate ruled too high to make 612 tests merge-blocking fleet-wide tonight. POPULATION: the emission-follows-resolution witness matrix (String RED, structural-Bool and non-kernel controls) plus every other `compiler_tests` row asserting on emitted bytes. RESTORATION TRIGGER, named at capability grain: a required-lane capability SUFFICIENT to execute, on the real acceptance path, a fixture assertion that a named `pub use` line is ABSENT from emitted bytes for a sole-exporter structural type resolved to a host-realized kernel scalar, and PRESENT for a structural kernel carrying a connective (Bool = True | False) — via either (i) `rust-unit-tests` promoted into the required aggregate after its runner-fault class is retired or priced, or (ii) a substrate-visible emitted-bytes probe surface admitted under the scaffold doctrine. A surface that can read emitted bytes but cannot discriminate those two cases does NOT satisfy this trigger. From 1f9a836abb17b24b73514be919f0dadcbc2c8d5b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 18:45:48 +0000 Subject: [PATCH 25/28] Recurring failure mode: an entry-scoped green over a corpus-wide edit Files the check never opened. An --entry run is evidence about the files its import closure OPENS and about nothing else, so a corpus-wide mechanical rewrite breaks, by construction, exactly what no single closure reaches -- and the green is honest about its own subject while reading as coverage of the population. Specimen is this PR: one clean claim_batch resolve, then a CI parse refusal in files that run never opened. The row's second half is the enumeration property, because it decides which instrument to reach for: a phase that aborts on first refusal cannot enumerate, so its failure list is a prefix and a green after fixing that list is not a green. CI named two files; the corpus walk named a third it had never reached. The row records that v1_src_dag_parse's own source already advertises itself as the cheapest check in the tree for exactly this, which makes the specimen an unread instrument rather than a missing one. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- dag/gunbc/recurring_failure_mode.dag | 3 +++ docs/design-ledgers.md | 1 + 2 files changed, 4 insertions(+) diff --git a/dag/gunbc/recurring_failure_mode.dag b/dag/gunbc/recurring_failure_mode.dag index 5cdd4e4e6d7..c684967c8ea 100644 --- a/dag/gunbc/recurring_failure_mode.dag +++ b/dag/gunbc/recurring_failure_mode.dag @@ -182,6 +182,8 @@ data coarser_parallel_authority: RecurringFailureMode = RecurringFailureMode { i data mutation_applied_to_the_wrong_artifact_of_a_generated_pair: RecurringFailureMode = RecurringFailureMode { identity: "mutation_applied_to_the_wrong_artifact_of_a_generated_pair" as NonEmptyStr, authored: "**a mutation control applied to the wrong artifact of a generated pair** (a subject exists twice -- once as authored source and once as its generated projection -- and the mutation is made in the copy the harness does not read, so the control stays GREEN and reads as evidence that it discriminates nothing). This is a SELF-INVERTING instrument and therefore worse than an inert one: a failed mutation is normally strong evidence that a check is vacuous, so the false negative argues for DELETING or rewriting a check that was sound. RECOGNITION RULE, mechanical and to be applied BEFORE choosing a target: name which artifact the HARNESS reads. A generated pair offers two plausible mutation targets and only one is the subject. RECEIPT, with both directions, from gunbc#9785. Mutating src/v1/stage0/src/std_algebra.rs -- the emitted mirror -- left both planted optionality controls green; claim_batch INTERPRETS a witness, so it reads dag/std/algebra.dag and the mirror was never the subject. Re-made in the .dag, the same two mutations discriminate in opposite directions: ReceiverElement to OptionalityForbidden reds the receiver-relative row and leaves the pair green, NamedTemplate to OptionalityRequired reds the pair and leaves the receiver-relative row green. The INVERSE case is in the same change and is what makes the rule a question rather than a preference: the join control mutates v1_interpreter.rs, because ITS subject is the interpreter arm and there the Rust is what executes. Same repository, same session, opposite correct targets. The general form covers any authored/generated pair -- .dag against its stage0 mirror, a carrier against its projected markdown, a schema against its emitted client -- and the question is never which artifact is authoritative, only which one the runner opened.", evidence: [] } +data entry_scoped_green_over_a_corpus_wide_edit: RecurringFailureMode = RecurringFailureMode { identity: "entry_scoped_green_over_a_corpus_wide_edit" as NonEmptyStr, authored: "**an entry-scoped green over a corpus-wide edit** (a mechanical rewrite applied across the corpus is verified by running ONE entry, and the run is green -- because the entry's import closure is not the edit's blast radius. An `--entry` run is evidence about the files that closure OPENS and about nothing else, and a corpus-wide edit breaks, by construction, the files no single closure reaches. The green is honest about its own subject and silent about the population, and silence reads as coverage. Specimen: gunbc#9785 (2026-09-01), a mechanical `.first()` field-access migration across 33 files. One `claim_batch --entry` run resolved clean, and CI then refused at the parse phase in files that run never opened. THE DISTINCTION FROM ITS NEIGHBOURS: this is not about what a check MEANS on the files it examined -- it is about WHICH FILES IT EVER OPENED. A closure-scoped instrument cannot be made to cover a corpus-scoped change by making it stricter. **RECOGNITION RULE: when the edit's selector is the corpus and the check's selector is a closure, the check has a denominator the change does not respect -- ask which files the instrument opened, not what it concluded.** THE SECOND HALF IS THE ENUMERATION PROPERTY, and it decides which instrument to reach for: a PHASE that aborts on the first refusal cannot enumerate, so its list of failures is a prefix and a green-after-fixing-that-list is not a green. On this specimen CI's parse phase named two files and stopped; the corpus walk -- the `v1_src_dag_parse` binary, a thin caller over `v1_compiler.cli_run` `run_v1_src_dag_parse` whose roots roster is `DAG_PARSE_SWEEP_ROOTS`, carried by `gunbc.declaration_index_seed_growth` -- parses every corpus file, continues past a refusal, and immediately named a third the phase had never reached. THAT INSTRUMENT ALREADY DESCRIBES ITSELF THIS WAY in its own source: it says it is kept because the sweep alone is a real local action, THE CHEAPEST CHECK IN THE TREE AND THE ONE WORTH REACHING FOR WHILE EDITING ANY `.dag`. So the specimen is not a missing instrument; it is an author who did not read the one that was already declared and already advertised for exactly this. THE REPAIR IS AN INSTRUMENT WHOSE SELECTOR MATCHES THE EDIT'S, not a stricter entry run: for a change whose selector is the corpus, run the corpus walk before the push.)", evidence: [] } + data recurring_failure_mode_roster: List = [ hollow_alias, state_space_conflation, @@ -224,4 +226,5 @@ data recurring_failure_mode_roster: List = [ coarser_parallel_authority, mutation_applied_to_the_wrong_artifact_of_a_generated_pair, + entry_scoped_green_over_a_corpus_wide_edit, ] diff --git a/docs/design-ledgers.md b/docs/design-ledgers.md index 0371993bea7..7cb7c5bb1f0 100644 --- a/docs/design-ledgers.md +++ b/docs/design-ledgers.md @@ -52,6 +52,7 @@ The landing measurement partitions the 31 parser-visible identities into **2 cit - **a review summary that inverts real roles and then affirms the join** (a review names files that all exist, assigns each the wrong role, and closes by asserting the one property it was uniquely positioned to test. Specimen: review 58215 approved gunbc#9937 at `fafd2adc7e1` with *'restoration of two ledger rows to recurring_failure_mode.dag ... plus an accompanying prose update in the gap-analysis doc ... Diff is narrow and matches the PR title.'* The carrier held ONE added row, not two; that row was a NEW class row, not a restoration of anything; the actual restoration was the docs file it demoted to 'accompanying'; and the diff did NOT match the title, because the `.dag` file had been swept in by a staged-change leak the PR body never declared. **DISTINGUISH IT FROM A HALLUCINATED SUBJECT, which is the neighbouring failure and a much easier one:** review 58070 described a `match` arm that appeared nowhere in its diff, and an invention is refutable by grep. Here EVERY NOUN IS PRESENT IN THE TREE and only the relations between them are wrong, so the usual defence -- check that the cited things exist -- returns all-clear. **THE AFFIRMATION IS THE HARM AND NOT THE MISDESCRIPTION.** A lane cannot audit its own title-versus-diff agreement with fresh eyes; a reviewer can, and this one asserted that agreement in the exact PR where it failed. A review that stays silent on a property leaves the check undone; a review that affirms it wrongly marks the check DONE, which is worse and is why this is not merely a low-quality summary. RECOGNITION RULE, and it is decidable rather than a matter of reading care: join the changed-FILE set against what the title and body claim to change, at file grain, and refuse any file the body does not account for. That join is mechanical, needs no understanding of the diff, and would have fired here. **THE BACKSTOP QUESTION IS THE SECOND HALF OF THE ROW, and its answer is honest rather than comforting:** the leaked roster row carried no regenerated `DESIGN.md` or `docs/design-ledgers.md`, and by construction the required build lane's generated-artifact phase compares every `CommitRequired` projection against its authority, so it WOULD have refused. It did not get the chance -- that lane was CANCELLED on the leaked sha when the next push superseded it, and the leak was caught by the author re-reading the diff. So the backstop is real and was not what caught it, and a row claiming the gate held here would be asserting an execution that never ran. **Rung: mitigatable** -- the harm is contained only by a second reader noticing, and on the specimen the second reader was the author. **Ceiling: mechanically preventable**, and no higher: whether a summary DESCRIBES a diff correctly is not decidable, so no construction forbids the bad summary -- but the one clause that did the damage is decidable, because a changed-file set and a body are both machine-readable. **Next-rung trigger, at capability grain: no review verdict is admissible unless a changed-FILE-set join against the declared scope has executed and passed** -- not a reviewer instructed to check it, which is the same assertion that failed here, and not a lint on summary wording, which would ratchet the prose while leaving the join undone.) - **coarser parallel authority** (two carriers are AUTHORED for one fact and one of them is LOSSIER, so the fork never presents as duplication — it presents as abstraction. §3 already forbids two authorities for one fact; what this row adds is the reason that rule keeps being read past, because the second carrier does not look like a copy. A coarse answer and a fine answer never contradict each other in the way two copies do: the coarse one reads as *less specific*, which a reviewer accepts as a summary, so the disagreement is invisible until an input distinguishes the collapsed cases. RECEIPT (measured on main while censusing the `first` interpreter/emitted divergence, gunbc#9785): `v1.compiler.infer_method` `builtin_function_registry` maps a primitive name to a RETURN TYPE, and `std.algebra` `AlgebraFieldTemplate` carries `param_types` and `return_type` for the same operations. 20 of the registry's names also have algebra rows, and the two already answer differently, because the registry is RECEIVER-BLIND where the algebra rows are receiver-relative: `reverse` is `List` against `ReceiverSelf`, so on a `String` receiver the two carriers name different types; `map_keys` and `map_values` share ONE element type variable where algebra distinguishes `ReceiverKey` from `ReceiverValue`, so for any `Map` with `K` /= `V` the registry cannot be right about both; `concat` is `String` against `ReceiverSelf`, so a list concat types as a String; `get` collapses the List reading and the Map reading the algebra rows keep apart. None of these is a stale copy that drifted — the registry was authored coarse and has been coarse the whole time. **WHAT MAKES THE COARSE CARRIER LOOK LEGITIMATE is that its coarseness is usually true of the QUESTION ITS FIRST CONSUMER ASKED.** A caller that only wants "is this name a builtin" is well served by a name-to-return-type map, and the carrier is correct for that consumer on the day it lands. It becomes an authority for a fact it never modelled the moment a second consumer asks a finer question of it, and it answers, because a map is total over its keys. **RECOGNITION RULE: when two carriers answer about one operation and one is coarser, ask whether the coarse answer is DERIVED from the fine one or AUTHORED beside it. A derived projection cannot disagree; an authored one is a fork whose disagreements are silent by construction, since "less specific" and "different" are indistinguishable at the call site.** The repair is never to reconcile the two rosters — that is a second synchronisation obligation, and §5 calls a check standing where construction was available validation. The repair is RELOCATION: the coarse carrier stops being an authority and becomes a projection of the fine one, or its rows leave for the layer that actually owns them. Here the same relocation dissolves a second class: once the registry is no longer a signature authority for anything `std.algebra` owns, there is no second declaration left to disagree, and the population that remains — primitives with no algebra row — is a closeable gap rather than an open denominator.) - **a mutation control applied to the wrong artifact of a generated pair** (a subject exists twice -- once as authored source and once as its generated projection -- and the mutation is made in the copy the harness does not read, so the control stays GREEN and reads as evidence that it discriminates nothing). This is a SELF-INVERTING instrument and therefore worse than an inert one: a failed mutation is normally strong evidence that a check is vacuous, so the false negative argues for DELETING or rewriting a check that was sound. RECOGNITION RULE, mechanical and to be applied BEFORE choosing a target: name which artifact the HARNESS reads. A generated pair offers two plausible mutation targets and only one is the subject. RECEIPT, with both directions, from gunbc#9785. Mutating src/v1/stage0/src/std_algebra.rs -- the emitted mirror -- left both planted optionality controls green; claim_batch INTERPRETS a witness, so it reads dag/std/algebra.dag and the mirror was never the subject. Re-made in the .dag, the same two mutations discriminate in opposite directions: ReceiverElement to OptionalityForbidden reds the receiver-relative row and leaves the pair green, NamedTemplate to OptionalityRequired reds the pair and leaves the receiver-relative row green. The INVERSE case is in the same change and is what makes the rule a question rather than a preference: the join control mutates v1_interpreter.rs, because ITS subject is the interpreter arm and there the Rust is what executes. Same repository, same session, opposite correct targets. The general form covers any authored/generated pair -- .dag against its stage0 mirror, a carrier against its projected markdown, a schema against its emitted client -- and the question is never which artifact is authoritative, only which one the runner opened. +- **an entry-scoped green over a corpus-wide edit** (a mechanical rewrite applied across the corpus is verified by running ONE entry, and the run is green -- because the entry's import closure is not the edit's blast radius. An `--entry` run is evidence about the files that closure OPENS and about nothing else, and a corpus-wide edit breaks, by construction, the files no single closure reaches. The green is honest about its own subject and silent about the population, and silence reads as coverage. Specimen: gunbc#9785 (2026-09-01), a mechanical `.first()` field-access migration across 33 files. One `claim_batch --entry` run resolved clean, and CI then refused at the parse phase in files that run never opened. THE DISTINCTION FROM ITS NEIGHBOURS: this is not about what a check MEANS on the files it examined -- it is about WHICH FILES IT EVER OPENED. A closure-scoped instrument cannot be made to cover a corpus-scoped change by making it stricter. **RECOGNITION RULE: when the edit's selector is the corpus and the check's selector is a closure, the check has a denominator the change does not respect -- ask which files the instrument opened, not what it concluded.** THE SECOND HALF IS THE ENUMERATION PROPERTY, and it decides which instrument to reach for: a PHASE that aborts on the first refusal cannot enumerate, so its list of failures is a prefix and a green-after-fixing-that-list is not a green. On this specimen CI's parse phase named two files and stopped; the corpus walk -- the `v1_src_dag_parse` binary, a thin caller over `v1_compiler.cli_run` `run_v1_src_dag_parse` whose roots roster is `DAG_PARSE_SWEEP_ROOTS`, carried by `gunbc.declaration_index_seed_growth` -- parses every corpus file, continues past a refusal, and immediately named a third the phase had never reached. THAT INSTRUMENT ALREADY DESCRIBES ITSELF THIS WAY in its own source: it says it is kept because the sweep alone is a real local action, THE CHEAPEST CHECK IN THE TREE AND THE ONE WORTH REACHING FOR WHILE EDITING ANY `.dag`. So the specimen is not a missing instrument; it is an author who did not read the one that was already declared and already advertised for exactly this. THE REPAIR IS AN INSTRUMENT WHOSE SELECTOR MATCHES THE EDIT'S, not a stricter entry run: for a change whose selector is the corpus, run the corpus walk before the push.) ## Declared rung drops (§4b(3)) From 806607ae509af4b0da570ab29a6f5d4f371f56f3 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 19:44:26 +0000 Subject: [PATCH 26/28] Three more first() populations the field-access census could not see The `.first().` grep bounded one shape. Two more were invisible to it and both were silent, not loud: MATCHING AN OPTIONAL AGAINST ELEMENT VARIANTS. `match steps.first() { Actuator.. => .. _ => false }` typechecks and answers false forever. Probed directly: the checker validates arm constructors against the ELEMENT type while checking exhaustiveness against the Optional -- two answers for one scrutinee in one match -- so where the element type is a coproduct the arms resolve, the wildcard satisfies exhaustiveness, and nothing refuses. 17 sites, one of them production (`extdeps.bmc.pid_control_decode` `decode_curve_side`). LET-THEN-MEMBER. `let entry = matched.first()` on one line and `entry.magnitude` on the next reads the OPTIONAL's payload, not the entry's field -- the exact confusion this module's own annotation records. 11 sites across 8 files, including three folds in `std/effect_axes.dag`. `indexed_decimal_value_at` now declares `Optional`, which made its one caller partial: a `map` has no arm for a missing output, so it could only fabricate a magnitude or let an Optional reach a declared non-optional field. The point set is built by a fold that refuses instead, with the Absent arm DERIVED -- an indexed reading whose output the join cannot supply is exactly the `CurveReadingWithoutOutput` the caller already raises. It accumulates by prepend so the fold stays linear (DESIGN 6 bare-minimum-cost), and the existing sort_by on the point index restores order. Every Absent arm here is derived from the guard it replaces: the `if length == 0` sibling is dissolved into the match rather than answered twice. Verified by execution: 4483 files parse-clean, and the curve, machine-shape, NBD, websocat, runner, effect-axes, WIF, cpu-cache, roadmap-contract and browser-observation witnesses PASS. `enforcement_consistency_gate_holds` fails both with and without the schedule_lens edit on this tree, so it is not this change -- measured by swapping that one file, not inferred. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- dag/extdeps/bmc/pid_control_decode.dag | 87 ++++++++++++++----- dag/extdeps/transports/rest.dag | 7 +- .../accelerator_demo_plan.dag | 5 +- dag/gunbc/bmc/bmc_fan_converge.dag | 7 +- dag/gunbc/bmc/bmc_fan_monitor.dag | 6 +- .../roadmap/roadmap_dispatch_actuator.dag | 11 ++- .../srv3/srv3_os_install_actuate_workflow.dag | 18 ++-- dag/std/effect_axes.dag | 57 ++++++------ ...and_mt_mitchell_authority_witness_test.dag | 13 +-- .../bmc_wif_delegation_chain_witness_test.dag | 70 ++++++++++----- .../cpu_l2_l3_cache_geometry_witness_test.dag | 10 +-- ...rst_optional_construction_witness_test.dag | 8 +- ...shape_catalog_integration_witness_test.dag | 60 ++++++------- dag/test/claim/machine_shape_witness_test.dag | 55 ++++++------ ...oxy_virtual_media_install_witness_test.dag | 34 ++++++-- ...oadmap_execution_contract_witness_test.dag | 3 +- ..._connectivity_repair_plan_witness_test.dag | 40 ++++++--- ...rface_browser_observation_witness_test.dag | 9 +- .../websocat_install_source_witness_test.dag | 14 +-- src/v2/lens/schedule_lens.dag | 10 ++- 20 files changed, 327 insertions(+), 197 deletions(-) diff --git a/dag/extdeps/bmc/pid_control_decode.dag b/dag/extdeps/bmc/pid_control_decode.dag index 9bba86df80c..391afdc62c7 100644 --- a/dag/extdeps/bmc/pid_control_decode.dag +++ b/dag/extdeps/bmc/pid_control_decode.dag @@ -317,14 +317,18 @@ fn decode_curve_side(container: JsonValue, path: DocumentPath, side: CurveSide) } } else { match hits.first() { - JsonObject { members } => - curve_side_fold( - path: path_field(parent: path, field: field), - side: side, - members: members, - acc: CurveSideDecoded { entries: [] }, - ) - other => CurveSideRefused { refusal: CurveSideNotAnObject { path: path, side: side } } + Present { value: hit } => + match hit { + JsonObject { members } => + curve_side_fold( + path: path_field(parent: path, field: field), + side: side, + members: members, + acc: CurveSideDecoded { entries: [] }, + ) + other => CurveSideRefused { refusal: CurveSideNotAnObject { path: path, side: side } } + } + Absent => CurveSideRefused { refusal: CurveSideMissing { path: path, side: side } } } } } @@ -351,10 +355,54 @@ fn decode_curve_side(container: JsonValue, path: DocumentPath, side: CurveSide) // split between the language-semantics question and the type-conformance half that is below floor // regardless of how the first is answered. What is claimed HERE is only the local fact: the rename // to `magnitude` is a workaround, and no compiler guarantee is claimed by this module. -fn indexed_decimal_value_at(entries: List, index: Int) -> ExactDecimal { - let matched = indexed_decimal_entries_with(entries: entries, index: index) - let entry = matched.first() - entry.magnitude +// THE LOOKUP ANSWERS AN OPTIONAL BECAUSE THE LIST MIGHT NOT HOLD THE INDEX, and saying so is what +// keeps its one caller total. It previously read `matched.first()` into a binding and took +// `.magnitude` off it, which read the OPTIONAL's own payload rather than the entry's field -- the +// same confusion the note above this module's decode records, in the module that records it. +fn indexed_decimal_value_at(entries: List, index: Int) -> Optional { + match indexed_decimal_entries_with(entries: entries, index: index).first() { + Present { value: entry } => Present { value: entry.magnitude } + Absent => Absent + } +} + +// THE POINT SET IS BUILT BY A FOLD THAT CAN REFUSE, not by a map that cannot. A map has no arm for +// a missing output, so the only ways to write it are to fabricate a magnitude or to let an Optional +// reach a declared non-optional field -- the first is forbidden outright and the second is the +// coercion this PR declares as debt. The refusal is DERIVED rather than invented: an indexed +// reading whose output the join cannot supply is exactly `CurveReadingWithoutOutput`, which the +// caller already raises for the same condition measured a different way. Accumulating by PREPEND +// keeps the fold linear, and the caller's `sort_by` over the point index restores the order. +fn curve_points_fold( + path: DocumentPath, + readings: List, + outputs: List, +) -> CurveDecode { + fold( + readings, + init: CurveDecoded { points: [] }, + f: fn(acc, r) { + match acc { + CurveRefused { refusal } => CurveRefused { refusal: refusal } + CurveDecoded { points } => + match indexed_decimal_value_at(entries: outputs, index: r.index) { + Present { value: magnitude } => + CurveDecoded { + points: concat( + [DemandCurvePoint { + index: r.index, + reading: decimal_celsius(value: r.magnitude), + output: zone_demand_value(value: magnitude), + }], + points, + ), + } + Absent => + CurveRefused { refusal: CurveReadingWithoutOutput { path: path, index: r.index } } + } + } + }, + ) } type CurveDecode @@ -383,17 +431,10 @@ fn curve_join(path: DocumentPath, readings: List, outputs: List< Present { value: stray_output } => CurveRefused { refusal: CurveOutputWithoutReading { path: path, index: stray_output.index } } Absent => - CurveDecoded { - points: sort_by( - map(readings, r => - DemandCurvePoint { - index: r.index, - reading: decimal_celsius(value: r.magnitude), - output: zone_demand_value(value: indexed_decimal_value_at(entries: outputs, index: r.index)), - } - ), - point => point.index, - ), + match curve_points_fold(path: path, readings: readings, outputs: outputs) { + CurveRefused { refusal } => CurveRefused { refusal: refusal } + CurveDecoded { points } => + CurveDecoded { points: sort_by(points, point => point.index) } } } } diff --git a/dag/extdeps/transports/rest.dag b/dag/extdeps/transports/rest.dag index 80365319a5c..baae765bc87 100644 --- a/dag/extdeps/transports/rest.dag +++ b/dag/extdeps/transports/rest.dag @@ -237,10 +237,9 @@ fn rest_fixture_scan( invocation: RestBoundOperationInvocation, scan: RestFixtureScan, ) -> RestFixtureScan { - if fixtures.count() == 0 { - scan - } else { - let fixture = fixtures.first() + match fixtures.first() { + Absent => scan + Present { value: fixture } => let matches = fixture.store == store && rest_bound_invocation_eq(a: fixture.invocation, b: invocation) let next = if matches { diff --git a/dag/gunbc/accelerator_demo/accelerator_demo_plan.dag b/dag/gunbc/accelerator_demo/accelerator_demo_plan.dag index 2f6d0758417..42daba8fb14 100644 --- a/dag/gunbc/accelerator_demo/accelerator_demo_plan.dag +++ b/dag/gunbc/accelerator_demo/accelerator_demo_plan.dag @@ -133,7 +133,9 @@ fn recognize_float_fma_fused_kernel_when_contraction_refused() -> AcceleratorRec } fn demo_plan_fused_op_count(plan: RealizationPlan) -> Int { - let batch = plan.schedule.first() + match plan.schedule.first() { + Absent => 0 + Present { value: batch } => match batch.first() { Absent => 0 Present { value: runnable } => match runnable { @@ -142,6 +144,7 @@ fn demo_plan_fused_op_count(plan: RealizationPlan) -> Int { RunnableDiscoveryBatch { source_roots: _, scan_dirs: _, explicit_entries: _, exclude_substrings: _, discovery_scope_dirs: _, profile: _ } => 0 } } + } } fn demo_plan_has_three_fused_ops(plan: RealizationPlan) -> Bool { diff --git a/dag/gunbc/bmc/bmc_fan_converge.dag b/dag/gunbc/bmc/bmc_fan_converge.dag index 7e67be23091..4f1d30e6d43 100644 --- a/dag/gunbc/bmc/bmc_fan_converge.dag +++ b/dag/gunbc/bmc/bmc_fan_converge.dag @@ -531,10 +531,9 @@ type BmcFanTypedMutationResult fn bmc_fan_observation_value(lines: List, key: String) -> String? { let prefix = concat(key, "=") - if lines.length() == 0 { - none - } else { - let line = lines.first() + match lines.first() { + Absent => none + Present { value: line } => if starts_with(s: line, prefix: prefix) { Present { value: substring( diff --git a/dag/gunbc/bmc/bmc_fan_monitor.dag b/dag/gunbc/bmc/bmc_fan_monitor.dag index 9eb9a454f15..81b2ff923cf 100644 --- a/dag/gunbc/bmc/bmc_fan_monitor.dag +++ b/dag/gunbc/bmc/bmc_fan_monitor.dag @@ -29,7 +29,8 @@ fn bmc_fan_assess_monitoring_samples_rec( required_elapsed: Second, maximum_sample_gap: Second, ) -> BmcFanMonitoringResult { - if remaining.length() == 0 { + match remaining.first() { + Absent => let receipt = BmcFanMonitoringReceipt { sample_count: sample_count, elapsed: previous_elapsed, @@ -51,8 +52,7 @@ fn bmc_fan_assess_monitoring_samples_rec( receipt: receipt, } } - } else { - let sample = remaining.first() + Present { value: sample } => let sample_gap_seconds = second_count(sample.elapsed) - second_count(previous_elapsed) let receipt = BmcFanMonitoringReceipt { sample_count: sample_count + 1, diff --git a/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag b/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag index ee5b2ac4782..470083849f6 100644 --- a/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag +++ b/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag @@ -1880,10 +1880,13 @@ fn tmux_session_observation_for_scope( if count(matched) == 0 { SessionAbsent } else { - let e = matched.first() - SessionNameObserved { - session_name: scope.session_name, - windows: e.windows, + match matched.first() { + Present { value: e } => + SessionNameObserved { + session_name: scope.session_name, + windows: e.windows, + } + Absent => SessionAbsent } } } diff --git a/dag/gunbc/srv3/srv3_os_install_actuate_workflow.dag b/dag/gunbc/srv3/srv3_os_install_actuate_workflow.dag index 7741b93be94..f30f82aa3f4 100644 --- a/dag/gunbc/srv3/srv3_os_install_actuate_workflow.dag +++ b/dag/gunbc/srv3/srv3_os_install_actuate_workflow.dag @@ -78,13 +78,21 @@ fn srv3_os_install_actuate_workflow_includes_operator_approval_before_boot() -> let steps = srv3_os_install_actuate_workflow_steps() srv3_os_install_actuate_workflow_step_count() == 8 && match steps.skip(n: 5).first() { - WorkflowOperatorApproval { escalation: e } => - e.escalation_id == srv3_boot_once_cd_escalation.escalation_id - _ => false + Present { value: only } => + match only { + WorkflowOperatorApproval { escalation: e } => + e.escalation_id == srv3_boot_once_cd_escalation.escalation_id + _ => false + } + Absent => false } && match steps.skip(n: 6).first() { - WorkflowBootOnceCd => true - _ => false + Present { value: only } => + match only { + WorkflowBootOnceCd => true + _ => false + } + Absent => false } } diff --git a/dag/std/effect_axes.dag b/dag/std/effect_axes.dag index da1e658b656..48256de9892 100644 --- a/dag/std/effect_axes.dag +++ b/dag/std/effect_axes.dag @@ -86,44 +86,41 @@ fn read_grant_intersects_live_tree(g: Grant) -> Bool { } fn grants_all_read_replayable(grants: List) -> Bool { - if grants.count() == 0 { - true - } else { - let g = grants.first() - let rest = grants.skip(n: 1) - if verb_eq(a: g.verb, b: Read) { - read_grant_replayable(g: g) && grants_all_read_replayable(grants: rest) - } else { - grants_all_read_replayable(grants: rest) - } + match grants.first() { + Absent => true + Present { value: g } => + let rest = grants.skip(n: 1) + if verb_eq(a: g.verb, b: Read) { + read_grant_replayable(g: g) && grants_all_read_replayable(grants: rest) + } else { + grants_all_read_replayable(grants: rest) + } } } fn grants_all_write_isolated(grants: List) -> Bool { - if grants.count() == 0 { - true - } else { - let g = grants.first() - let rest = grants.skip(n: 1) - if verb_eq(a: g.verb, b: Write) { - write_grant_isolated(g: g) && grants_all_write_isolated(grants: rest) - } else { - grants_all_write_isolated(grants: rest) - } + match grants.first() { + Absent => true + Present { value: g } => + let rest = grants.skip(n: 1) + if verb_eq(a: g.verb, b: Write) { + write_grant_isolated(g: g) && grants_all_write_isolated(grants: rest) + } else { + grants_all_write_isolated(grants: rest) + } } } fn grants_any_read_intersects_live_tree(grants: List) -> Bool { - if grants.count() == 0 { - false - } else { - let g = grants.first() - let rest = grants.skip(n: 1) - if verb_eq(a: g.verb, b: Read) { - read_grant_intersects_live_tree(g: g) || grants_any_read_intersects_live_tree(grants: rest) - } else { - grants_any_read_intersects_live_tree(grants: rest) - } + match grants.first() { + Absent => false + Present { value: g } => + let rest = grants.skip(n: 1) + if verb_eq(a: g.verb, b: Read) { + read_grant_intersects_live_tree(g: g) || grants_any_read_intersects_live_tree(grants: rest) + } else { + grants_any_read_intersects_live_tree(grants: rest) + } } } diff --git a/dag/test/claim/altra_platform_and_mt_mitchell_authority_witness_test.dag b/dag/test/claim/altra_platform_and_mt_mitchell_authority_witness_test.dag index 10397d17ed4..d95cef0b7e9 100644 --- a/dag/test/claim/altra_platform_and_mt_mitchell_authority_witness_test.dag +++ b/dag/test/claim/altra_platform_and_mt_mitchell_authority_witness_test.dag @@ -258,12 +258,15 @@ test fn w_a_public_layout_document_would_read_as_reachable() -> Bool { // --- OCP Mt. Mitchell motherboard specification, revision 0.80 --- test fn w_mt_mitchell_revision_table_ends_at_the_modelled_revision() -> Bool { - let latest = mt_mitchell_revision_table.last() list_length(items: mt_mitchell_revision_table) == 3 - && latest.revision == "0.80" - && latest.publication_date.year == 2022 - && latest.publication_date.month == 9 - && latest.publication_date.day == 9 + && match mt_mitchell_revision_table.last() { + Present { value: latest } => + latest.revision == "0.80" + && latest.publication_date.year == 2022 + && latest.publication_date.month == 9 + && latest.publication_date.day == 9 + Absent => false + } && match mt_mitchell_revision_table.first() { Present { value: h0 } => h0.revision == "0.50" Absent => false diff --git a/dag/test/claim/bmc/bmc_wif_delegation_chain_witness_test.dag b/dag/test/claim/bmc/bmc_wif_delegation_chain_witness_test.dag index 7062caa5feb..586b2924199 100644 --- a/dag/test/claim/bmc/bmc_wif_delegation_chain_witness_test.dag +++ b/dag/test/claim/bmc/bmc_wif_delegation_chain_witness_test.dag @@ -23,28 +23,40 @@ test fn chain_has_four_steps_in_delegation_order() -> Bool { test fn exchange_step_pins_repo_attribute_condition() -> Bool { match bmc_wif_canary_delegation_chain.steps.skip(n: 1).first() { - WifTokenExchange { pool: _, provider: _, attribute_condition: cond } => - string_contains(s: cond as String, pattern: "assertion.repository == 'gunb-ai/gunbc'") - && cond as String == bmc_wif_attribute_condition() - _ => false + Present { value: step } => + match step { + WifTokenExchange { pool: _, provider: _, attribute_condition: cond } => + string_contains(s: cond as String, pattern: "assertion.repository == 'gunb-ai/gunbc'") + && cond as String == bmc_wif_attribute_condition() + _ => false + } + Absent => false } } test fn impersonation_step_targets_the_scoped_assimilator_sa() -> Bool { match bmc_wif_canary_delegation_chain.steps.skip(n: 2).first() { - ServiceAccountImpersonation { impersonated_sa: sa, granted_roles: roles } => - sa == bmc_assimilator_sa_email - && fold(roles, init: true, f: (acc, r) => acc && bmc_role_allowed(role: r)) - _ => false + Present { value: step } => + match step { + ServiceAccountImpersonation { impersonated_sa: sa, granted_roles: roles } => + sa == bmc_assimilator_sa_email + && fold(roles, init: true, f: (acc, r) => acc && bmc_role_allowed(role: r)) + _ => false + } + Absent => false } } test fn secret_access_step_targets_canary_not_bmc_secret() -> Bool { match bmc_wif_canary_delegation_chain.steps.skip(n: 3).first() { - SecretVersionAccess { secret_ref: ref } => - secret_ref_secret_resource(ref: ref) == secret_ref_secret_resource(ref: wif_canary_secret_ref) - && secret_ref_secret_resource(ref: ref) != secret_ref_secret_resource(ref: bmc_srv3_admin_secret_ref) - _ => false + Present { value: step } => + match step { + SecretVersionAccess { secret_ref: ref } => + secret_ref_secret_resource(ref: ref) == secret_ref_secret_resource(ref: wif_canary_secret_ref) + && secret_ref_secret_resource(ref: ref) != secret_ref_secret_resource(ref: bmc_srv3_admin_secret_ref) + _ => false + } + Absent => false } } @@ -54,7 +66,7 @@ test fn bootstrap_grants_zero_bindings_for_bmc_secret() -> Bool { && bmc_wif_signed_grants().length() == 1 && bmc_wif_bootstrap_bindings().length() == 1 && match bmc_wif_bootstrap_bindings().first() { - Present { value: h0 } => h0.members.length() == 1 + Present { value: binding } => binding.members.length() == 1 Absent => false } } @@ -80,16 +92,30 @@ test fn secret_refs_are_free_of_credential_shaped_strings() -> Bool { test fn srv3_credential_path_is_an_enumerated_option_space_not_a_binding() -> Bool { srv3_credential_path_options.length() == 4 - && match srv3_credential_path_options.first() { OperatorOneHourToken => true, _ => false } + && match srv3_credential_path_options.first() { + Present { value: option } => match option { OperatorOneHourToken => true, _ => false } + Absent => false + } && match srv3_credential_path_options.skip(n: 1).first() { - ActorPinnedWorkflowDispatch { allowed_actor: a } => a as String == "briansrls" - _ => false + Present { value: option } => + match option { + ActorPinnedWorkflowDispatch { allowed_actor: a } => a as String == "briansrls" + _ => false + } + Absent => false + } + && match srv3_credential_path_options.skip(n: 2).first() { + Present { value: option } => match option { DedicatedActuatorIdentity { note: _ } => true, _ => false } + Absent => false } - && match srv3_credential_path_options.skip(n: 2).first() { DedicatedActuatorIdentity { note: _ } => true, _ => false } && match srv3_credential_path_options.skip(n: 3).first() { - ExistingAssimilatorSaBinding { legacy_grant: g } => - secret_ref_secret_resource(ref: g.secret_ref) == secret_ref_secret_resource(ref: bmc_srv3_admin_legacy_binding.secret_ref) - _ => false + Present { value: option } => + match option { + ExistingAssimilatorSaBinding { legacy_grant: g } => + secret_ref_secret_resource(ref: g.secret_ref) == secret_ref_secret_resource(ref: bmc_srv3_admin_legacy_binding.secret_ref) + _ => false + } + Absent => false } } @@ -115,8 +141,8 @@ test fn secret_ref_login_handler_added_beside_factory_login_not_over_it() -> Boo test fn bmc_smoke_workflow_scopes_id_token_write_to_its_single_designated_job() -> Bool { bmc_token_smoke_workflow.jobs.length() == 1 - && match jobs.first() { - Present { value: h0 } => bmc_token_smoke_workflow.h0.id == bmc_token_smoke_job().id + && match bmc_token_smoke_workflow.jobs.first() { + Present { value: job } => job.id == bmc_token_smoke_job().id Absent => false } && match bmc_token_smoke_workflow.permissions { diff --git a/dag/test/claim/cpu_l2_l3_cache_geometry_witness_test.dag b/dag/test/claim/cpu_l2_l3_cache_geometry_witness_test.dag index 978bb0b5d75..7b9eaf4718c 100644 --- a/dag/test/claim/cpu_l2_l3_cache_geometry_witness_test.dag +++ b/dag/test/claim/cpu_l2_l3_cache_geometry_witness_test.dag @@ -39,7 +39,7 @@ test fn w_ampere_l2_citations_cover_all_fields() -> Bool { let citations = ampere_altra_l2_cache_citations list_length(items: citations) == 2 && match citations.first() { - Present { value: h0 } => h0.authority == ampere_altra_rev_a1_datasheet_authority + Present { value: citation } => citation.authority == ampere_altra_rev_a1_datasheet_authority Absent => false } } @@ -56,12 +56,12 @@ test fn w_ampere_l3_geometry_diverges_by_sku() -> Bool { test fn w_ampere_l3_citations_diverge_by_family_datasheet() -> Bool { list_length(items: altra_max_m12830_catalog.l3_cache_citations) == 2 && list_length(items: altra_q6430_catalog.l3_cache_citations) == 2 - && match l3_cache_citations.first() { - Present { value: h0 } => altra_max_m12830_catalog.h0.authority == ampere_altra_rev_a1_datasheet_authority + && match altra_max_m12830_catalog.l3_cache_citations.first() { + Present { value: citation } => citation.authority == ampere_altra_rev_a1_datasheet_authority Absent => false } - && match l3_cache_citations.first() { - Present { value: h0 } => altra_q6430_catalog.h0.authority == ampere_altra_classic_rev_a1_datasheet_authority + && match altra_q6430_catalog.l3_cache_citations.first() { + Present { value: citation } => citation.authority == ampere_altra_classic_rev_a1_datasheet_authority Absent => false } } diff --git a/dag/test/claim/first_optional_construction_witness_test.dag b/dag/test/claim/first_optional_construction_witness_test.dag index 2686cf99b30..58e4fc2a3cb 100644 --- a/dag/test/claim/first_optional_construction_witness_test.dag +++ b/dag/test/claim/first_optional_construction_witness_test.dag @@ -117,9 +117,11 @@ test fn an_absent_optional_into_a_free_type_variable_is_not_refused() -> Bool { // Constructing the `Optional` made every consumer that COMPARES a `first()` result to a bare value // compare across two representations, and `Value::eq` cannot decide those: measured before the // wall, `["schema-v2", "body"].first() == "schema-v2"` answered FALSE with no diagnostic, and -// `[] |> first == none` answered FALSE too. Nine of the fourteen such sites in the generated-artifact -// gate's import closure are merge-admission receipt schema checks, where a quiet `false` rejects a -// valid receipt. A failure arm that fabricates a plausible answer instead of refusing is what +// `[] |> first == none` answered FALSE too. TEN of the fourteen such sites in the generated-artifact +// gate's import closure are merge-admission receipt schema checks -- four in +// `gunbc.merge_admission_produce` and six in `gunbc.merge_admission_subject`, per gunbc#9912's +// landed consumer census -- where a quiet `false` rejects a valid receipt. An earlier revision of +// this annotation said nine and understated the merge-admission share by one. A failure arm that fabricates a plausible answer instead of refusing is what // DESIGN section 5 forbids outright, so both shapes now raise `CrossRepresentationEquality`. // // THE TWO REDS ARE FIXTURE-MEASURED, NOT ENROLLED HERE, AND THAT IS STATED RATHER THAN GLOSSED: a diff --git a/dag/test/claim/machine_shape_catalog_integration_witness_test.dag b/dag/test/claim/machine_shape_catalog_integration_witness_test.dag index 559814028e0..efb816492bd 100644 --- a/dag/test/claim/machine_shape_catalog_integration_witness_test.dag +++ b/dag/test/claim/machine_shape_catalog_integration_witness_test.dag @@ -44,42 +44,34 @@ fn cpu_catalog_shape() -> MachineShape { } test fn w_cpu_cache_levels_integrated_into_memory_shape() -> Bool { - match domains.first() { - Present { value: h0 } => let memory = cpu_catalog_shape().h0.memory + match cpu_catalog_shape().domains.first() { + Present { value: domain } => + let memory = domain.memory + list_length(items: memory) == 3 + && match memory.first() { + Present { value: l1 } => + l1.id == ampere_l1_level + && byte_size_count(b: l1.capacity) == ampere_l1_bytes_per_core + && match l1.transfer_grain { + Present { value: grain } => byte_size_count(b: grain) == ampere_cache_line_bytes + Absent => false + } + Absent => false + } + && match memory.skip(n: 1).first() { + Present { value: l2 } => + l2.id == ampere_l2_level + && byte_size_count(b: l2.capacity) == ampere_l2_bytes_per_core + Absent => false + } + && match memory.skip(n: 2).first() { + Present { value: l3 } => + l3.id == ampere_l3_level + && byte_size_count(b: l3.capacity) == ampere_l3_bytes + Absent => false + } Absent => false } - list_length(items: memory) == 3 - && match memory.first() { - Present { value: h0 } => h0.id == ampere_l1_level - Absent => false - } - && match memory.first() { - Present { value: h0 } => byte_size_count(b: h0.capacity) == ampere_l1_bytes_per_core - Absent => false - } - && match memory.first() { - Present { value: h0 } => match h0.transfer_grain { - Present { value: grain } => byte_size_count(b: grain) == ampere_cache_line_bytes - Absent => false - } - Absent => false - } - && match memory.skip(n: 1).first() { - Present { value: h0 } => h0.id == ampere_l2_level - Absent => false - } - && match memory.skip(n: 1).first() { - Present { value: h0 } => byte_size_count(b: h0.capacity) == ampere_l2_bytes_per_core - Absent => false - } - && match memory.skip(n: 2).first() { - Present { value: h0 } => h0.id == ampere_l3_level - Absent => false - } - && match memory.skip(n: 2).first() { - Present { value: h0 } => byte_size_count(b: h0.capacity) == ampere_l3_bytes - Absent => false - } } test fn w_cpu_catalog_lane_count_matches_threads() -> Bool { diff --git a/dag/test/claim/machine_shape_witness_test.dag b/dag/test/claim/machine_shape_witness_test.dag index 29569527c30..bb9d499d3f3 100644 --- a/dag/test/claim/machine_shape_witness_test.dag +++ b/dag/test/claim/machine_shape_witness_test.dag @@ -73,20 +73,24 @@ fn shape_from_rtx5090_catalog() -> MachineShape { test fn catalog_derivation_matches_row() -> Bool { let shape = shape_from_rtx5090_catalog() - match domains.first() { - Present { value: h0 } => let memory = shape.h0.memory - Absent => false - } - let device = memory.skip(n: list_length(items: memory) - 1).first() - match machine_shape_domain_lane_count(shape: shape) { - Present { value: lanes } => - hardware_thread_count_value(t: lanes) == 21760 - && byte_size_count(b: device.capacity) == byte_size_count(b: nvidia_rtx_5090_catalog.memory.capacity) - && match device.bandwidth { - Present { value: bw } => - bandwidth_count(b: bw) == bandwidth_count(b: nvidia_rtx_5090_catalog.memory_bandwidth) + match shape.domains.first() { + Present { value: domain } => + let memory = domain.memory + match memory.skip(n: list_length(items: memory) - 1).first() { + Present { value: device } => + match machine_shape_domain_lane_count(shape: shape) { + Present { value: lanes } => + hardware_thread_count_value(t: lanes) == 21760 + && byte_size_count(b: device.capacity) == byte_size_count(b: nvidia_rtx_5090_catalog.memory.capacity) + && match device.bandwidth { + Present { value: bw } => + bandwidth_count(b: bw) == bandwidth_count(b: nvidia_rtx_5090_catalog.memory_bandwidth) + Absent => false + } Absent => false } + Absent => false + } Absent => false } } @@ -109,24 +113,25 @@ fn shape_from_m5_catalog() -> MachineShape { } test fn gpu_sm_cache_levels_integrated_when_cited() -> Bool { - match domains.first() { - Present { value: h0 } => let memory = shape_from_rtx5090_catalog().h0.memory + match shape_from_rtx5090_catalog().domains.first() { + Present { value: domain } => + let memory = domain.memory + list_length(items: memory) == 3 + && match memory.first() { + Present { value: device } => byte_size_count(b: device.capacity) == rtx5090_sm_register_bytes + Absent => false + } + && match memory.skip(n: 1).first() { + Present { value: device } => byte_size_count(b: device.capacity) == rtx5090_sm_shared_bytes + Absent => false + } Absent => false } - list_length(items: memory) == 3 - && match memory.first() { - Present { value: h0 } => byte_size_count(b: h0.capacity) == rtx5090_sm_register_bytes - Absent => false - } - && match memory.skip(n: 1).first() { - Present { value: h0 } => byte_size_count(b: h0.capacity) == rtx5090_sm_shared_bytes - Absent => false - } } test fn gpu_sm_cache_absent_when_ungranted() -> Bool { - match domains.first() { - Present { value: h0 } => list_length(items: shape_from_m5_catalog().h0.memory) == 1 + match shape_from_m5_catalog().domains.first() { + Present { value: domain } => list_length(items: domain.memory) == 1 Absent => false } } diff --git a/dag/test/claim/nbd_proxy_virtual_media_install_witness_test.dag b/dag/test/claim/nbd_proxy_virtual_media_install_witness_test.dag index b6d162dcc25..6cf519740e3 100644 --- a/dag/test/claim/nbd_proxy_virtual_media_install_witness_test.dag +++ b/dag/test/claim/nbd_proxy_virtual_media_install_witness_test.dag @@ -37,17 +37,29 @@ test fn nbd_proxy_actuator_steps_serve_boot_restart() -> Bool { ) list_length(items: steps) == 3 && match steps.first() { - ActuatorNbdProxyServe { endpoint: e, slot: 0, index: 0, export: _ } => - e == VmSlotIndexEndpoint - _ => false + Present { value: only } => + match only { + ActuatorNbdProxyServe { endpoint: e, slot: 0, index: 0, export: _ } => + e == VmSlotIndexEndpoint + _ => false + } + Absent => false } && match steps.skip(n: 1).first() { - ActuatorBootOnce { target: BootTargetCd, enabled: _ } => true - _ => false + Present { value: only } => + match only { + ActuatorBootOnce { target: BootTargetCd, enabled: _ } => true + _ => false + } + Absent => false } && match steps.skip(n: 2).first() { - ActuatorForceRestart { reset_type: _ } => true - _ => false + Present { value: only } => + match only { + ActuatorForceRestart { reset_type: _ } => true + _ => false + } + Absent => false } } @@ -58,8 +70,12 @@ test fn pxe_actuator_steps_stage_boot_restart() -> Bool { ) list_length(items: steps) == 3 && match steps.first() { - ActuatorPxeNetworkBootStaging => true - _ => false + Present { value: only } => + match only { + ActuatorPxeNetworkBootStaging => true + _ => false + } + Absent => false } } diff --git a/dag/test/claim/roadmap/roadmap_execution_contract_witness_test.dag b/dag/test/claim/roadmap/roadmap_execution_contract_witness_test.dag index 90e29c6ff11..2c42298a528 100644 --- a/dag/test/claim/roadmap/roadmap_execution_contract_witness_test.dag +++ b/dag/test/claim/roadmap/roadmap_execution_contract_witness_test.dag @@ -52,7 +52,8 @@ test fn dag_claim_derives_only_the_claim_runner() -> Bool { ) count(capabilities) == 1 && match capabilities.first() { - Present { value: h0 } => execution_capability_label(capability: h0.value) == "gunbc-claim-runner" + Present { value: only } => + execution_capability_label(capability: only) == "gunbc-claim-runner" Absent => false } } diff --git a/dag/test/claim/runner/runner_connectivity_repair_plan_witness_test.dag b/dag/test/claim/runner/runner_connectivity_repair_plan_witness_test.dag index ecceb8ebab8..f774ef4ed1c 100644 --- a/dag/test/claim/runner/runner_connectivity_repair_plan_witness_test.dag +++ b/dag/test/claim/runner/runner_connectivity_repair_plan_witness_test.dag @@ -99,24 +99,44 @@ test fn replace_action_projects_five_step_repair_sequence() -> Bool { RepairExecute { slot: _, steps: steps } => list_length(items: steps) == 5 && match steps.first() { - ObserveRunnerLocalState { slot: _ } => true - _ => false + Present { value: only } => + match only { + ObserveRunnerLocalState { slot: _ } => true + _ => false + } + Absent => false } && match steps.skip(n: 1).first() { - ObserveGitHubRunnerRegistration { slot: _ } => true - _ => false + Present { value: only } => + match only { + ObserveGitHubRunnerRegistration { slot: _ } => true + _ => false + } + Absent => false } && match steps.skip(n: 2).first() { - ReplaceRunnerIncarnation { slot: _, from: _, unit: _ } => true - _ => false + Present { value: only } => + match only { + ReplaceRunnerIncarnation { slot: _, from: _, unit: _ } => true + _ => false + } + Absent => false } && match steps.skip(n: 3).first() { - ObserveRunnerLocalState { slot: _ } => true - _ => false + Present { value: only } => + match only { + ObserveRunnerLocalState { slot: _ } => true + _ => false + } + Absent => false } && match steps.skip(n: 4).first() { - ObserveGitHubRunnerRegistration { slot: _ } => true - _ => false + Present { value: only } => + match only { + ObserveGitHubRunnerRegistration { slot: _ } => true + _ => false + } + Absent => false } RepairNoop { reason: _ } => false RepairRefusedPlan { slot: _, reason: _ } => false diff --git a/dag/test/claim/served_surface_browser_observation_witness_test.dag b/dag/test/claim/served_surface_browser_observation_witness_test.dag index 035ace83359..94219ba952c 100644 --- a/dag/test/claim/served_surface_browser_observation_witness_test.dag +++ b/dag/test/claim/served_surface_browser_observation_witness_test.dag @@ -124,7 +124,10 @@ test fn witness_bodied_502_retains_network_document() -> Bool { test fn witness_reload_trial_claims_sufficiency_not_necessity() -> Bool { let observation = playwright_chromium_151_linux_arm64_headless_navigation_run.specimens.connection_refused - let trial = observation.recovery_trials.first() - trial.action == ExplicitReload - && trial.outcome == HealthyResponseDocumentObserved + match observation.recovery_trials.first() { + Present { value: trial } => + trial.action == ExplicitReload + && trial.outcome == HealthyResponseDocumentObserved + Absent => false + } } diff --git a/dag/test/claim/websocat_install_source_witness_test.dag b/dag/test/claim/websocat_install_source_witness_test.dag index 10c87dd290d..0675976d83d 100644 --- a/dag/test/claim/websocat_install_source_witness_test.dag +++ b/dag/test/claim/websocat_install_source_witness_test.dag @@ -5,10 +5,14 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly test fn websocat_cli_tool_installs_via_github_release_not_apt() -> Bool { match websocat_cli_tool.installable_via.first() { - SourceGitHubRelease { repo: r, tag: t, install_path: p, asset_aarch64: _, asset_x86_64: _ } => - r == websocat_github_repo - && t == websocat_github_tag - && p == websocat_install_path - _ => false + Present { value: only } => + match only { + SourceGitHubRelease { repo: r, tag: t, install_path: p, asset_aarch64: _, asset_x86_64: _ } => + r == websocat_github_repo + && t == websocat_github_tag + && p == websocat_install_path + _ => false + } + Absent => false } } diff --git a/src/v2/lens/schedule_lens.dag b/src/v2/lens/schedule_lens.dag index bf4fc25b8cc..777fdb05d15 100644 --- a/src/v2/lens/schedule_lens.dag +++ b/src/v2/lens/schedule_lens.dag @@ -152,7 +152,14 @@ fn schedule_lens_verdict_for_ci_floor_ordinary( diagnostic: schedule_lens_violation_diagnostic(kind: CheapGatesMissingFromFirstBatch) } } else { - let batch1 = plan.schedule.skip(n: 1).first() + match plan.schedule.skip(n: 1).first() { + Absent => + Violation { + diagnostic: schedule_lens_violation_diagnostic( + kind: CompileGateNotSecond { expected: compile_gate_fn } + ) + } + Present { value: batch1 } => if (batch1.count() != 1) || !schedule_batch_contains_label(batch: batch1, target: compile_gate_fn) { Violation { diagnostic: schedule_lens_violation_diagnostic( @@ -175,6 +182,7 @@ fn schedule_lens_verdict_for_ci_floor_ordinary( Holds } } + } } } } From fa11d87da0e12893d180e88dc123ee9c68592915 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 20:37:03 +0000 Subject: [PATCH 27/28] A fifth first() shape: the prefix-call form with a field read The four shapes censused so far all keyed on the METHOD spelling `.first()`. `gunbc.roadmap.roadmap_forecast_witness` reads `first(gaps).nodes` in prefix-call form inside a match-arm conjunct, which every one of those greps was structurally unable to express. Migrated to a nested match with an `Absent => false` arm, which is the honest answer for a conjunct asserting a property of the first element: there is no first element, so the property does not hold. Five shapes is therefore a FLOOR on the shape count, not a total. Each of the five was invisible to every grep that preceded it, and this one was found by asking what spelling the previous four assumed rather than by extending them. NOT VALIDATED BY TYPECHECK, and the reason is measured rather than assumed. `claim_batch` cannot resolve this entry at all: `gunbc.roadmap.roadmap_forecast` `path_to_node` and `node_forecast_build` bind a `let` from a match whose other arm `return`s, and the checker takes the let's type as the coproduct union, so three direct-call arguments fail inhabitance. A controlled one-file swap to main's version of that file reproduces all three at the same sites (lines shifted only by the count of lines this branch adds), so the errors are pre-existing on main and not this branch's. The edit is verified by parse only -- 4493 file(s) parse-clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- dag/test/claim/roadmap/roadmap_forecast_witness_test.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dag/test/claim/roadmap/roadmap_forecast_witness_test.dag b/dag/test/claim/roadmap/roadmap_forecast_witness_test.dag index 0cffb6b0264..e88ca4c06d9 100644 --- a/dag/test/claim/roadmap/roadmap_forecast_witness_test.dag +++ b/dag/test/claim/roadmap/roadmap_forecast_witness_test.dag @@ -462,7 +462,7 @@ test fn witness_empty_calibration_cell_refuses() -> Bool { ) { ScheduleForecastKnown { forecast: _ } => false ScheduleForecastRefused { refusals } => any(refusals, refusal => match refusal { - NoCalibrationSample { gaps, population_exclusions: _, assumptions: _, scenario_adjustment: _ } => count(gaps) == 1 && first(gaps).nodes == [n.node] + NoCalibrationSample { gaps, population_exclusions: _, assumptions: _, scenario_adjustment: _ } => count(gaps) == 1 && match first(gaps) { Present { value: gap } => gap.nodes == [n.node] Absent => false } ForecastGraphEmpty => false DependencyCycle { nodes: _ } => false DependencyEndpointUnknown { nodes: _ } => false From e7afae6a7639ba6d94981f59a0fc232ed6ca85b9 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 1 Sep 2026 21:16:09 +0000 Subject: [PATCH 28/28] Migrate the three prefix-call value-position first() sites in roadmap_forecast int_at and median_int now return Optional and propagate; calibrate_cell's `count(samples) == 0` guard is dissolved into `match first(samples)`, deriving the CalibrationRefusedNoSample it already declared from the same call that decides it. HELD LOCAL AND NOT PUSHED. This does NOT clear the floor's three errors on this branch, which is measured rather than assumed: after this migration the three errors survive verbatim at the same sites. Their cause is a checker hole -- a `let` bound from a match whose sibling arm `return`s is typed as the union of the binding and the function's return coproduct, because a diverging arm is not modelled as contributing nothing to the join. That is fixed in a separate PR; this branch waits for it rather than routing around it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HsALPpj3hERxcuCfK6Cc23 --- dag/gunbc/roadmap/roadmap_forecast.dag | 35 +++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/dag/gunbc/roadmap/roadmap_forecast.dag b/dag/gunbc/roadmap/roadmap_forecast.dag index 9fa430e3eea..0becaf20131 100644 --- a/dag/gunbc/roadmap/roadmap_forecast.dag +++ b/dag/gunbc/roadmap/roadmap_forecast.dag @@ -325,16 +325,22 @@ type CalibrationResult = Calibrated { interval: CycleTimeInterval } | CalibrationRefusedNoSample { cell: SizingCell } -fn int_at(sorted: List, index: Int) -> Int { +fn int_at(sorted: List, index: Int) -> Optional { first(sorted |> skip(n: index)) } -fn median_int(sorted: List) -> Int { +fn median_int(sorted: List) -> Optional { let n = count(sorted) if (n % 2) == 1 { int_at(sorted: sorted, index: n / 2) } else { - (int_at(sorted: sorted, index: (n / 2) - 1) + int_at(sorted: sorted, index: n / 2)) / 2 + match int_at(sorted: sorted, index: (n / 2) - 1) { + Absent => Absent + Present { value: lower } => match int_at(sorted: sorted, index: n / 2) { + Absent => Absent + Present { value: upper } => Present { value: (lower + upper) / 2 } + } + } } } @@ -344,15 +350,20 @@ fn calibrate_cell(cell: SizingCell, observations: List) -> |> filter(o => sizing_cell_eq(a: o.sizing_cell, b: cell)) |> map(o => millisecond_count(m: o.cycle_time)) |> sort_by(n => n) - if count(samples) == 0 { - CalibrationRefusedNoSample { cell: cell } - } else { - Calibrated { - interval: CycleTimeInterval { - minimum: millisecond(count: first(samples)), - median: millisecond(count: median_int(sorted: samples)), - maximum: millisecond(count: first(reverse(samples))), - sample_count: count(samples), + match first(samples) { + Absent => CalibrationRefusedNoSample { cell: cell } + Present { value: minimum } => match first(reverse(samples)) { + Absent => CalibrationRefusedNoSample { cell: cell } + Present { value: maximum } => match median_int(sorted: samples) { + Absent => CalibrationRefusedNoSample { cell: cell } + Present { value: median } => Calibrated { + interval: CycleTimeInterval { + minimum: millisecond(count: minimum), + median: millisecond(count: median), + maximum: millisecond(count: maximum), + sample_count: count(samples), + } + } } } }