Skip to content

Genericity is declared, not inferred from a failed lookup - #10727

Merged
briansrls merged 22 commits into
mainfrom
session/smart-ant-528
Sep 8, 2026
Merged

briansrls merged 22 commits into
mainfrom
session/smart-ant-528

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

The state this refuses

At the census-borrowed declaration route, absence from a lookup was standing as positive evidence of generic ownership — and the environment that lookup consulted has no bindings at all, so the evidence was unconditional rather than occasional.

v1.compiler.env census_declaration_type_env builds bindings, str_bindings, ancestry_str_bindings and intern_table as empty maps, seeded only with the declaration's type-parameter bindings. In that environment lookup_binding_by_name_local can only ever find a tp_name. Two sites read a miss there as "this leaf is a generic":

  • v1.compiler.lookup declaration_unbound_leaf_names — results concatenated into the generic-name roster;
  • v1.compiler.env declaration_substitution_basis — second disjunct, the same test again.

So for every census-borrowed declaration, every unqualified non-kernel concrete type name in every parameter was enrolled as a generic parameter of that declaration and stamped TypeVariable { id: <its own name> }. Always, not sometimes.

Why that is a floor defect

v1.compiler.infer direct_call_argument_inhabitance_diags selects the substitution basis as the declared side whenever it carries an unbound type variable, and declared_type_inhabitance reads that stamp as a generic formal and declines with UndecidableGenericFormal. A value of any constructor reaching a NonEmptyStr or a HostIdentity position was therefore admitted in silence — not because the pair was undecidable, but because the declared side's identity had been overwritten before the judgment ran.

DESIGN §4b puts "values inhabit declared types" in the ordinary compiler floor, and §5 names this shape exactly: unable to resolve, so widen to "generic", so decline. The absorbing arm destroyed the only signal that would have made the deficit rank.

The authority was already in hand, one field over

v1.compiler.lookup borrowed_census_callable_candidate computes borrowed_generic_param_names on its first line and classifies the return type of that same declaration positively with it, through qualify_borrowed_type_names: roster members stay variables, every other bare leaf resolves to its owner module and is qualified. The parameter types, in the next field of the same record literal, went through the guess.

One question, two answers, inside one record literal — DESIGN §3. The parameter side now consumes the same roster, and the absence disjunct is deleted from both sites together, because they were one classification authority written twice.

Scope, deliberately narrow: only the roster moves. declared_type stays param_node_type_expr. Routing it through qualify_borrowed_type_names as well would change the name every consumer of ResolvedFormal.declared_type reads — and one of them, direct_call_generic_type_argument_inhabitance_diags, compares authored_name_at of the declared node against the actual's for equality. That is a second, wider claim, and a population delta after it could not be attributed to this one. If the judgment turns out to need qualification to reach Inhabits on a bare-declared / qualified-produced pair, it returns as its own PR with its own justification.

The roster reads a declared fact, not a naming convention

v1.compiler.parse parse_optional_type_params mints, for each name in the angle-bracket clause, one param whose name and whose type-expr name are that name. The <...> clause has no other carrier. A search for an authored parameter of the form Name: Name across src/v1, src/v2 and dag matches nothing, while fn f<T>(...) matches throughout std — so the spelling this roster recognises is not hand-authorable in the corpus and exists only where the parser minted it from a declared generic clause. fn_type_param_names and param_is_generic_decl already read the same encoding; this adds a fourth reader of one fact, not a second fact. (Consolidating those readers is a real §3 item and deliberately not opened here.)

Cost

The obvious alternative — keep the absence test but consult the census-backed lookup_binding_by_name — is refused on shape, and the shape argument needs no timing: it drags listed_import_required_bare_call_blocked, the global-bare ambiguity list and the nearest-ancestor containment walk onto every leaf of every declaration, where the roster route is a map lookup against a roster already computed for the sibling field. DESIGN §6 bare-minimum-cost refuses the first shape independently of the semantic argument, and the cheaper path is also the correct one — the usual direction when a heuristic is replaced by the fact it was approximating.

(An earlier draft of this paragraph cited a wall-clock figure for that alternative. It is withdrawn: the run that produced it compiled stale stage0 mirrors, so it measured a compiler that did not contain the change. No number is cited here in its place.)

Producer census: is the parser the only site that can mint name == type-expr name?

The source-shape reading above says what nobody has written; the claim the repair depends on is what nothing can produce. make_param_node has twelve call sites (thirteen matches, one the definition), and every one is classified:

producer can it mint pname == tname?
parse.parse_optional_type_params yes, by construction — the intended and only structural producer
types.algebra_method_field and the two callable-type builders (×3) no — name is the literal "_"; would need a type named _
parse at the two name: "" sites (×2) no — excluded by the pname != "" guard
infer fold-signature params (×2) no — fixed names acc / elem against computed types
parse record-field → param (×2) only if an authored field is spelled Name: Name
parse authored value params (×2) only if an authored param is spelled Name: Name

The last four are authored-dependent, and both authored shapes were censused across src/v1, src/v2 and dag: zero occurrences of a parameter or a record field whose name equals its type name.

So exactly one producer mints the shape structurally; the rest are either name-excluded by construction or need an authored spelling that does not occur. Stated at honest rung: for the eight structural producers this is structurally impossible (§4b(4)) — the shape has no constructor there. For the four authored-dependent ones it is mechanically preventable at best: an author who writes fn f(Vendor: Vendor) would have that parameter read as a generic. That is a pre-existing property of the parser's encoding, shared by all three readers of it below, and is neither introduced nor climbed here.

Recorded §3 fork (not opened here)

Three readers test one fact — "is this param the parser's encoding of a generic clause?" — with three separate predicates:

  • v1.compiler.resolve fn_type_param_names
  • v1.compiler.infer param_is_generic_decl
  • v1.compiler.env borrowed_generic_param_names (the one this PR routes the parameter side through)

They differ in detail (param_is_generic_decl additionally admits a type-expr already stamped a variable). One fact, three tests, is a §3 fork; it is known and deliberately unwritten here so this cut stays one cut. Recording it so the next person to touch any of the three does not re-derive this analysis.

Declared boundary (§4b(2)) — it refuses where it cannot decide

The roster is the declaration's own generic clause. A synthesized or member declaration that borrows an enclosing owner's generic without restating it is not on that roster, so its borrowed leaf stays bare and reaches the judgment with a declared name that resolves to nothing — reported UndecidableFormalUnresolved.

That is the correct disposition, not a gap: the position genuinely is not judgeable from what the declaration carries, and the reason names precisely that. The change refuses where it cannot decide instead of guessing, which is the whole point of it — the state it replaces claimed genericity it could not demonstrate. Next-rung trigger, at capability grain: an owner-generic carrier on the borrowed declaration. Not a census and not a fixture — the carrier.

This PR does not claim the roster is exhaustive over every generic a declaration could mention.

Measured

Re-derived on the current head. Every earlier figure in this body is SUPERSEDED, including the previous "final-head" table: the tree has since taken another origin/main merge and eight commits, and a number that outlives its base is not evidence.

Construction. Same corpus on both halves, same instrument, two binaries differing only in the three stage0 mirrors — the before half built from origin/main's v1_compiler_infer{,_env,_lookup}.rs, the after half from this branch. Both built locally and frozen outside target/ before running. Vary the compiler, hold the corpus.

Transition matrix over site identity (module, span):

from to sites
UndecidableGenericFormal no diagnostic 8425
UndecidableGenericFormal unchanged 5097
no diagnostic UndecidableRefinementIntroduction 4944
UndecidableOptionalCarrier unchanged 1970
UndecidableProducedIdentityErased unchanged 881
UndecidableGenericFormal UndecidableProducedIdentityErased 103
UndecidableGenericFormal UndecidableRefinementIntroduction 45
DeclaredTypeNotInhabited DeclaredTypeNotInhabited 7
UndecidableFormalUnresolved unchanged 6
no diagnostic UndecidableRefinementPeerChains 5
no diagnostic UndecidableGenericFormal 1

The refusal set is identical BY IDENTITY: the same 7 sites before and after. No refusal gained, none lost. This diff removes a fail-open without adding a single refusal the corpus did not already carry — and that fact does more work in this PR than any other, because it is what answers the freeze question below.

The reason split is visible in the matrix and it was not cosmetic. 5 sites report UndecidableRefinementPeerChains and 4944 report UndecidableRefinementIntroduction. Before the split those were one bucket of 4949 filed under a label saying they were introductions, 5 of which are not. Small, and exactly the population that would have been silently carried past the next climb.

Rung-honest statement. This does not climb the class: no previously-accepted invalid site newly refuses.

False generic classification is removed; the underlying inhabitance deficit remains non-blocking but is now typed, located and enumerable.

The −8425 is not permitted to do rhetorical work the +4949 contradicts.

Every off-diagonal cell, characterised rather than counted:

  • The 8425 are the repair: declared types classified as the declaration's own generic parameter because a local-only lookup missed them.
  • The 103 are an exhaustive join: all 103 came from UndecidableGenericFormal and none from any other disposition. With the declared identity restored the judgment progresses further and finds the produced identity genuinely unavailable — each a bare identifier bound by a match pattern or let. The next honest deficit, exposed rather than introduced.
  • The 4944 and the 5 were silently accepted before, on the local-declaration route that was never stamped. None of them refuses.
  • The 45 had their false generic classification removed and then reach the refinement question, declining there instead — a move between two non-blocking dispositions.
  • The 1 is a single site in v2.lens.unused_parameters at the generic type argument seam, adjacent in the same call expression to a formal whose false classification this repair cleared. A different seam, not a re-appearance.

One instrument defect found in my own controls, and it generalises

All six controls enrolled in the previous head were false, and they were false for a reason worth stating rather than quietly fixing. violation_count reports every row of the class in the whole census, not the fixture's contribution — the counts run to thousands — while the literals I asserted (== 1, == 0) were copied from a probe delta. That is exactly the shape DESIGN §5 refuses: a merge-blocking test comparing a live population to a numeric literal not grounded in a controlled fixture. They did not merely have wrong numbers; they were comparing two different quantities.

The repair generalises: each arm is now a PAIR of fixtures differing on exactly one axis, so the shared corpus contribution cancels and only the axis is measured. Anyone authoring a witness against a census will reach for the same wrong literal, and the pair form is the answer.

Enrolled controls

No enforcement RED is owed, because no enforcement climb is claimed. An earlier draft carried one — a previously-accepted unrelated-constructor site must newly refuse — and it is retired, not satisfied. Forcing it would make this PR solve the separate literal-introduction question merely to justify the story about why it existed, and an unmet acceptance control in a merged body is worse than none: the next reader takes it as met.

What is owed is a discriminating RED for the classification repair. All five arms below were measured live on this head, and all six of their predecessors were observed RED under the real floor harness before the oracle repair — so they discriminate in the direction that matters.

control asserts (pair oracle) discriminating mutation
w_a_concrete_declared_leaf_is_not_classified_generic_through_the_census sha256_digest_wire_form(digest: Sha256Digest) == a fixture with no call restore the local-miss classification in either deleted site → stamped TypeVariable { id: "Sha256Digest" } → the pair separates by one
w_a_branded_refinement_at_its_own_declared_base_inhabits declared NonEmptyStr ← produced HostIdentity: 0 refused, and == its exact-match partner revert the where-refinement exclusion → refuses, as it did at two live corpus sites
w_two_distinct_brands_over_one_base_are_not_refused_twice 0 refused, and > its exact-match partner: this relation declines, the existing brand wall refuses measured at the direct-call argument seam, not assumed from the cast path
w_a_nominal_opaque_alias_does_not_ground_through_the_refinement_chain Secret does not ground to String (1 refused) follow a nominal_opaque alias in the chain → sibling pair
w_a_bare_literal_at_a_refinement_formal_declines_and_a_cast_one_does_not bare literal > cast form separates "we surfaced the introduction question" from "we made every refinement pair undecidable"

The positive census arm is withdrawn because its RED is not authorable

The sixth control was to assert that a declared generic clause is still classified generic through the census route. Measured: that fixture contributes zero rows of the class — equal to a fixture with no call at all — so the seam emits nothing there whether the classification is right or wrong. An arm asserting one is false; an arm asserting zero is green by construction and would later be cited as coverage of something it never measured. DESIGN §4b says to ask whether the RED is authorable before writing the check, so the arm and its now-dangling fixture are removed rather than enrolled as a decoration.

The cause is a harness boundary, not a fixture shape. src/v1 is not a --source-root in any workflow, so no witness under the required floor can observe declaration_substitution_basis's output at all; dag/test/claim/checkpoint_identity_keying_witness_test.dag records the same boundary and exists to work around it black-box. That makes this a §4b missing-harness case, which is a next-rung trigger and explicitly not a permanent ceiling.

Trigger, at capability grain: a floor-executable surface that can observe the substitution basis's classification — either src/v1 on a floor source root, or a black-box seam that renders the stamp distinguishably.

The residue, stated precisely. What is measured is the removal of a false positive, which the negative arm measures directly and discriminatingly. What stays unmeasured is the survival of the positive authority — that a genuine declared generic is still classified as one. The roster union below makes that considerably safer by construction; construction is not evidence, and this body does not blur them.

Review finding taken: the roster is a union again

Deleting the absence disjunct was the repair. Replacing fn_type_param_names with borrowed_generic_param_names alongside it was a second, uncontrolled change, invisible to every control enrolled at the time. The two predicates are not the same — the borrowed roster is wider on one axis and narrower on two, dropping any declared type param whose type expression is not a childless NoConnective leaf — so such a param would have been reclassified as a concrete unresolved name: the exact failure the negative control exists to prevent, arriving through the other door. declaration_generic_names is now concat(tp_names, map_keys(borrowed_generic_param_names(...))), which is what the pre-repair code passed. Only the absence disjunct is deleted. (Review finding, tidy-lynx-804, 2026-09-07.)

I took the union rather than measuring the two predicates for agreement: the union is correct whether or not the sets agree today, so measuring to license a narrowing that is not needed is work priced against elegance (§6).

Admission under the v1 freeze (gunbc.v1_maintenance_standing v1_seed_standing)

This PR changes the frozen v1 seed, so it owes an admission argument, and this section is it. §3's frozen-X carve-out reclassifies v1 as semantics-frozen with maintenance active under a PURPOSE test — a change is admitted when it serves the v2 self-host program — with five refused classes that DOMINATE every admission. The whole diff's source change is in src/v1, so the question is live and was not answered in earlier revisions of this body.

The admitted instance is DefectRepairDiagnosticsMayMove, and the standing draws the line in exactly the terms this change needs: the seed doing something INCORRECT is admitted, repair it, diagnostics may move; the seed FAILING TO DO something it never did is SeedFeatureCompletion, refused. The producer repair is pinned to a specific incorrect behaviour the seed exhibits today, evidenced rather than asserted: a lookup miss against an environment carrying no bindings was read as positive evidence of generic ownership, unconditionally on the census-borrowed route, and 8411 sites are measured moving off that misclassification.

The self-host connection is direct, and it is a measurement rather than a claim: v1 COMPILES v2. Partitioning the moved population by source root:

population total in src/v2
cleared false-generic 8425 3707 (44.0%)
advanced to UndecidableProducedIdentityErased 103 98 (95.1%)
refinement introduction 4944 223 (4.5%)

The compiler that builds v2 was overwriting the declared identity at 3707 positions in v2's own sources and admitting any constructor there in silence. That is not a downstream benefit of a seed improvement — it is the self-host program's own files being mis-judged today, so repairing it meets the purpose test at its centre rather than at its edge. The 95.1% is the sharper figure: the population where the judgment now gets far enough to find the produced identity missing is almost entirely v2.

The lane is compiler-guarantee — "the DESIGN §4b ladder climbs" — not v1 exit. A reviewer reading only the v1-exit lane sees lines added to the thing being exited, which is why the lane is named here.

And the seam has no v2 counterpart, so "fix it in v2 instead" is not an available option. Derivable predicate rather than an assertion: lookup_binding_by_name_local, substitution_basis and declaration_unbound_leaf_names have zero occurrences anywhere under src/v2 (they occur only in src/v1). Anyone can re-run that at any later head.

The hardest objection, taken at its strongest

A reviewer applying the refusal precedent — the lane refused as SeedFeatureCompletion for authoring OperatorSpec/TypeCheckpoint rows so the emitter could render constructs it has no row for — could reasonably say that a new inhabitance relation with a new type and two new verdict reasons is NewLanguageBehavior. That is the right objection to raise and it deserves a measurement, not an assurance.

It is answered by the refusal set, which is identical by identity: the same 7 sites before and after. The diff adds no refusal and removes none. Nothing the seed accepted is now rejected, and nothing it rejected is now accepted — so there is no new language behaviour to speak of. What changes is why a non-blocking decline is reported: one coarse reason that was frequently FALSE is replaced by reasons that are true, and the widening arm restores to Inhabits a pair the pre-repair seed also admitted (silently, for the wrong reason).

And the relation is not optional garnish on the repair — it is the repair's integration closure. The producer fix, landed alone, makes the seed newly REFUSE correct code: with the declared identity restored, declared NonEmptyStr <- produced HostIdentity refuses at live corpus sites. Shipping that would be a wrong-closed diff, which §5 makes a hard reject. So the relation exists because the admitted repair exposed a false refusal, and it earns admission on the same instance rather than as capability sought for its own sake. The distinguishing test is the refused-class one: this adds no capability the seed did not already claim — it removes a fabricated judgment and refuses to fabricate its replacement, declining where another authority already decides rather than minting a second one.

What would have made me stop. If the relation had needed to decide pairs the seed never claimed to decide, or had added a refusal the corpus did not already carry, it would be seed capability and I would have said so rather than argued it. The refusal-set identity is what distinguishes those cases, and it is measured on the final head rather than assumed.

The relation fires — proven, because making it unavailable on a miss is exactly the failure that hides

The chain is now discarded whenever any link fails to resolve. A fail-closed collapse is the safe direction, which is precisely why it can hide: if every chain collapsed, the relation would be permanently unavailable — never wrong, never right, never firing — and that is the inert check DESIGN §4b calls worse than absent, because it gets cited as coverage.

Measured on this head, counting emitted reasons over dag + src/v2:

reason sites
UndecidableRefinementIntroduction 4989
UndecidableRefinementPeerChains 5
DeclaredTypeNotInhabited 7 (unchanged)

Both arms reach verdicts, and the peer arm is inhabited rather than being a variant nothing produces.

The widening arm is proven by an enrolled control, and the mechanism matters. w_a_branded_refinement_at_its_own_declared_base_inhabits asserts NotInhabited(widening) == NotInhabited(exact-match partner). If refinement_inhabitance returned Absent for every pair, the widening pair would fall through to the now-unblinded nominal-product arm, which refuses NonEmptyStr against HostIdentity as distinct constructors, while the exact-match partner would not refuse. The equality breaks and the control reds. It is green, so the relation is reaching RefinementWidensToDeclaredBase and admitting.

That property is new: while the product arm was blinded, a dead relation would have produced 0 == 0 and passed vacuously. Removing the blinding is what turned a control that could pass while the relation was dead into one that cannot.

A review-originated finding that moved to its own PR

Review 62172 reported that a where-refinement exclusion added here reopened a refusal for disjoint pairs — a branded refinement at a true product formal. The conclusion was right and the cause was not: measured on both sides, with two compilers differing only in the three stage0 infer mirrors, that pair is unrefused under origin/main as well. The defect is pre-existing and is not this diff's.

It is therefore enrolled in its own PR — #10817 — as a discriminating control, an ExpectAssertionFalse admission and a recurring_failure_mode row, on the rule that a review-originated finding moves to its own PR when its discriminating RED reproduces on main without the reviewed diff. Discovery provenance is recorded here; implementation ownership follows the demonstrated subject. This PR deliberately keeps no second copy of that control — two owners for one property is the fork this project spends its time removing.

The where-refinement blinding is still removed here, on its own merits: it was unnecessary, since the refinement arm runs before the product arm, and removing it is what makes the widening control discriminating above. It is not claimed to repair the disjoint case.

Status

The five enrolled controls and the whole required floor are green locally on this head; the same harness reproduced all six predecessors as red before the repair. The regenerated stage0 mirror is committed with the .dag change, verified by content. docs/design-failure-modes.md is re-projected — it drifted because the merge resolution took main's projection without re-projecting the new row.

One observed unrelated signal, not chased and not fixed here: the floor emits STALE-COST-DEBT for test.claim.live_deploy.emit.apply_script_and_tree_sync_unit_stay_within_ci_runner_grants, enrolled in v2.workflow.floor_cost_debt. It is not counted in unexpected_failures and is not what refuses the lane. This diff does not touch src/v2/workflow/floor_cost_debt.dag at all.

Two claims withdrawn, both refuted by fixture rather than argued down

  • "An explicit cast is not carried at this seam." False: takes_non_empty(s: "x" as NonEmptyStr) produces nothing from this relation.
  • "The refinement predicate goes unchecked at introduction." False, and this one changes the story. A three-way pair settles it: takes_non_empty(s: "") is refused by the where-refinement machinery (type mismatch: expected Product(NonEmptyStr), got Primitive(String)), takes_non_empty(s: "x") is admitted by it, and this relation declines both. So the introduction question is already decided by an authority this relation does not consult — the "never undecidable, merely unconsulted" shape the numeric note in the same file records. The next-rung trigger is correspondingly cheaper: consult that verdict, not carry a new elaboration fact to the seam.

The recurring_failure_mode row is corrected the same way: it asserted that the roster residue reports UndecidableFormalUnresolved, and the census moved that population by zero across the repair, so the row now records that boundary as asserted and unexercised.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

Brian Searls and others added 8 commits September 7, 2026 04:28
At the census-borrowed declaration route, absence from a lookup was standing
as positive evidence of generic ownership -- and the environment that lookup
consulted has no bindings at all, so the evidence was unconditional rather
than occasional.

census_declaration_type_env builds bindings, str_bindings, ancestry_str_bindings
and intern_table as empty maps, seeded only with the declaration's type-parameter
bindings. In that environment lookup_binding_by_name_local can only ever find a
tp_name. Two sites read a miss there as "this leaf is a generic": the roster
producer declaration_unbound_leaf_names, whose results were concatenated into the
generic-name roster, and the stamp producer declaration_substitution_basis, whose
second disjunct repeated the same test. So for every census-borrowed declaration,
every unqualified non-kernel concrete type name in every parameter was enrolled as
a generic parameter and stamped TypeVariable { id: <its own name> } -- always,
not sometimes.

The harm is at the DESIGN 4b floor. direct_call_argument_inhabitance_diags takes
the substitution basis as the declared side whenever it carries an unbound type
variable, and declared_type_inhabitance reads that stamp as a generic formal and
declines with UndecidableGenericFormal. A value of any constructor reaching a
NonEmptyStr or HostIdentity position was therefore admitted in silence -- not
because the pair was undecidable, but because the declared side's identity had
been overwritten before the judgment ran. That is DESIGN section 5's absorbing
fallback: unable to resolve, so widen to generic, so decline.

The authority was already in hand one field over. borrowed_census_callable_candidate
computes borrowed_generic_param_names on its first line and classifies the RETURN
type of the same declaration positively with it, via qualify_borrowed_type_names:
roster members stay variables, every other bare leaf resolves to its owner module
and is qualified. The parameter types, in the next field of the same record literal,
went through the guess. One question, two answers (DESIGN section 3). The parameter
side now consumes the same roster and the same qualifier, and the absence disjunct
is deleted from both sites together, because they were one classification authority
written twice.

The roster reads a declared fact, not a naming convention: parse_optional_type_params
mints one param per name in the angle-bracket clause whose name and whose type-expr
name are both that name, and no authored parameter of that shape exists in the
corpus. fn_type_param_names and param_is_generic_decl already read the same
encoding.

Declared limit: a synthesized or member declaration that borrows an enclosing
owner's generic without restating it is not on its own roster, so that leaf stays
bare and is reported UndecidableFormalUnresolved -- the reason that means exactly
that -- rather than claiming a genericity the declaration does not carry. Its
next-rung trigger is an owner-generic carrier on the borrowed declaration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
DESIGN 4c admits only standalone leading blocks attached to module-scope
declarations; the block sat inside declaration_substitution_basis's body and
the v2 self-compile refused it seven times, once per line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
Routing declared_type through qualify_borrowed_type_names was a second, wider
claim riding under this one's title. Every consumer of ResolvedFormal.declared_type
would have read a qualified name where it read a bare one, and one of them --
direct_call_generic_type_argument_inhabitance_diags -- compares authored_name_at
of the declared node against the actual's for EQUALITY. A population delta after
that change could not be attributed to the genericity repair.

Only the roster moves: declaration_generic_names is now borrowed_generic_param_names
(the declaration's own generic clause) rather than a census of lookup failures, and
declaration_substitution_basis consults that roster alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
… repair

The .dag edit without its emitted seed is drift: heal-generated-artifacts
succeeded on the previous head while required-witnesses-build refused on
exactly v1_compiler_infer_env.rs and v1_compiler_infer_lookup.rs. Leaning on
heal to launder that is relying on one mechanism to answer for a lane that is
separately refusing, so the mirrors are regenerated and committed here,
verified by content: the env mirror loses the lookup disjunct, and the lookup
mirror loses declaration_unbound_leaf_names entirely.

Regenerated by target/release/claim_executor --required-ci --required-lane build
into target/stage0-regen-candidate and installed from there.

Files the class row adds: gunbc.recurring_failure_mode
lookup_miss_read_as_positive_classification, enrolled in the roster, with its
projection into docs/design-failure-modes.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
…one chain

Removing the generic-formal stamp made a question reachable that had never been
put: a value whose type sits on the same refinement chain as its declared
position. Measured the moment the stamp came out, two corpus sites refused --
HostIdentity flowing into a NonEmptyStr formal, in dag/test/claim/ci/
ci_deploy_target_host_witness_test.dag -- which is widening to its own declared
base and is correct code. v1.compiler.infer declared_type_conformance_note had
already measured that exact class from the other side and classified it as
correct code falsely refused.

Two causes, both fixed here.

nominal_product_head_name_if_declared_product read a where-refinement as a
nominal product: `NonEmptyStr = String where non_empty` carries connective Conj
with one child, which is also the shape of a one-field record, so the bare
Conj-with-children test admitted refinements to an arm whose contract is
"concrete named products and their transparent aliases". is_where_refinement_type
already existed to tell them apart and is now consulted.

refinement_inhabitance decides the pair from where_refinement_chain -- the peel
authority peel_where_refinement_base already consumes, so this adds a relation
over the existing single authority and not a second peel. Widening (the produced
type carries the declared one on its chain) INHABITS. The other two arms decline.

WHY NARROWING DECLINES RATHER THAN REFUSING, and this is a measurement. std's
brand prose refuses narrowing, and the first build of this relation did too: 10
corpus refusals became 4928, of which 3870 were a plain String reaching a
NonEmptyStr formal. That is not four thousand defects. Refinement values are
introduced by literal elaboration and declared casts, and a produced node no
longer carries which introduction made it, so the type pair does not settle the
question and refusing on it would fabricate a verdict in the direction DESIGN
section 5 forbids exactly as firmly as fabricating a success. The pair is
reported UndecidableRefinementIntroduction: located, per-reason, countable, so
the deficit can rank. Next-rung trigger: the refinement-introduction authority
reaching this seam, carried on the produced node.

nominal_opaque is not touched: `type Secret nominal_opaque = String` is not a
where-refinement, its chain is one link, and the relation returns absent for it
-- Secret does not ground to String, which is the wall that form exists to
provide.

Measured, same tree, locally built binaries frozen before running, partitioned
by site identity: the refusal set is IDENTICAL to main's -- none gained, none
lost -- while generic-formal declines fall 13643 to 5078 and 4920 pairs reach
the new typed refinement decline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
…t this seam

The first account of why narrowing declines said the produced node no longer
carries which introduction made it. An executed check refutes that half.

dag/test/fixture/compile_phase_frontier_receipt_fixture calls
sha256_digest(hex: "..." as NonEmptyStr) -- a String literal at a refinement
formal, carrying an explicit cast -- and this relation produces NO diagnostic for
it at all. Its four diagnostics are optional-carrier and nothing else. Meanwhile
every site that does decline is a bare literal handed straight to a refinement
formal with no cast: "feedFwdOffsetCoeff" at a NonEmptyStr, 168 at a numeric
refinement, "declaration-worker" at a NonEmptyStr. So one of the two introduction
forms is already read here, and the blanket claim was wrong.

The remaining form is literal elaboration at the declared boundary, which
std.literal_elaboration owns and this relation does not consult -- the same shape
the numeric-realization note in this file already records, where the class was
never undecidable, it was UNCONSULTED. Declining is still right, because
refusing on a fact one is declining to look up fabricates a verdict; but the
trigger is now named at the capability that actually decides it: the elaboration
verdict for a literal at its destination refinement reaching this seam on the
produced node.

Annotation and the reported reason both corrected. No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
Two measurements, one section 3 answer, no behaviour change (annotations are
erased from the semantic projection, so the stage0 mirror is unchanged).

FIRST, the refinement-introduction trigger is not speculative. An empty string
literal handed to a NonEmptyStr formal compiles with NO diagnostic from anything
in the tree. So the 4875 sites this relation newly declines are real unchecked
refinement introductions -- the predicate is not verified at introduction today,
and this relation is the first thing that makes the population visible, located
and countable.

SECOND, the horizontal case is already walled and must not be walled twice.
where_refinement_brand_nominal_verdict, reached through
where_refinement_mismatch_diags on the expected-type path, compares brand
DECLARATION SITES rather than spellings and refuses BrandNominalDistinctDeclarations.
Refusing sibling pairs in this relation as well would be a second authority for
one judgment.

It would also be wrong at this grain, measured rather than reasoned: refusing
every sibling pair produced FilePath at a NonEmptyStr formal, GcpProjectId at a
NonEmptyStr formal and Sha512DigestHex at a NonEmptyStr formal -- peer predicate
refinements over the same base carrying no conflicting brand at all, whose chains
fail to nest only because each is declared over String directly rather than over
NonEmptyStr. A sibling relation is not a brand conflict, and only the brand
authority can tell the two apart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
# Conflicts:
#	dag/gunbc/recurring_failure_mode/roster.dag
#	docs/design-failure-modes.md
gunbc-ci-auto-heal and others added 6 commits September 7, 2026 10:09
Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md lookup_miss_read_as_positive_classification
Ledger-Repair-Judged: docs/design-rung-drops.md
CONTROLS, all measured live on the merged tree before enrolment.

Producer grain, the paired mutations that a diagnostic-count drop cannot prove.
Both go through the GLOBAL-BARE CENSUS route -- callee named without an import,
so it resolves through the corpus census and reaches the producer this repair
changed.
  positive: optional_present<T> still declines UndecidableGenericFormal (1).
    Silencing borrowed_generic_param_names leaves the leaf unstamped, resolving
    nowhere, producing NO diagnostic -- the arm reds on zero.
  negative: sha256_digest_wire_form(digest: Sha256Digest) declines nothing (0).
    Restoring the local-miss classification in either deleted site stamps
    TypeVariable { id: "Sha256Digest" } and the arm reds on one.
Together they prove both halves: declared implies generic, failed lookup does not.

Refinement grain.
  a branded refinement at its own declared base INHABITS (0 refused, 0 undecided
    -- the second count is the discriminating half, since zero refusals alone
    would also hold if the arm merely stopped deciding);
  two distinct brands over one base are not refused twice: this relation declines
    and the existing brand wall refuses, measured AT THE DIRECT-CALL ARGUMENT
    SEAM rather than assumed from the cast path;
  nominal_opaque does not ground through the chain;
  a bare literal declines and a cast one does not, at the same formal.

TWO CLAIMS OF MINE WITHDRAWN, both refuted by fixtures rather than argued down.

The annotation said an explicit cast is not carried at this seam. It is:
takes_non_empty(s: "x" as NonEmptyStr) produces nothing from this relation.

The annotation then said the predicate goes unchecked at introduction, on a probe
that reported no diagnostic for an empty literal. A three-way pair refutes it:
takes_non_empty(s: "") is REFUSED by the where-refinement machinery with
`type mismatch: expected Product(NonEmptyStr), got Primitive(String)`, while
takes_non_empty(s: "x") is admitted by it, and this relation declines both. So
the introduction question is ALREADY DECIDED by an authority this relation does
not consult -- the same "never undecidable, merely unconsulted" shape the numeric
note in this file records. The next-rung trigger is correspondingly cheaper:
consult that verdict, not carry a new elaboration fact to the seam.

The recurring_failure_mode row asserted that the roster residue reports
UndecidableFormalUnresolved. The census moved that population by zero across the
repair, so the row now records it as an ASSERTED AND UNEXERCISED boundary. A row
is the durable artifact and a disposition nothing executed is
specification-without-execution.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md lookup_miss_read_as_positive_classification
Ledger-Repair-Judged: docs/design-rung-drops.md
Three repairs, each from a measurement or a review finding, not from taste.

THE ROSTER IS A UNION AGAIN. Deleting the absence disjunct was the repair;
replacing fn_type_param_names with borrowed_generic_param_names alongside it was
a second, uncontrolled change. The two predicates are not the same: the borrowed
roster is wider on one axis and NARROWER on two, so a declared type param whose
type expression is not a childless NoConnective leaf would have been reclassified
as a concrete unresolved name -- the exact failure the negative control exists to
prevent, arriving through the other door. declaration_generic_names is now
concat(tp_names, map_keys(borrowed_generic_param_names(...))). Review finding,
tidy-lynx-804, 2026-09-07.

THE CONTROLS WERE AUTHORED AGAINST THE WRONG ORACLE AND ALL SIX WERE FALSE.
violation_count reports every row of the class in the WHOLE census, not the
fixture's contribution, and the literals in those arms were copied from a probe
DELTA. That is precisely what DESIGN 5 refuses: a merge-blocking test comparing a
live population to a numeric literal that is not grounded in a controlled
fixture. Each arm is now a PAIR of fixtures differing on exactly one axis, so the
shared corpus contribution cancels and only the axis is measured. Measured on
this head, with the same instrument on both halves:

  declared NonEmptyStr <- produced HostIdentity  ==  its exact-match partner
  the horizontal brand pair                       >  its exact-match partner
  bare literal at a refinement formal             >  the cast form
  concrete leaf via global-bare                   ==  a fixture with no call

THE POSITIVE CENSUS ARM IS WITHDRAWN BECAUSE ITS RED IS NOT AUTHORABLE. The
fixture contributes zero rows of the class -- equal to a fixture with no call at
all -- so the seam emits nothing there whether the classification is right or
wrong. An arm asserting one is false; an arm asserting zero is green by
construction and would later be cited as coverage of something it never
measured. The cause is a harness boundary, not a fixture shape: src/v1 is not a
source root under the required floor, so no witness there can observe the
substitution basis. NEXT-RUNG TRIGGER, at capability grain: a floor-executable
surface that can observe the substitution basis's classification -- src/v1 on a
floor source root, or a black-box seam that renders the stamp distinguishably.

What is measured is the REMOVAL OF A FALSE POSITIVE, which the negative arm
measures directly. What stays unmeasured is the survival of the positive
authority. The union above makes that safer by construction; construction is not
evidence and this does not claim it is.

Also regenerates docs/design-failure-modes.md, which drifted because the merge
resolution took main's projection without re-projecting the new row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 7, 2026 16:47
Brian Searls and others added 7 commits September 7, 2026 19:05
Six items, all from review, all on the relation this PR adds. They are one
shape stated once: the new relation collapsed distinctions its neighbours
preserve -- two facts under one reason, two identities under one spelling, and a
missing identity repaired by lowering the grain until something compared equal.

1. THE PEER ARM CARRIES ITS OWN REASON. Narrowing and siblings both reported
   UndecidableRefinementIntroduction, whose label describes introduction only.
   The measured population proves they differ: declared NonEmptyStr <- produced
   FilePath, GcpProjectId, Sha512DigestHex are peer predicates over one base,
   neither introductions nor brand conflicts. A mixed bucket is not located,
   per-reason or countable -- the property that note claims for itself -- and it
   would mis-aim the next trigger, discharging it while leaving a peer residue
   under a name asserting it had been handled. (Review 62043, cursor/auto.)

2. THE CONSTRUCTOR IS RENAMED WITH THE REASON. RefinementSiblingBrands ->
   RefinementPeerChains: the measured population contains no brands, so the
   old name encoded the same conflation one level above the reason.

3. THE CHAIN IS COMPARED AT DECLARATION SITE, NOT SPELLING. It consumed
   qualified_last_segment -- a module-stripped last segment -- while
   where_refinement_brand_declaration_site in the same file builds file#name
   identity for the same question. Two homonymous refinements would have
   compared EQUAL and widening would have returned Inhabits for a pair that does
   not inhabit. This file already suffered that class: a second HostIdentity was
   minted beside product.placement_supply's and did not surface as a duplicate
   declaration. Only one survives today because the incident was CLEANED UP, not
   avoided -- so this is a reintroduction, not a hypothetical.

4. THE LOOKUP-MISS ARM REFUSES INSTEAD OF WIDENING. It manufactured a singleton
   chain from the name's last segment when no declaration was established, so
   callers treating both chains as established compared something that was never
   resolved -- DESIGN §5's absorbing fallback, and under declaration-site keying
   it would fabricate an identity the subject does not have, at the one grain
   where unrelated things compare equal. ABSENCE OF IDENTITY CANNOT BE REPAIRED
   BY LOWERING THE IDENTITY GRAIN UNTIL SOMETHING COMPARES EQUAL.

5. THE FAILURE-MODE ROW'S CEILING STATES BOTH POPULATIONS. It claimed ceiling 3
   from a premise about the CLAUSE while the subject is the CLASSIFICATION,
   which a predicate decides and an authored shape can satisfy: eight structural
   producers cannot mint `pname == type-expr name`, four authored-dependent
   sites can. Now 3 for the clause-derived population, 2 for authored
   parameters, with the authored shape named as what stands between it and 3.
   The row's repair sentence also records that the roster passed is a UNION.

6. BOTH HALVES OF THE BRAND DELEGATION ARE ASSERTED. The witness asserted only
   that this relation abstains. If the brand wall regressed or stopped reaching
   the argument path, that stayed green while the horizontal pair was admitted
   in silence by both relations -- green for a reason other than the one it
   claims, which is the defect this whole lane exists to remove. The wall's
   TypeMismatch firing is now a conjunct of the same witness, against the same
   exact-match partner so the corpus term cancels.

The whole required floor is green locally with all six in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
# Conflicts:
#	dag/gunbc/recurring_failure_mode/roster.dag
#	docs/design-failure-modes.md
The merge took main's side of docs/design-failure-modes.md, which is the
declared repair route for that path's merge driver, but the route leaves the
projection carrying main's rows and not this branch's until something re-derives
it. So the row was declared and rostered and NOT projected -- the generated
artifact drift the build lane refuses.

Verified by CONTENT in both directions rather than by exit code: every row main
carries is present, and this branch's row is present. That check is the one that
matters after a MERGE, not only after a regeneration -- this is the second
generated artifact on this branch lost that way (the stage0 mirrors were the
first), and in both cases the loss was invisible in the merge itself: no
conflict markers, no complaint, a file quietly holding the other side.

The stage0 mirrors were re-derived on the merged tree in the same step and
agree by content, so they are unchanged here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md lookup_miss_read_as_positive_classification
Ledger-Repair-Judged: docs/design-rung-drops.md
Two review findings, both correct, both falsifying something this diff asserted
about itself.

EVERY ARM IS NOW A DIFFERENCE, INCLUDING THE ZEROES AND THE ONES. The
nominal_opaque arm asserted `DeclaredTypeNotInhabited == 1` -- a merge-blocking
equality against a population the census draws from the fixture's whole import
closure, with a bare literal recording today's tree. This file's own standing
note says why that cannot be an oracle, seventy lines above the arm that did it.
Re-authored against a Secret-free partner differing only on the nominal_opaque
axis. Re-checking the others against the same question rather than trusting an
earlier pass found the same shape in the `== 0` conjuncts, which look
fixture-local and are not: refusal counts happen to be zero across that closure
today, which is the coincidence that makes the absolute form look safe and is
not a time-stable fact. All five arms are now differences between two fixtures.
(Review 62112, claude/opus.)

A CHAIN WITH ANY UNIDENTIFIED LINK IS DISCARDED, NOT COMPACTED. The annotation
claimed dropping a link "can only turn an admission into a decline -- the
conservative direction, never a new admission." THAT CLAIM IS FALSE, and the
counterexample is the HEAD: drop the declared side's head and its BASE is
promoted to head, so the produced chain contains it, the widening arm fires, and
a value is admitted at a strictly narrower declared refinement it was never
shown to inhabit. Compaction is not the conservative direction; it is the
widening one, one link up -- the same move the sibling paragraph forbids by
name. The function now returns the empty chain unless every link is identified,
and the annotation states what is true rather than what was convenient.
(Review 62130, claude/opus.)

The whole required floor is green locally with both in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
…degrading

what you compare

Both arms in this relation reached for a weaker comparison when the precise one
was unavailable -- one lowering the GRAIN to a spelling, one shortening the
CHAIN past a link it could not identify -- and both were assessed as
conservative in general while reversing direction at a particular position. That
is one principle with two instances, not two patches, and the file now says it
once above the function that carries both rather than twice inside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head whole-diff review of 774a048: REQUEST_CHANGES.

  1. BLOCKER — the v1-freeze admission argument does not discharge PublicSurfaceGrowth, which the standing says DOMINATES purpose admission. The authority explicitly defines PublicSurfaceGrowth as a diff over the emitted seed's exported declarations. This head adds exported declarations in the public v1_compiler_infer module: refinement_chain_link_identities, refinement_chain_names, the new RefinementInhabitance enum, refinement_inhabitance, plus new variants on the public InhabitanceUndecidableReason. The generated mirror renders these as pub. Refusal-set identity is good evidence against NewLanguageBehavior; it is not evidence that the exported surface did not grow. Either restructure the integration closure so it does not grow the frozen seed's exported declaration surface, or obtain an authority ruling that actually changes/discharges this dominating refusal. The current purpose/self-host measurements cannot override it.

  2. BLOCKER — the refinement identity repair still loses exact declaration identity before the new site-key comparison, and the 'all links identified' check does not actually prove declaration resolution. refinement_chain_names starts by taking authored_name_at(n) and calling lookup_type_by_name, even though lookup_type_for is the existing node-aware authority and uses node.ident first. Thus a node that already carries identity can be rebound through ambient bare-name/homonym resolution before declaration-site IDs are compared. Inside the chain, where_refinement_chain explicitly falls back to the raw base node when lookup_type_for misses; refinement_chain_link_identities then calls where_refinement_brand_declaration_site and treats the presence of ident_span as proof of identity. But ident_span is not declaration provenance — default_ident_span gives any named node an ident span. An unresolved reference can therefore be assigned a file#name key and survive the completeness check. This is the same class the comments say is forbidden: absence of identity is being repaired by degrading what is compared. Resolve the head and every chain link through an exact declaration-aware carrier (or make the whole relation unavailable on any resolution miss); do not turn reference spelling/location into declaration identity.

Gate note: this head is no longer an integration candidate even apart from the findings. Its base is 100b6e7..., while current main is f69e0a3... (9 commits ahead), including a docs/design-failure-modes projection change. Per exact-head doctrine, merge current main, re-project/regenerate, re-derive the matrix/refusal identity set, and rerun CI before requesting another verdict. Any new head is a new review subject.

Blocker 2 of the REQUEST_CHANGES on 774a048, verified in source before
accepting and not argued.

THE KEY WAS NOT AN IDENTITY. where_refinement_chain falls back to the raw base
node on a lookup miss, and where_refinement_brand_declaration_site keys on
ident_span -- which default_ident_span sets from the node's OWN name_span at
construction. So on a fallback link the key was a REFERENCE-site key, wrong in
both directions at once: two distinct declarations referenced from one file key
EQUAL, one declaration referenced from two files keys UNEQUAL. A key that both
merges distinct things and splits identical ones is not an identity, and there
is no tightening that repairs one, so the fallback is gone rather than sharpened.

THE ENTRY POINT RESOLVED BY SPELLING. refinement_chain_names took
authored_name_at and then looked the type up BY NAME, so a node already carrying
identity was rebound by spelling at the head of a relation whose whole subject
is spelling collision. Both it and every link now resolve through
lookup_type_for, the node-aware authority that matches node.ident first, and the
site is taken from the resolved DECLARATION.

THE COMPLETENESS CHECK MEASURED THE WRONG THING. The count equality compared
ident_span PRESENCE, which every named node has, so it proved the links were
NAMED and could not fail for the reason it existed. It now compares RESOLVED
links, and any miss discards the whole chain: the pair is not this relation's
subject. Refuse, never widen -- no falling through to the raw node.

Also drops the where-refinement blinding of the nominal-product arm, which was
unnecessary: the refinement arm runs first, so widening is admitted before that
arm is consulted, and every control stays green without it. It is NOT claimed to
repair the disjoint-pair case from review 62172 -- measured on both sides, main's
compiler does not refuse that pair either, so that defect is pre-existing and is
being enrolled separately rather than attributed here.

The whole required floor is green locally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
@briansrls
briansrls merged commit 7fb62e6 into main Sep 8, 2026
4 checks passed
@briansrls
briansrls deleted the session/smart-ant-528 branch September 8, 2026 02:30
briansrls pushed a commit that referenced this pull request Sep 8, 2026
Four conflicts, ALL generated mirrors: v1_compiler_infer.rs,
v1_compiler_infer_lookup.rs, lib.rs, emitted_population.rs. #10743 regenerated
the same mirrors this branch regenerates, and #10727/#10737 changed the same
resolver.

Both .dag AUTHORITIES (04_infer, 04_lookup) auto-merged as real content merges
-- that is where the semantics live. The mirrors are NOT hand-resolved: taking
ours here is a bootstrap step only, because a mirror tree that cannot compile
cannot run the producer that would regenerate it. cargo check green at this
point confirms a working producer; the mirrors are re-derived from the merged
authorities in the following commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wNf4iRSUKNtgxmE8qxkww
gunbai-bot Bot pushed a commit that referenced this pull request Sep 8, 2026
REVIEW FINDING 1, CONFIRMED AND IT IS THIS PR'S OWN CLASS. Roster was
175 on main and 174 here: added production_actuation... and silently
dropped decision_surface_truncation (#10712) and
lookup_miss_read_as_positive_classification (#10727), both landed after
the branch point and both with their module files still present. The
triple-dot diff renders it as reorder-plus-two and shows nothing. Found
by the count comparison the PR's own row prescribes, named by an
identity join, and repaired by merging main so the roster is 176 with an
empty join in both directions -- imports and list body checked
separately, since either half can drop alone.

REVIEW FINDING 2, CONFIRMED. The reduction left the launch graph's
imports (32 lines -> 8 in the module, 37 -> 18 in the witness) and an
annotation asserting mint-last/LaunchAdmitted ordering that no longer
has a mint or a launch to range over -- a 4c annotation no Accepted
program reads, so a false one never reds and it drifts to flatter the
change. Annotation replaced with what the module now does, including
why the ordering argument is NOT restated here. Orphan fixtures deleted
(launch_org, launch_net, launch_binding, witness_reserve, both lifecycle
intents, launch_test_slot); witness_reserve's own note was stale too,
still saying production refuses every launch.

Also declares the seal a 3c frontier rather than leaving it implicit:
nothing consumes it, and no witness CAN -- its input is a
sole_constructor snapshot -- so the trigger is named beside it, with the
honest alternative stated.

Three claims pass. Compile shows the 3 pre-existing extdeps/gunbc shell
transport errors and nothing in these files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN
gunbai-bot Bot pushed a commit that referenced this pull request Sep 8, 2026
The row's architecture is unchanged -- placement outside the admitted
roster, no fifth arm, evidence-not-permission framing. These are precision
fixes to what it asserts as historical record, each verified in source
before writing.

1. "THERE IS NO REVIEW" WAS FALSE, AND FALSE WHERE THE ROW COULD LEAST
   AFFORD IT, since the ruling-versus-objection distinction IS the row's
   subject. There WAS a review: the exact-head REQUEST_CHANGES on #10727,
   GitHub review 5135375330, which is what raised the PublicSurfaceGrowth
   objection. What never existed is a RULING answering it, so the merge is
   the only disposition -- and a disposition is not an adjudication.

2. "THIS CHANGE SATISFIED NO TEST" SMUGGLED IN A NEGATIVE ADJUDICATION.
   Asserting the change FAILED the purpose test is itself a ruling, and
   this row exists to say no ruling was made. Restated: the purpose test
   was never adjudicated for this change, so it has no place in a
   population of adjudicated satisfactions. Same placement conclusion.

3. THE MODAL CLAIM IS RESTORED AND NOW STANDS ON TWO INDEPENDENT LEGS,
   verified rather than relayed:
     LEG ONE, the source language cannot ASK -- .dag has no module-private
     declaration concept; authority is v1.compiler.emit_rust's ceiling note
     above rust_scalar_checkpoint_reference_base, whose next-rung trigger
     is "a module-private function boundary in the language".
     LEG TWO, the emission path cannot ANSWER -- v1.compiler.languages
     declares VisibilitySpec with ONE prefix per target and no
     representation of a public/private pair; Rust binds
     KeywordVisibility { prefix: rust_visibility }, rust_visibility is
     "pub ", and rust_visibility_prefix takes NO ARGUMENT.
   The counterexample that looks decisive is recorded and refuted in the
   row so it is not raised again: emit_cli_root_struct's `public: Bool` is
   a bespoke local conditional for one hand-written CLI struct, not the
   declaration path. The 46-mirror census is demoted to observed
   confirmation of the two legs rather than the proof of them.

4. THE EVIDENCE PARAGRAPH UNDERSTATED THE MATRIX. "What moved is which
   non-blocking reason a decline reports" omits the direction carrying the
   repair. The matrix moved BOTH ways: 8425 sites cleared a spurious
   UndecidableGenericFormal decline outright (diagnostic to none), and the
   new relation fires at 4989 introduction sites plus 5 peer chains.
   Reason-to-reason is the third kind, not the only kind. The producer is
   named so the figures are re-derivable rather than quoted.

Verified on this head: projection gate regen clean, and
claim_executor --required-floor verdict=FloorClean, 3549/3549 terminal,
claims_failed=0, unexpected_failures=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF
briansrls pushed a commit that referenced this pull request Sep 8, 2026
* Make an existing floor defect permanently executable and named

A branded where-refinement value (HostIdentity, `NonEmptyStr where brand(...)`)
passed at an ordinary product formal reaches NO refusal at the direct call
argument seam. A value of an unrelated constructor is accepted at a declared
position -- the DESIGN §4b floor sentence, `values inhabit declared types`.

THE MONOTONICITY IS INVERTED, which is what makes it worth its own class. The
two refinement chains share no link, so this is the STRONGER incompatibility;
pairs whose chains merely MEET are at least reported undecidable and are
therefore countable. The unrelated pair matches no arm at all, falls through the
whole procedure, and is admitted. The further apart two things are, the more
likely they are accepted.

THIS IS PRE-EXISTING AND THE ATTRIBUTION IS MEASURED, NOT ASSERTED. The control
was authored while investigating review 62172, which attributed the hole to a
where-refinement exclusion in the nominal-product arm on gunbc#10727. That
attribution does not survive measurement: the same control, over the same
fixtures and the same corpus, run against two compilers differing only in the
three stage0 infer mirrors, returns false under BOTH main's and the candidate's.
Removing the exclusion does not change the result, because the product arm was
not making the refusal the exclusion was thought to suppress. The finding's
CONCLUSION was right and its CAUSE was not.

It therefore lands here rather than in #10727: a review-originated finding moves
to its own PR when its discriminating RED reproduces on main WITHOUT the
reviewed diff. Discovery provenance stays recorded; implementation ownership
follows the demonstrated subject. Self-sufficiency is executed rather than
argued -- on this branch, which is main plus these three files, the floor plans
and executes 3540 claims with verdict_incomplete=0 and the assertion RUNS and
returns false, so it is ExpectAssertionFalse and not a pre-verdict refusal.

Three pieces, no production repair: the discriminating control, its
ExpectAssertionFalse admission, and the failure-mode row. Measured on this head:
verdict=FloorClean, known_red_held 19 -> 20, claims_failed=0.

The row's next-rung trigger is stated at CAPABILITY grain -- the judgment
REFUSES a produced value whose refinement chain is disjoint from the declared
position's. Neither a control existing nor a diagnostic variant being declared
retires it; both would be satisfied while the capability stayed dead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md disjoint_chains_admitted_while_meeting_chains_decline
Ledger-Repair-Judged: docs/design-rung-drops.md

* wip: A/B/C fixes

* Fold the specimen onto its existing class; one closure for both fixtures

Three blockers from GitHub review 5136486384, all verified against the tree
before accepting.

A - THE ORACLE WAS UNSOUND, AND THE CONSEQUENCE WAS A FALSE DISSOLUTION TRIGGER.
The failing fixture imported product.placement_supply and the partner did not,
so the two closure-wide counts were taken over DIFFERENT closures and the shared
term did not cancel by construction -- the assertion was false only because two
unequal closures happened to contribute equally today. That is not flakiness
here: this control is enrolled as an expected RED whose greening is a counted
un-quarantine that DELETES its admission row, so any DeclaredTypeNotInhabited
appearing anywhere in that closure would have greened it for an unrelated reason
and retired the admission while the defect stayed live. Both fixtures now carry
identical imports and identical declarations and differ on exactly one token,
the actual passed. The rule was already stated seventy lines up in the same file.

B - IT IS NOT A NEW CLASS. The mechanism is absence_classifier_default_bucket's
own -- an accepted bucket defined by the ABSENCE of a matching arm, the residual
catching only shapes whose absence pattern differs -- and decisively the SAME
REPAIR closes both: carry the discriminating fact, consume it in an exhaustive
match with no default accepting bucket. Two things sharing a mechanism AND a
repair are one class, and DESIGN section 2 refuses a fresh authority for a
concept that already exists. The differing arity (a relation over a pair rather
than a classifier over a subject) is immaterial: the residual-accepts shape does
not depend on how many subjects the question ranges over. That row already
carries a second specimen in a different authority, filed and not repaired,
which is exactly this situation.

WHAT IS GENUINELY NEW LANDS AS A RECOGNITION RULE ON THAT ROW, because the
inversion is NOT derivable from its existing rule: "a new kind can silently
resemble the default" predicts that UNMODELED kinds land in acceptance and says
nothing about an ORDERING over severity being inverted. Here the escaping
population is not a kind nobody enumerated -- it is present, well defined, known
at authoring time, and exactly the EXTREME of the incompatibility order. WHERE
THE DOMAIN IS ORDERED BY SEVERITY AND THE ARMS COVER ONLY THE NEAR END, THE
ACCEPTED BUCKET IS THE FAR END, SO SAFETY DECREASES MONOTONICALLY WITH
INCOMPATIBILITY.

C - THE DANGLING CITATION IS GONE with the separate module: nothing here cites a
neighbour that is unreachable in the tree it lands in.

And the quadruplicated prose from review 62297 is cut to citations: the receipt
row is the authority, the admission reason carries the admission-local fact, and
the two comment blocks name the row instead of restating it. The one comment
that stays is a fact about the FIXTURE construction, which no row owns.

Measured on this head: verdict=FloorClean, planned=executed=terminal=3550,
known_red_held=20, claims_failed=0, unexpected_failures=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

* wip-blockers

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md absence_classifier_default_bucket
Ledger-Repair-Judged: docs/design-rung-drops.md

* Address the three blockers: identical fixtures, causal fold, final-default trigger

1. THE PAIR ORACLE'S TWO FIXTURES WERE NOT BYTE-IDENTICAL. They differed in
   their module declarations as well as in the argument under test, so the
   difference the oracle measures was not attributable to the argument alone.
   Both strings now declare `module probe_disjoint_refinement_at_product`;
   the ONLY remaining difference is `probe_host_d` vs `probe_box_d` at the
   call. Re-established on the merged head: required-floor verdict=FloorClean,
   3550/3550 terminal, claims_failed=0, known_red_held=20 -- the assertion
   still RUNS and returns false, so ExpectAssertionFalse still holds.

2. THE FOLD ARGUMENT INVOKED A UNIVERSAL LAW IT DOES NOT HAVE. "Two things
   that share a mechanism and a repair are one class" is not true in general
   and was doing the work the cause should do. Deleted. The fold is now
   grounded on the SHARED CAUSAL PREDICATE: an accepting residual defined by
   "no explicit arm matched", so a member of the declared subject population
   that no arm dispositions silently lands in acceptance. Arity changes none
   of that. A shared repair is evidence ABOUT the cause, never the argument.

   The severity-inversion recognition rule is likewise softened to a
   CONDITIONAL CONSEQUENCE and explicitly not a monotonicity theorem: when
   the enumerated arms cover the near end of a severity order, the far end is
   what falls through -- a consequence of which cases were written, not a law
   about incompatibility, and a tell to check for rather than a property to
   rely on.

3. THE DISSOLUTION TRIGGER WAS BROADER THAN THE DEFECT. "Refuses every pair
   whose refinement chains are disjoint" would license an OVER-REFUSING
   repair, since disjointness is not itself evidence of incompatibility. It
   is re-phrased at the FINAL-DEFAULT grain -- where `Absent => Inhabits`
   fires -- in both carriers:

     Inhabits for an identity-established pair requires a NAMED POSITIVE
     COMPATIBILITY AUTHORITY -- there is no implicit accepting residual --
     sufficient for the HostIdentity-at-Box specimen to refuse.

   Each carrier states why the obvious phrasing is a false trigger, so the
   grain is not re-lost by the next reader.

docs/design-failure-modes.md is the deterministic projection of the edited
row plus main's roster catch-up; it was regenerated, not hand-merged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

* Regenerate the failure-mode projection from the merged authorities

The merge left main's copy of this generated artifact; the projection is
re-derived from the merged .dag rows rather than spliced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

* Integrate main 5e24cc9: resolve two append-rosters as unions, verified both ways

Two of the three overlapping files are APPEND-ROSTERS, where a take-one-side
resolution loses the other lane's row without conflicting, without breaking
the parse, and without reddening anything either lane runs. So both are
resolved as unions and then verified BY CONTENT IN BOTH DIRECTIONS -- the
thing most likely to vanish here is somebody else's entry, and its absence
is silent.

  `dag/gunbc/explicit_witness_admission.dag` -- main deleted the
  `green_control_sanctioned_reader_body_not_flagged` probe (its dissolution
  fired) in the same region this branch added its own. Resolved by taking
  main's deletion AND keeping this branch's probe. VERIFIED: the probe
  roster is exactly main's plus one entry, `w_a_branded_refinement_at_an_
  unrelated_product_formal_still_refuses`, with nothing removed.

  `src/v2/workflow/floor_expected_red.dag` -- main dropped
  `floor_expected_red_chunk_22` from the aggregator AND deleted its
  function, while this branch had added a chunk. Taking either side alone
  would have either resurrected a call to a function that no longer exists
  or dropped this branch's chunk. The aggregator is rebuilt from MAIN's
  chunk list with this branch's chunk inserted. VERIFIED: expected-red
  identities go 97 -> 98, exactly one addition and no removals, and every
  chunk function referenced by the aggregator is defined.

  `docs/design-failure-modes.md` is the generated projection and was
  regenerated from the merged authorities rather than resolved.

`dag/gunbc/recurring_failure_mode/absence_classifier_default_bucket.dag` is
NOT in the overlap -- main did not touch the fold target, so the specimen
and its recognition rule are uncontested.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

* Integrate main b6f7987: union the admission roster, regenerate the projection

Two overlapping files, resolved by kind rather than by text.

`dag/gunbc/explicit_witness_admission.dag` -- the same append-roster that
nearly broke this PR on the previous integration. Main added several probes
in the region this branch added one to. Resolved as main's current state
PLUS this branch's entry, and VERIFIED BY SET DIFFERENCE IN BOTH
DIRECTIONS rather than by count, because a count passes when one entry is
swapped for another: the roster is exactly main's plus
`w_a_branded_refinement_at_an_unrelated_product_formal_still_refuses`,
with nothing removed.

`docs/design-failure-modes.md` is a GENERATED projection and was not
text-merged. A three-way merge has no useful granularity there -- a
failure-mode row projects as one very long line, so any conflict is a
whole-row conflict and the merge can emit bytes no generator would
produce. Resolved by taking the side that makes the AUTHORITIES correct and
then regenerating.

Verified on the merged tree:
  parse sweep -- 5231 files parse-clean;
  fixed point -- regenerator run leaves no diff;
  authored-delta arrival -- every sentence the authority gained is present.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
briansrls pushed a commit that referenced this pull request Sep 8, 2026
…#10826)

* Record #10727 as an unadjudicated merged instance, not an admission

gunbc#10727 merged on 2026-09-08 as 7fb62e6 carrying five new
exported declarations in the emitted v1 seed while PublicSurfaceGrowth
remained a dominating refusal in v1_seed_standing and the boundary
question was open and escalated.

The row states, in its own words, that the merge settles the disposition
of that exact change and NOT the semantics of the PublicSurfaceGrowth
class. Three placement constraints hold deliberately:

  - it is NOT under "what this standing has admitted so far", because
    that roster records changes that SATISFIED the purpose test;
  - it mints NO fifth MaintenanceAdmissionInstance arm, which would be
    the self-authored boundary change this standing warns against;
  - it is NOT filed only elsewhere, because the dangerous future
    inference happens while somebody is reading THIS authority.

docs/design-failure-modes.md carries the deterministic projection of
main's decision_surface_truncation row; it is catch-up, not new content.

Verified on this head: docs projection gate regen clean, and
claim_executor --required-floor verdict=FloorClean, 3549/3549 terminal,
claims_failed=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

* Correct four historical claims in the unadjudicated-merged-instance row

The row's architecture is unchanged -- placement outside the admitted
roster, no fifth arm, evidence-not-permission framing. These are precision
fixes to what it asserts as historical record, each verified in source
before writing.

1. "THERE IS NO REVIEW" WAS FALSE, AND FALSE WHERE THE ROW COULD LEAST
   AFFORD IT, since the ruling-versus-objection distinction IS the row's
   subject. There WAS a review: the exact-head REQUEST_CHANGES on #10727,
   GitHub review 5135375330, which is what raised the PublicSurfaceGrowth
   objection. What never existed is a RULING answering it, so the merge is
   the only disposition -- and a disposition is not an adjudication.

2. "THIS CHANGE SATISFIED NO TEST" SMUGGLED IN A NEGATIVE ADJUDICATION.
   Asserting the change FAILED the purpose test is itself a ruling, and
   this row exists to say no ruling was made. Restated: the purpose test
   was never adjudicated for this change, so it has no place in a
   population of adjudicated satisfactions. Same placement conclusion.

3. THE MODAL CLAIM IS RESTORED AND NOW STANDS ON TWO INDEPENDENT LEGS,
   verified rather than relayed:
     LEG ONE, the source language cannot ASK -- .dag has no module-private
     declaration concept; authority is v1.compiler.emit_rust's ceiling note
     above rust_scalar_checkpoint_reference_base, whose next-rung trigger
     is "a module-private function boundary in the language".
     LEG TWO, the emission path cannot ANSWER -- v1.compiler.languages
     declares VisibilitySpec with ONE prefix per target and no
     representation of a public/private pair; Rust binds
     KeywordVisibility { prefix: rust_visibility }, rust_visibility is
     "pub ", and rust_visibility_prefix takes NO ARGUMENT.
   The counterexample that looks decisive is recorded and refuted in the
   row so it is not raised again: emit_cli_root_struct's `public: Bool` is
   a bespoke local conditional for one hand-written CLI struct, not the
   declaration path. The 46-mirror census is demoted to observed
   confirmation of the two legs rather than the proof of them.

4. THE EVIDENCE PARAGRAPH UNDERSTATED THE MATRIX. "What moved is which
   non-blocking reason a decline reports" omits the direction carrying the
   repair. The matrix moved BOTH ways: 8425 sites cleared a spurious
   UndecidableGenericFormal decline outright (diagnostic to none), and the
   new relation fires at 4989 introduction sites plus 5 peer chains.
   Reason-to-reason is the third kind, not the only kind. The producer is
   named so the figures are re-derivable rather than quoted.

Verified on this head: projection gate regen clean, and
claim_executor --required-floor verdict=FloorClean, 3549/3549 terminal,
claims_failed=0, unexpected_failures=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

* Move the disposition into a typed carrier and name the instruments

Review 62399 raised two findings on the String note row; both are correct
against the authority docs and both are fixed rather than argued.

FINDING 1 -- DANGLING DECLARATION (§3c) PLUS UNCLASSIFIED PROSE (§4c).
Verified: `git grep` returned exactly one hit for the note row, its own
definition, and the four sibling `*_note: String` rows in this file are
equally unread. §4c is explicit that a ruling, citation, status or count
belongs in a typed carrier and that a String declaration whose sole purpose
is commentary is misplaced data -- and this row was precisely a
ruling-status record plus a citation plus a census. Prevalence is debt, not
precedent; a fifth would have been a third one added by me.

  The row is now `UnadjudicatedDisposition = MergedWhileObjectionStood`,
  carrying the change, the merge SHA, the objecting review and the refusal
  class at issue as FIELDS. It is a separate type and NOT an arm of
  `MaintenanceAdmissionInstance`, for the reason the previous draft already
  argued and which the reviewer agreed with: that vocabulary records shapes
  the purpose test has been satisfied BY, and an unadjudicated merge
  satisfied nothing and failed nothing, so an arm there would mint the
  ruling that was asked for and not given.

  §3c is ANSWERED rather than left dangling, and answered honestly: the
  consuming route is the one that consumes `v1_seed_standing` itself --
  DESIGN states this authority's rung plainly, "the vocabulary is consumed
  by review diligence, not by any gate" -- so a row here reaches its
  consumer when a reviewer classifies a proposed v1 change against this
  standing. That is the declared route, not a claim that a fold reads it.

  The irreducible rationale moves to a module-scope `//` annotation, which
  is §4c's own quarantine channel for prose and the shape this file already
  uses for its operator rulings.

FINDING 2 -- TRANSCRIBED MEASUREMENT (§6). The census was a copied count.
It is now stated as a PREDICATE anyone can re-run -- over the generated
mirrors, count `pub fn` against bare `fn` and `pub` types against bare --
with the reading marked as a DATED OBSERVATION on main after 7fb62e6
and explicitly not the proof of the claim. Its scope limit survives
re-derivation: the same predicate over `src/v1/stage0/src` as a whole is
FALSE. The behavioural matrix likewise now states what it ESTABLISHED --
refusal set identical by identity, everything else non-blocking and moving
in both directions -- and names the instrument instead of leaning on its
counts.

Verified: projection gate regen clean on this head.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

* Consume CommitSha, carry the full 40-hex head, and fix the parse break

REVIEW 62407 -- the disposition row declined to consume an authority that
already exists. `std.types` declares `CommitSha`, named by every Git-head
consumer in the tree, and `merged_as: String` was a second name for that
one concept (DESIGN §3) on the one declaration in this file that is
structured rather than prose. It is now `merged_as: CommitSha`, with the
import.

The value is also widened from the abbreviated `7fb62e6e5e7` to the full
40-hex head. `CommitSha` is presently an unvalidated alias, but `std.types`
homes a located syntax wall beside it -- lowercase 40-hex for an externally
supplied Git head -- so an abbreviated value would have been written to be
refused the day that wall becomes a constructor.

The two remaining `String` leaves are left as they are, deliberately and
for the reason the review itself gives: `change` is a repo-plus-number and
`objecting_review` is a review reference, and no carrier for either shape
exists in `dag/` yet, so minting one here would be the §2 re-invention the
`CommitSha` fix exists to avoid. `CommitSha` is different -- it exists.

THE PARSE BREAK, and it is why the required floor refused on 828d3ea. In
`.dag` a DATA expression may not begin on the line after `=`; the
constructor now sits on the equals line, matching `v1_seed_standing` in
this same file. The six `type X =` declarations that break to the next line
are NOT the same shape and are correct as written.

WHAT THIS COST AND WHAT I TAKE FROM IT: my local `--required-floor` run
returned FloorClean on the broken form, so the local instrument did not
discriminate on this class while CI's did. "The edit is in the file" and
"the file parses" are two different facts, and only the second is
established by running the parser that CI runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

* One fact, one spelling: make both SHA references in the authority full

DESIGN §3 -- the row carried the merge head twice, forty lines apart, once
as the full 40-hex `merged_as` value and once abbreviated inside the
producers annotation. One fact with two spellings inside a single
authority. Both are now the full form.

Also carries the merge of origin/main 5e24cc9; the authority subject is
byte-preserved across it (identical blob).

VERIFIED WITH THE INSTRUMENT THAT ACTUALLY REACHES THIS SUBJECT:
`claim_executor --required-ci --source-root dag --source-root src/v2
--required-lane witnesses` -- exit 0, zero adjudication BLOCKING. The
`--required-floor` reading this PR previously cited is VACUOUS here for
the reason this branch's sibling specimen establishes: nothing imports
`gunbc.v1_maintenance_standing`, so the floor never parses it and its green
was never about this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuB8wHjijaFwNoyzm78stF

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Sep 9, 2026
…#10674)

* Compose the micro-VM launch plan, and mint the credential last

Four stages have to hold before a guest may start: the fabric cell must be
owned, the VM config must resolve, the admission must pass, and the App
credential must carry organization self-hosted-runners write. This composes them
in that order and returns one typed plan.

The order is load-bearing rather than stylistic. Every stage but the last is
pure and free to refuse; a JIT mint is not, because it creates a runner identity
at GitHub. A refusal after a mint leaves that identity registered against the
organization for a VM that will never start, nothing on the host knows to delete
it, and orphaned registrations are reconcilable only by name. So the mint runs
last, and its authorized value is reachable only from inside LaunchAdmitted --
a caller cannot hold a mint authorization for a launch that was refused.

Every admission arm is matched where the launch rules on it rather than through
a helper over RunnerMicroVmAdmission. Such a helper needs an arm for the
admitted case that cannot occur there, and the only thing to put in it is a
fabricated refusal, in the one place nothing can observe it.

plan_microvm_launch_of_binding carries the decisions and takes a binding a
witness can build; plan_microvm_launch is the five-line projection over the
sealed RequiredBuildCellSnapshot. Putting the ordering behind the seal would
have made it unwitnessable, which is how the previous PR ended up with an
annotation claiming coverage that did not exist.

Refusal arms stay separate rather than collapsing into one cause string: the
cell belongs to fabric_required_build_cell, the size to runner_slot_allocation,
the topology to runner_unit, the credential to auth.github_credential, and
collapsing them sends an operator to the wrong module.

Credential fixtures are imported from test.claim.github_app_registry rather than
rebuilt, so there is one answer to what an installation holds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* temp: diagnostic arm probes for the launch fixture

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Take the VMM reserve as an input, and state that production admits no guest

The launch plan read gunbc_runner_microvm_realization_reserve() inside the
composition, which made the admitted arm unreachable from any caller.
Production's reserve is ReserveUnmodeled -- deliberately, because no VMM
resident footprint is a cited quantity in extdeps.virtualization.firecracker and
inventing one would be a fabricated plausible output -- so every launch refuses
at MemoryFitUnresolved and the mint stage below it was dead code no input could
reach.

The lane caught this: the positive control asserted LaunchAdmitted, a state no
input can produce. That is the same decoration class this branch documented for
members_owned, this time written into a test rather than a production path, and
it would have shipped as coverage for an ordering nothing could exercise.

The reserve is now a parameter. The fit judgment is decided by the caller that
owns the quantity, the ordering below becomes reachable and therefore
witnessable, and production keeps refusing at the entry point rather than by
accident inside a composition.

Adds the control that says what is true today: with production's own reserve,
every launch refuses, so NO CI JOB RUNS IN A MICRO-VM until that quantity is
cited or measured and declared. It goes red the day the reserve becomes
declared, which is when the launch path becomes live and the claim has to be
re-read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Observe whether a runner host can measure a VMM's footprint

The micro-VM path admits no guest because the realization reserve is
ReserveUnmodeled: no VMM resident footprint is a cited quantity, and inventing
one would be a fabricated plausible output. The quantity has to be OBSERVED on a
host before it can be declared, so which instrument exists is a host readiness
fact in the same way /dev/kvm is -- a host that boots a guest but cannot report
what the VMM cost cannot retire the reserve.

Reports GNU time, cgroup v2 memory.peak, and the systemd version line, in the
receipt the existing microvm_host_converge mode already prints. No new module
and no new workflow job: this had to have a consumer the moment it existed.

The instruments are named rather than ranked and nothing here picks one. Each
reports the same quantity exactly, and which is available is a property of the
host's systemd and kernel, so selecting among them belongs with the measurement.
The systemd version is read rather than inferred because MemoryPeak is
version-gated at v254.

Sampling /proc/<pid>/status VmHWM is deliberately not among them: it needs the
process alive, so a guest that powers itself off can be missed, and a missed
sample under-reports -- the wrong direction for a value that becomes a safety
reserve.

Readiness deliberately does not move with the instruments, with a control for
it: wiring them into firecracker_host_ready would have taken every host
not-ready the moment this field landed, to answer a measurement question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Import systemctl_version_command, which no local gate had checked

The fleet-converge dispatch died on srv2 with NoSuchFunction while the whole
witnesses lane had reported RC=0 and FloorClean over the same tree.

Two reasons stack and either alone is enough. runner_microvm_host_ready sits
declined_outside_gate_closure, so no CI check compiles it. And claims evaluate
only the functions they call, so the witnesses added beside this observer cover
its pure functions while the func body is never evaluated -- wet bodies run only
on a host. A green floor is not evidence that a wet function resolves.

gunbc compile --entry DOES catch it, confirmed by running the red arm with the
mutation verified applied first. So the gate existed and was not run; it is now
run before any dispatch that executes a .dag entry on a host.

Worth recording that this is the opposite blindness from the indented-annotation
refusal earlier on this branch, where the lane's parse phase caught what
compile --entry missed. Neither instrument dominates; a touched module needs both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Model the VMM footprint probe, sampled across guest sizes

The micro-VM path refuses every launch because the realization reserve is
ReserveUnmodeled. This is the observation that could retire it, and it produces a
RELATIONSHIP OVER SIZES rather than a constant, because the model's own reasoning
is that the overhead is not constant.

Sampling several sizes is not thoroughness, it is the whole experiment. Guest RAM
is lazily faulted, so a page joins the VMM's resident set only when the guest
touches it, and a guest that boots and powers off touches a small and roughly
size-independent amount. One reading at one size therefore measures the workload,
not the size. A peak that stays flat as declared size rises says the declared size
is not committed; a peak that tracks it says it is. Those are different worlds for
an admission rule and one sample cannot tell them apart.

Stated in the module and not only here: this does NOT establish a worst case. That
is what a guest costs when it USES its declared memory, which needs a guest
workload that touches all of it. Reporting these numbers as if they bounded that
case would be the fabricated plausible output the reserve refusal exists to
prevent, arriving by way of a measurement instead of a guess.

The instruments were chosen on evidence rather than assumption: the srv2 receipt
reports gnu_time present, systemd 255 so MemoryPeak is available, and cgroup
memory.peak absent. GNU time's %M is a kernel-maintained high-water mark, which is
why it was preferred over sampling VmHWM -- sampling needs the process alive and
under-reports a subject that exits on its own, the wrong direction for a reserve.

The span check carries its own negative arm as a fixture rather than deferring to a
mutation run: the collapses it exists to catch -- one size, two adjacent, three
clustered -- are asserted to fail on every run. The third is the one reasoning
alone would miss, since count >= 3 passes it and only the span requirement does not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Convert mebibytes to bytes explicitly; pin the sizes in absolutes

The declared sample sizes were 512..4096 BYTES, not mebibytes. Assigning
mebibyte(count: N) into a ByteSize field is accepted and does not convert -- the
magnitude rides across unchanged -- and std.measure mebibyte_to_byte_size is the
conversion that was available and unused.

The type system stopped the READ and permitted the WRITE: mebibyte_count on a
widened value is a hard error, so the obvious repair is to switch to
byte_size_count, which compiles clean and relabels the same number as bytes. That
repair is what I made earlier on this branch and reported as a units win. It was
not one.

WHY NOTHING ELSE CAUGHT IT, which is the part worth fixing rather than the typo.
The span control asserts largest >= smallest * 4 over the same list. A uniform
unit error preserves every ratio, so it passed identically on bytes and on
mebibytes; only the absolute assertion in the render test went red, and that one
covers the witness's own fixtures rather than the list production samples.

So this also pins the production sizes in absolute bytes. The literals are the
specification -- these are the sizes the probe declares it will sample -- so the
control is a controlled fixture rather than a measurement copied out of the tree.

General rule, recorded because it generalises past this file: when a suite tests a
RELATIONSHIP -- ratio, ordering, spread, delta -- at least one control must pin an
ABSOLUTE, or a scale error is invisible to the whole suite.

Also enrolls the empty-receipt control permanently rather than as a one-off, per
4b(4): footprint_receipt_is_complete folds with init: true, so an empty outcome
list is vacuously complete and only the count guard beside it refuses. That guard
is load-bearing and had no control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Convert the one mebibyte site a digits-only regex could not match

The previous commit claimed to fix the unit bug and did not. It converted with a
pattern requiring digits, so every literal call site was repaired while
mebibyte(count: mib) in the sampled() helper -- an identifier, not a literal --
was silently skipped. That helper is exactly what the failing render claim drives,
so the fix looked complete, compiled clean, and left the same claim red.

A mechanical edit that matches most of its population and misses the rest is
indistinguishable from a complete one at the diff, and the widening it failed to
remove is legal, so nothing local objected. What found it was running the lane
before pushing rather than reporting the fix from the edit.

So the check is now over the POPULATION rather than the pattern: every mebibyte(
in both files is wrapped in mebibyte_to_byte_size, verified by a grep whose
expected output is empty, not by trusting the substitution's own reach.

The absolute control added in the previous commit is what isolated this. It passed
while the render claim failed, which said conversion works and the fault is in the
render path -- without it the two hypotheses were indistinguishable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Re-read the launch control now that the reserve is declared

production_refuses_every_launch_while_the_vmm_reserve_is_unmodeled asserted that
every launch refused at MemoryFitUnresolved, and it said in its own text that it
would turn RED the day the reserve became declared -- at which point the launch
path becomes live and the control has to be re-read rather than silently starting
to pass a guest through.

#10657 was that day. It set the allowance aside and made the reserve
ReserveDeclared, so production now reaches an admitted guest and the assertion is
false. The control was not broken; it fired. This is the re-reading it demanded.

The red was only visible on the PR merge ref: my branch alone still has the
unmodeled reserve, so both local instruments greened while CI -- which builds
branch-merged-with-main -- refused. Running an instrument against a tree that
lacks the change under test measures the wrong subject no matter how correct the
instrument is, and a green from it looks exactly like a green from the right one.

What replaces the row is the same question asked of the live path, in three parts.
The admission control pins that the guest admitted is the DECLARED size rather
than any default. The refusal control keeps that from being satisfied by a fit
judgment that answers yes to everything: a reserve larger than the cell must admit
no guest of any size. And the original discriminating RED stays enrolled with an
explicitly unmodeled reserve -- 4b(4) keeps a class's red executing after its wall
lands, and an unresolved fit is exactly what the launch path must never pass a
guest through, whoever the caller is.

The arithmetic is deliberately NOT re-derived in the witness. guest_memory_fits
already carries the comparison, and recomputing it here would be a second
representation of one constraint, satisfiable by editing the test while the
production path still lied.

Both new controls were verified by mutation rather than by reading: shrinking the
oversized reserve and expecting a size production does not declare turn each of
them red, while the untouched third stays green -- without that last one a run
that failed everything would have looked like proof.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Require the execution grant, so the micro-VM is a realization under the fabric

runner_microvm_launch_plan composed a launch straight from a cell binding and
started a VMM. gunbc.fabric_ci_program FCI-3 owns that question -- what causes the
target process -- and its exit predicate is that a persistent executor re-reads
the committed Grant before starting, so an absent, corrupt or expired Grant yields
ZERO target processes. FCI-1's non-goals name Work-process-start and
ExecutionGrant explicitly; FCI-2's name process-start. The module therefore did
FCI-3's job while skipping FCI-2, which is the second execution system ROADMAP
forbids when it says the fabric is four consumers of one contract.

That is also why nothing consumed the module. The correct consumer is the FCI-3
executor, and it could not consume a launch whose signature omitted the grant --
so writing any caller would have cemented the bypass rather than closing it.

The grant is now a REQUIRED PARAMETER rather than something this module looks up.
The absent case has no constructor, so it is unwritable rather than validated; the
fence check against the issuing account covers corrupt and expired, because a
grant whose lease generation no longer admits is a superseded authorization.
Authorization is the first stage: nothing starts without it whatever the cell's
state, and a control pins that ordering by presenting an unowned cell and
requiring the grant refusal to still win.

THE CONTROLS CAUGHT A BUG THAT PASSED THEM. issue_grant returns the account
carrying the recorded lease, and the first version fenced against the PRE-issuance
account -- so every launch refused, and both grant controls passed against a
mechanism that was refusing everything. Only the seven positive claims failing
revealed it. The grant and its committed account are now one IssuedAuthorization
value, because splitting them is what made the error writable.

Verified by mutation in PRODUCTION rather than in the test: neutering the fence
check turns both grant controls red and leaves the other seven green. No claim
asserts the absent-grant case, because its RED is unauthorable once the parameter
is required -- that check would be a 4b decoration, permanently green and cited as
coverage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Move the VMM footprint measurement to its own branch

The host-readiness observation and the footprint probe answer a different question
than the launch plan does, and they stand alone: no reference to the launch plan,
the execution grant or the mint. They are now PR #10806 off main.

This branch keeps one subject -- what causes a micro-VM to start, and under whose
authorization -- so its title is the one squash-merge writes into main's history.

The extdeps rows move with the code that needs them: gnu_time_max_rss_command and
systemctl_version_command are admitted for the probe, so they belong to the probe's
branch and are reverted here rather than left admitting callers that no longer exist
on this branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Join the grant to the reservation it authorizes, and split planning from authorization

Requiring an ExecutionGrant parameter closed the absent-grant case and nothing
else. The grant and the cell binding arrived as independent arguments and the only
thing read off the grant was its money reservation, so a grant issued for
execution A composed with the reserved cell for execution B was ADMITTED whenever
the supplied account fenced A. That is worse than no grant: its presence reads as
authorization while authorizing nothing about the subject. Found in review; the
PR text claiming this bound the launch under the fabric contract overclaimed.

MicroVmExecutionBinding is sole_constructor and reachable only through
seal_execution_binding, which refuses unless Work, Demand and Offer all agree
between the grant and the reservation. The mismatched pair now has no constructor
rather than being caught by a check inside the planner.

THE RULE IS FACTORED OUT OF THE SEAL SO ITS RED IS AUTHORABLE.
RequiredBuildCellSnapshot is sole_constructor precisely so a caller cannot
manufacture a reservation, which also means no witness outside its module can
build a DISAGREEING one -- a control aimed at the seal could never construct the
input it exists to catch, so it would be permanently green and cited as coverage.
Exposing a snapshot builder for tests would punch the hole the seal exists to
close, the tradeoff runner_microvm_attempt's own witness already records. So
grant_authorizes_reservation takes the coordinates, where a disagreeing pair IS
constructible, and the seal reads them off the snapshot and delegates to it.

PLANNING NO LONGER PRETENDS TO BE AUTHORIZATION.
plan_microvm_launch_of_binding takes no grant at all, because cell ownership, VM
config, admission and the mint are not authorization questions and a signature
that accepted a grant while ignoring its subject is what created the opening.
Production reaches it only through the sealed value, so the gate cannot be routed
around; witnesses drive each level where that level's red is writable.

WHAT THIS STILL DOES NOT ESTABLISH, recorded because the type name would read as
more. ExecutionGrant is an ordinary constructible record, so this proves AGREEMENT
between a presented grant and a presented reservation, not that fabric issuance
durably committed either. The unforgeable form is a persistent executor re-reading
the committed grant from durable storage after the submitter terminates, which is
FCI-3's own exit predicate and the next slice.

Ten claims pass. The substitution control was verified by removing ONLY the Work
join in production: it reds alone while the fence and positive controls stay
green, so it is bound to this defect rather than to a rule that refuses broadly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Reduce to the authorization rule, and file what the two homes cost

The launch plan half is deleted. Not because it was unconsumed -- because it was
REDUNDANT. runner_microvm_witness_test already carries
a_reserve_larger_than_the_whole_envelope_admits_no_guest,
the_production_reserve_is_the_declared_allowance,
the_production_reserve_lets_the_declared_configuration_reach_admission_under_authored_topology,
orphanable_unit_refuses_the_microvm_migration,
shared_slot_directory_credential_is_refused and the whole guest-memory boundary
family; cell ownership is in runner_microvm_attempt_witness_test and the
credential join in the jit mint witness. Deleting the seven planning claims
removes NO coverage. I re-derived them because this branch predated #10657 and I
never read the neighbouring witness, which is the same root as a roster edit on
this branch that would have dropped ten rows other lanes had landed.

What survives is the part nothing else answers. Grant and cell binding arrived as
independent arguments with only the money reservation read, so a grant issued for
execution A authorized execution B whenever the account fenced A -- the grant's
presence read as authorization while authorizing nothing about the subject.
grant_authorizes_reservation now requires Work, Demand and Offer to agree,
MicroVmExecutionBinding is sole_constructor so the mismatched pair has no
constructor, and three controls carry it.

The module is renamed for what it now contains. A module called launch_plan
holding no launch plan is a name promising something it does not carry.

ON THE SEAL, because the question was whether it is a 4b decoration.
SoleConstructorViolation for cross-module construction is already proven by
sole_constructor_violation_outside_module over a synthetic two-module pair, so the
red IS authorable and the seal is not a decoration. A type-specific fixture would
re-prove the general wall in a second home, which is the same redundancy this
commit deletes. Stated honestly: that evidence runs under cargo test -p
v1-compiler --lib, which no CI step executes -- the declared drop
rust_unit_tests_off_the_merge_path -- so the wall is compile-enforced but the
evidence one would cite for it is not on the merge path.

This refusal and #10733's do not duplicate. jitconfig-bytes=0 is about CREDENTIAL
MATERIAL; this is about AUTHORIZATION SUBJECT, and it orders strictly earlier
because it refuses before any drive is built. They compose.

Verified after the reduction rather than before: three claims pass, and removing
ONLY the Work join in the renamed module reds the substitution control alone while
the fence and positive controls stay green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Record the count-first merge check on the two-homes row

The near-misses this branch produced belong with the class they are
nearest to: an append-only carrier losing a row renders as reordering,
so the diff is blind and only the count against the base raises it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Restore two dark roster rows, and cut the launch-plan residue

REVIEW FINDING 1, CONFIRMED AND IT IS THIS PR'S OWN CLASS. Roster was
175 on main and 174 here: added production_actuation... and silently
dropped decision_surface_truncation (#10712) and
lookup_miss_read_as_positive_classification (#10727), both landed after
the branch point and both with their module files still present. The
triple-dot diff renders it as reorder-plus-two and shows nothing. Found
by the count comparison the PR's own row prescribes, named by an
identity join, and repaired by merging main so the roster is 176 with an
empty join in both directions -- imports and list body checked
separately, since either half can drop alone.

REVIEW FINDING 2, CONFIRMED. The reduction left the launch graph's
imports (32 lines -> 8 in the module, 37 -> 18 in the witness) and an
annotation asserting mint-last/LaunchAdmitted ordering that no longer
has a mint or a launch to range over -- a 4c annotation no Accepted
program reads, so a false one never reds and it drifts to flatter the
change. Annotation replaced with what the module now does, including
why the ordering argument is NOT restated here. Orphan fixtures deleted
(launch_org, launch_net, launch_binding, witness_reserve, both lifecycle
intents, launch_test_slot); witness_reserve's own note was stale too,
still saying production refuses every launch.

Also declares the seal a 3c frontier rather than leaving it implicit:
nothing consumes it, and no witness CAN -- its input is a
sole_constructor snapshot -- so the trigger is named beside it, with the
honest alternative stated.

Three claims pass. Compile shows the 3 pre-existing extdeps/gunbc shell
transport errors and nothing in these files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md production_actuation_unmodeled_beside_a_modeled_probe
Ledger-Rows-Repaired: docs/design-failure-modes.md decision_surface_truncation
Ledger-Repair-Judged: docs/design-rung-drops.md

* Give the Demand and Offer joins their own discriminating REDs

Review 62349 is right and it is the sharpest catch on this branch. The
rule has FOUR refusal arms -- fence, Work, Demand, Offer -- and the
witness enrolled REDs for TWO. A rule that dropped the Demand and Offer
checks entirely would have greened every claim in the file, so the join
was reported at a rung its executed evidence does not establish. That is
rung inflation in the module built to stop a substitution.

Two controls added, same shape as the Work case. Arm-to-assertion count
is now 4 and 4.

MUTATION-PROVEN INDIVIDUALLY, because five passing claims do not
establish discrimination -- a control that fails alongside others is not
a discriminating RED:
  remove ONLY the Demand arm -> ONLY a_grant_for_another_demand reds
  remove ONLY the Offer arm  -> ONLY a_grant_for_another_offer reds
Both times the other four stay green.

Also records in the witness WHY these REDs are authorable when the seal's
is not, since the questions sound identical and get opposite answers: the
seal's input is a sole_constructor snapshot, so a disagreeing one has no
constructor a fixture can reach and a control there would be a 4b
decoration; FabricIdentity is an ordinary record, so a disagreeing demand
or offer is writable right here. The constructor decides it, not how
important the check feels.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Delete the unconsumed seal, and restore two more dark roster rows

Review 62362, both findings correct.

THE SEAL IS DELETED, WHICH IS WHAT MY OWN ANNOTATION SAID TO DO. I had
declared it a 3c frontier, but 3c admits one only with a NAMED LATER
CHANGE, and mine was FCI-3 -- which the displacement analysis shows is
blocked behind a prerequisite nobody has started. The review's phrasing
is right: a gate id plus a missing plan and an unpriceable prerequisite
is not a named later change.

And the annotation cited docs/plans/microvm-launch-displacement-analysis.md,
WHICH DOES NOT EXIST ON THIS BRANCH -- it is on the unmerged #10823. So
the justification for keeping unconsumed code rested on an artifact that
does not resolve, which is the citation defect this session has now made
three times across unmerged branch boundaries.

This also DISCHARGES rather than overrides the earlier ruling to keep the
seal: that keep was conditional on a fixture being able to author the
forbidden state and be refused. No witness can call it at all -- its
input is a sole_constructor snapshot -- so the condition failed.

Module is 79 lines: one rule, four refusal arms, five claims. All five
still pass with the seal gone, which is the evidence that they were
driving the RULE and never the seal.

ROSTER WAS BEHIND MAIN AGAIN, 176 vs 177, missing
guarded_filter_branch_dropped_by_emitter and
match_variant_lookup_does_not_peel_type_alias. Merged: 178 imports and
178 list entries, empty join against main in both directions. Second
time on this branch -- the ledger surface moves faster than an
edit-review cycle, and only the count comparison catches it.

The projection came back UU with NO conflict markers, the merge driver
refusing as designed. That artifact is HealRegeneratesAfterProvisionalMerge,
so the provisional state is staged for heal rather than hand-written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md guarded_filter_branch_dropped_by_emitter
Ledger-Rows-Repaired: docs/design-failure-modes.md match_variant_lookup_does_not_peel_type_alias
Ledger-Repair-Judged: docs/design-rung-drops.md

* Declare the frontier for the rule this module keeps, and record a false annotation as false

Review 62386, both findings correct.

THE ANNOTATION WAS FALSE, NOT MERELY STALE. It said the rule was
"factored out of the seal above it" and that "the seal delegates to it".
The seal was deleted last commit, and bind_microvm_to_cell takes only a
snapshot and never sees an ExecutionGrant -- so the sentence asserted a
relationship between two declarations that does not exist in the corpus.
Recorded as false rather than quietly removed: a 4c annotation is read by
no Accepted program, so a false one never reds, and the next reader
deserves to know the claim was wrong rather than find a tidy comment.

THE FRONTIER WAS DECLARED FOR WHAT I DELETED AND NOT FOR WHAT I KEPT,
which is the review's actual point and it is exact. 3c's two admissible
states differ by whether the consumer and trigger are stated BESIDE the
declaration; for grant_authorizes_reservation they were not, so the rule
was dangling rather than declared. Now stated: named consumer is the
FCI-3 executor whose exit predicate this rule is the agreement half of,
trigger is that executor existing, and the route today is that nothing
in the runner path calls it.

STATED WITH ITS COST RATHER THAN AS A REASSURANCE: that trigger is not
near, because the actuation FCI-3 must displace is authored in no
repository. A reader weighing whether the module earned its place should
have that beside what it does carry -- four typed refusal arms, each
mutation-proven to red ALONE. Correctness established by execution;
only production consumption deferred.

AND A TENSION IN MY OWN REASONING, NAMED RATHER THAN LEFT FOR A REVIEWER:
I deleted the seal partly on "the trigger is not near", and I now declare
a frontier for the rule on that same trigger. 3c requires a trigger to be
STATED, not to be near, so "not near" was over-applied there. The seal
still went for the right reason -- no fixture could construct its
sole_constructor input, so it had ZERO executed evidence. The seal failed
consumption AND evidence; the rule fails only consumption.

5 claims pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Home the grant-authorization join beside the issuer, with typed refusal variants

Review 62520 raised two findings and both were real.

The refusal type collapsed four named causes into AuthorizationDisagrees { cause: String }
while the annotation directly above it asserted "THE REFUSAL ARMS ARE NOT COLLAPSED INTO ONE
`cause` STRING". The four arms were four if-branches producing four prose strings. That is
DESIGN section 2's anemic String leaf, and because a section 4c annotation is read by no
Accepted program, the false claim could never red. The type is now a sum matching the sibling
ReceiptAdmission: GrantAuthorizes | GrantFenceRefused | GrantAuthorizesDifferentWork |
GrantAnswersDifferentDemand | GrantIssuedAgainstDifferentOffer. The five controls match a
named variant instead of string_contains, which strengthens them -- a refusal naming the
wrong authority could still contain the right words.

The home finding was also right, but its prescribed destination does not compile.
product.fabric.budget already imports product.fabric.execution, so homing a rule that needs
MoneyAccount and fence_admits into execution.dag creates an import cycle -- the one structural
law section 4 states. The rule is homed in product.fabric.grant_issue instead, which already
imports execution, budget, identity and work, and which issues the grant: the agreement join
is the direct counterpart of issue_grant, at FCI-2's grain. gunbc.runner_microvm_execution_authorization
is deleted and the witness moved beside the issuance fixture it already imported.

The section 3c frontier is re-declared at the new home: the rule still has no production
consumer, its named consumer is the FCI-3 executor, and the trigger is stated.

Executed evidence: five claims pass under claim_batch --claim-run against
dag/test/claim/fabric/fabric_grant_authorization_witness_test.dag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Scope the append-only-carrier corollary to carriers that are still a list

#10822 deleted the hand-appended failure-mode roster: membership is the row
DIRECTORY now, derived before compile. This row's corollary prescribed a count
comparison against the base for append-only carriers and cited the roster at 163
against 173 as a live countable specimen. Half that citation is a superseded claim,
and it had not landed yet -- so this corrects the row before it ever becomes an
authority rather than editing a published one.

I had declined this edit earlier on the grounds that the row was a live authority and
that changing prose inside a PR waiting on an unrelated gate is how a superseded
claim survives review. The reasoning was sound and its PREMISE WAS FALSE: the row is
absent from origin/main and exists only on this branch. There is no landed authority
to preserve.

The corrected text is taken verbatim from #10853 (deep-crane-107, 9a2358a), which
added the identical file and differed only in this one receipt string; that PR closes
rather than landing a second copy for this one to later drop. The file is now
byte-identical to its version, so one row has one authority.

What the text now does: scopes the rule to append-only carriers that are STILL A
LIST, keeps extdeps.exec.command admit_callers at 67-vs-68 as the surviving specimen,
marks the roster SUPERSEDED FOR ONE CARRIER ONLY citing #10822, and keeps the closing
instruction not to invent a replacement count for the dissolved carrier -- directory
membership is a different subject.

The correction is self-applying: refreshing that citation as though the roster were
still countable would itself be a_live_authority_name_carries_a_superseded_claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

* Fence the money reservation with the money generation, not the lease's

Review 62587 found two real defects and the second explains why the first survived.

THE RULE JOINED TWO INDEPENDENTLY ADVANCING COUNTERS. fence_admits compares the
generation the ACCOUNT holds for a reference, and product.fabric.budget reserve_money
records that from the MONEY request (`generation: request.generation`). The rule
presented grant_lease_generation, which answers the RESOURCE lease's counter. So a
grant authorized whenever the two happened to coincide, and a legitimate grant whose
counters had diverged was refused as GrantFenceRefused -- a cause naming the wrong
fact. product.fabric.execution already records this class one authority over:
comparing generations alone was a real fencing hole, and it made prose describe a wall
the code did not build. This was that class re-introduced against a different
generation authority.

ExecutionGrant carries NO money generation -- money_reservation is a bare
ReservationRef -- so there was no correct value to read off the grant. It is now a
presented coordinate, which is what the rest of this rule already does and what makes
its REDs authorable. The witness presents money_request.generation, what the ISSUER
asked for, against what the account RECORDED: two independently authored facts.
Reading it back off the account would compare a value to itself and green by
construction, which is the 4b decoration this rule exists to avoid.

THE POSITIVE CONTROL WAS GREEN BY FIXTURE COINCIDENCE. money_request.generation is 9
and fixture_lease.generation is 9, so the old control passed while proving only that
two unrelated counters agreed. The fixtures are left at 9 and the discrimination is
carried by a new claim that presents the RIGHT account against a WRONG generation --
the arm the coincidence was hiding, since the existing exhausted_account claim refuses
because the account holds no lease for the reference AT ALL and would pass a rule that
ignored the generation entirely.

MUTATION-PROVEN, and the result is the review's finding as an executed measurement:
with the defect restored, EXACTLY ONE claim fails --
a_grant_fenced_at_another_money_generation_authorizes_nothing -- while the other five
pass, INCLUDING the old positive control. The pre-existing suite was structurally
blind to this defect, which is how it survived. Reverted, all six pass.

The now-unused grant_lease_generation import is dropped: leaving it would advertise a
dependency the code no longer has.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ1HbKBP5SH24N2zxExYmN

---------

Co-authored-by: Brian Searls <cutencool.work@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant