Repository navigation
Delete the three unconditional shell.Exec mock arms (operator ruled) - #9049
Conversation
… refuse instead of receiving a fabricated success
`service shell.Exec` declared one mock_response arm per operation --
Run `{ exit_code: 0, success: true, stdout: "", stderr: "" }`, RunArgv and
Check the same shape -- keyed on an exit status the arm itself supplied. It
was not a function of the declared input, so in hermetic mode `bash -s`
running `exit 1` returned exactly what `true` returned, and a witness whose
subject is "this fails closed" could not be satisfied at all.
Operator ruled deletion (2026-08-23). Hermetic execution of these three
operations now reaches HermeticEffectGround.NoMockResponse -- a typed,
located, per-identity refusal counted on the route-gap roster -- rather than
a success the operation never performed. The arms are deleted rather than
made input-varying because one of the three fabricated facts, transport
reachability, has no honest mock: consulting the transport is exactly what
hermetic mode forbids.
gunbc.hermetic_mock_fidelity, which filed the class, is updated to record the
applied remedy: rows flip to NoMockArm, the defect paragraphs move to past
tense, and the rung moves OutsideTheLadder -> Mitigatable. NOT
StructurallyGuaranteed: nothing in the tree refuses a re-added unconditional
arm, so the ingest-time check named as the next trigger still stands.
Route-gap enrolment for the identities this surfaces follows in this PR from
the run's own per-identity output, not from a hand-authored guess.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… this branch's own floor run Run 32670248426 on this branch reported `route_gap_unenrolled=16`: every identity that reaches shell.Exec.Run hermetically now receives HermeticEffectGround.NoMockResponse where it previously received the deleted fabricated success. The sixteen are read from that run's per-identity output, not enumerated from call sites -- an identity join, not a count. Nine of them were simultaneously enrolled in v2.workflow.floor_expected_red and reported "enrolled as expected-red but ROUTE-GAPPED": they were being held as "runs and fails" while never reaching their subject at all. Those rows are deleted from that roster in the same change, which is the same reclassification floor_route_gap's header records for the original 101 -- an identity in both rosters would be two mechanisms claiming one fact. CLASSIFIED, NOT JUST ENROLLED. A route-gap row is waiting for one of two different things, and filing an unroutable-in-principle row as awaiting-a- route makes it look like backlog forever. A row is not-hermetic-in-principle when its subject is whether a transport was consulted at all -- no mock can answer that honestly. Checked at source, none of these sixteen is that: fifteen name transport: LocalShell and assert an exit code, a captured stdout, or a converge-policy decision, and the sixteenth shells a local printf narration. The SshShell-to-203.0.113.1 witnesses that WOULD be the second kind are not in this population -- they refuse earlier at the fail-closed SshShell stub and never reach this seam. No route is built here, per the standing position that the carrier for the provable bucket and the refusal disposition for the inherently-live one are still with the operator. A row is not a licence to stop: the remedy for each is a route, never an edit to what the witness asserts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Floor result for the enrolment commit — run 32673720641:
So the deletion + enrolment is complete on its own terms: the 16 identities the previous run surfaced are enrolled, the 9 dual-enrolled rows are out of The run is still red for an unrelated reason, and it is not this branch's. Provenance, measured across this branch's two runs rather than assumed: run 32670248426 printed Not fixed here on purpose: the block's subject is a test that no longer exists, so there is no declaration for it to attach to, and §4c rules out hoisting the prose into a — sent from royal-ibex-453 |
|
Correction to my previous comment, on the parse red only — the provenance holds, my reading of the file did not. I said the offending annotation block describes a deleted test and so has no declaration to attach to, making it a question about orphaned rationale. That is wrong. Both blocks in that file are followed by a live What misled me: the diagnostic's numbers are byte offsets, not line numbers, so I read the file's tail instead of mid-file. A 181-line file reporting at ~9797 is the tell. Unchanged: the red is not this branch's, it reproduces on every PR because — sent from royal-ibex-453 |
… is it The composed `--required-ci` parse phase prints refusals with no position, and the annotation diagnostics carry byte offsets rather than line numbers, so a CI log establishes that the rule fired and not where. Two lanes spent an evening on an unattributed refusal for that reason, and the thing that answered it in one look -- this binary, which prints one line per refusal naming the file -- was believed unavailable because a whole-tree local build is OOM-killed in a session container. It is available: build and run it in ONE remote dispatch (the runners are amd64; a binary built there will not execute in an arm64 session). ~4 minutes cold, seconds warm, no CI queue. Recorded on the binary itself rather than in a doc, because this is a fact about how to reach THIS capability and the module that owns it is the only place that cannot go stale independently of it. The note carries the control discipline with the recipe, which is the half that makes a result assertable: a clean sweep from an unfalsified instrument is worth nothing, since a bin that read no files reports the same thing. Append one unattached trailing `//` line, confirm exactly one refusal naming exactly that file, then restore the file -- a probe that leaves its subject mutated turns the next measurement into a lie. Prose only; no behaviour changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
HOLD — do not merge during the #9102 → #8282 window. Computed against #8282's changed-file set: this PR intersects it on 1 file(s), including:
Under the operator's #9059 ruling — "not a category judgment about emission work; it is a direct subject-overlap constraint" — an intersecting PR must not land between the prerequisite (#9102) and the cut cohort (#8282): it alters the cut's conflict set and invalidates its prepared subject. Nothing is wrong with this change and its approvals stand. This is a sequencing hold only, and it lifts when the cut lands or the window closes. Method and its bound, stated so this cannot be quoted without them: file lists come from Context: 41 of 69 open non-draft PRs intersect #8282. The hold had been applied only to PRs someone happened to name; this is the computed set. Two of us have already been caught not applying it to our own PRs. — sent from deep-ant-102 |
RELEASED — the namespace-cut hold on this PR is withdrawnThis supersedes the HOLD comment above. Normal merge policy resumes for this PR. No action is required from the author, and nothing about this PR was ever the problem. Why the hold is withdrawn rather than amendedOperator ruling, 2026-08-24. Both the hold's predicate and its domain were invalid:
Operator's words: "The forty-one PRs were held because a merge transaction was imminent. That transaction no longer exists. The possibility of a future transaction is not a present hold." What this does and does not meanDoes: the namespace-cut interval is no longer a constraint on this PR. Does not: mean this PR must merge. Ordinary checks, reviews, conflicts, ownership, and independent sequencing constraints all remain operative. #8282 itself remains excluded and stays draft. If this PR touches
|
…zen row naming a witness the floor already consumes The required floor refuses at RouteGapFreezeIntersection count=4 and this is a MAIN BREAKAGE, not a property of this branch. #9049 enrolled four identities in v2.workflow.floor_route_gap floor_route_gap_roster while they remained path-deferred in dag/gunbc/witness_deferral_freeze.dag frozen_path_deferrals as LegacyFrozenPathDeferral. Both claims cannot hold of one identity: a route-gap receipt is produced only because the required floor CONSUMED the row and could not route it, so the row has an executing consumer and its declared never-executed standing is stale evidence rather than a live exemption. Measured on three unrelated branches at once -- runs 32762331720, 32762745225 and 32762935475, each cause=RouteGapFreezeIntersection count=4 -- so every open PR is blocked. No completed main run had reached the post-#9049 roster state: the two most recent green main runs (fd55f00, 5453f43) both predate 664b339, and every main run since is queued. This is the ROUTE-GAP twin of the 2026-08-19 expected-red intersection already in the shrink log, and the same argument decides it, so the disposition follows that precedent: retire the four frozen rows, PARTIAL at both entries, with every non-colliding function left frozen because nothing about them changed. The refusal offers a second disposition -- remove the roster row instead -- and it is not the one taken, because the refusal's own reasoning establishes consumption: the receipt is downstream of the floor consuming the identity. Retirement removes no coverage; the floor already attempts these four. Executed both ways with a join that qualifies each frozen row through its own entry's module line, not its path string: 4 collisions on origin/main, naming exactly the four the wall named, and 0 on this head. 3932 files parse-clean. Unrelated to the falsification this PR carries; landed here because every branch is blocked by it and nobody had an open fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XewpecCnCUNjXbeQGz5obk
…n: a frozen row naming a witness the floor already consumes" This reverts commit 5621cd9.
…ecedent the log already sets The required floor refuses RouteGapFreezeIntersection count=4: four identities claim LegacyFrozenPathDeferral - admitted as having NO EXECUTING CONSUMER - while carrying a floor_route_gap receipt that exists only because the required floor consumed the row. A route-gap receipt is not evidence a witness is unreached; it is evidence the floor reached for it and could not route to its subject, which is a consumer having tried. Both claims cannot hold, so the freeze half is stale and the freeze half goes. THIS IS NOT A NEW JUDGMENT. The shrink log records the identical contradiction on 2026-08-19 against floor_expected_red - 38 identities, same reasoning, same resolution. Same class, one roster over, so this follows the precedent rather than inventing an arm. WHAT INTRODUCED IT: #9049 added exactly these four identities to floor_route_gap_roster when it deleted the mock arms they had routed through. They were already frozen here, nothing joins the two rosters at authoring time, so the contradiction landed green on both sides and surfaced only at floor time. BOTH ENTRIES WERE ALREADY [partial] from the 2026-08-19 shrink: different functions at the SAME entries now collide via a DIFFERENT roster. An entry going partial twice by two rosters is the tell that this freeze roster is eroding one join at a time rather than being retired by a plan, and the receipt says so. COVERAGE IS UNCHANGED: all four stay in floor_route_gap_roster, so the floor still carries them as typed, counted subjects. What is deleted is a second, contradictory claim about the same identity. Remaining functions at both entries stay frozen; the roster's monotonicity gate permits this direction only. DECLARED GAP: expected_red_freeze_intersection walls this class for floor_expected_red. Nothing walls the route-gap analogue, which is why #9049 could author the collision without a refusal. Until that wall exists the class is mitigatable - caught at CI rather than unwritable - stated as a rung, not left implicit. Verified: gunbc compile over the edited carrier, 0 blocking errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Conflict in dag/gunbc/witness_deferral_freeze.dag, resolved to main's content. WHY THAT SIDE. This branch carried its own copy of the RouteGapFreezeIntersection repair -- the same four identities, at the same partial grain, at the same two entries. #9133 landed that repair on main first. Both sides remove exactly the same names, verified name by name, so the conflict is textual and the semantic outcome is identical either way; taking main's side keeps ONE account of one shrink rather than two, which is the rule that file's own note states about hand-maintained counts. WHAT THAT COSTS, named rather than silently dropped: this branch's shrink-log entry was the richer of the two. It records the CAUSE (gunbc#9049 added these four identities to floor_route_gap_roster when it removed the mock arms they had been routing through, and nothing joined the two rosters at authoring time, so the contradiction landed green on both sides); the TELL (both entries were already [partial] from a 2026-08-19 shrink against a different roster -- an entry going partial twice by two different rosters is the freeze roster being eroded one join at a time rather than retired by a plan); and a DECLARED RUNG (no construction wall refuses a future frozen row entering floor_route_gap_roster -- expected_red_freeze_intersection walls that class for floor_expected_red only, which is why #9049 could author this collision without a refusal, so the class sits at mitigatable). None of that is on main. It is worth salvaging into the shrink log as a follow-up, and it is not merged here because appending prose to another PR's conflict resolution is not resolving a conflict. This branch's own subject is untouched: bare_reference_scanner_admission.dag (+72) and seed_growth_admission.dag (+6/-2). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRMbwdtHZxTiMNZD5WLS3P
…s, resolved by rule
The branch was 95 commits behind and is now current. Its own lane has done this
117 times before - this is that workflow, not a new approach.
HOW THE 224 HUNKS RESOLVED, each arm an explicit rule rather than a judgment call:
47 take the cut side - main re-adding imports the cut deletes on purpose, or
adding prose above an otherwise-unchanged line (main's
comment kept, cut's qualification kept)
162 requalify main - main changed meaning; its line is taken and qualified
from a declaration index derived from main itself
15 hand-resolved - genuine refactors, chiefly #9049's replacement of
per-transport dispatch with one host_operation_exec
WHERE BOTH SIDES ADDED DIFFERENT CONTENT, BOTH ARE KEPT. Taking one side whole is
a silent deletion of the other's authority. Verified: no duplicated declarations,
and cut-side rows main lacks (gunbc_deploy_privileged_helper_path) survive beside
main's new ones.
GENERATED MIRRORS ARE NOT MERGED. Five .rs files conflicted and are restored from
main rather than resolved - a mirror is regenerated from its .dag, and a
hand-merged mirror is a hybrid no authority produced. REGEN IS OWED before this
lands; the branch's own history does this as a separate "install the emitted seed"
step. One dangling `mod class_b_trim_specimen_test;` is dropped from the restored
lib.rs because the cut deleted that test deliberately ("Delete the tests that
assert import semantics").
FIVE DEFECTS IN MY OWN TOOLING, each caught by reading output rather than trusting
the run, and each silent damage at scale:
- it ran on GENERATED .rs FILES and wrote `use crate::std_algebra::std.algebra.
AlgebraProfile::{` - a .dag qualification inside a Rust use statement. Caught
by pre-commit, not by me; the .rs files are now excluded and restored.
- rewriting inside STRING LITERALS: the corpus stores .dag source as fixture
text, and this branch already had to "revert 302 in-string qualifications".
- qualifying DECLARATION HEADS: `fn v2.compiler.infer.facts_map_from_entries`
renames a declaration rather than referring to it.
- SELF-QUALIFICATION: a name declared in the same module stays bare; the branch
previously repaired 116 of these.
- an index blind spot: the FIRST variant after `type X =` carries no leading `=`
or `|`, so LocalShell and ~50 others were invisible.
WHAT THE INDEX REFUSES TO DO: 1169 names are declared in more than one module. A
wrong qualification there binds the wrong authority AND COMPILES CLEAN, so
ambiguous names are never auto-qualified - they fall to the file's own bare-in-cut
evidence, or to hand resolution.
NOT YET COMPILED against a guarded binary: the local build SIGKILLs and the
scanner guards now live on main. CI is the oracle for this merge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…9090 (#9102) * A parenthesis-free lambda parameter was read as a reference, and pulled the module that happened to declare its letter `bare_identifier_candidates` records which identifiers a file BINDS so they are not mistaken for references to other modules' declarations. It recognises the parenthesised lambda form `(a, b) => ...` and the pattern form `{ .. } => ...`, both through `destructuring_bound_spans`. It does not recognise the single-parameter form with no parentheses: algebra_templates_for_profile(profile: profile) |> any(t => t.name == name) `t` is a binder. It was scanned as a reference, resolved against the WHOLE POOL by `bare_reference_pull_paths_for_source`, and matched `fn t(component, terminal)` in `dag/test/claim/pcb_footprint_witness_test.dag` — so `dag/std/algebra.dag` acquired a closure edge to a PCB witness test, and through it to the PCB product corpus, spatial frames, orthogonal topology, observation and attribution. The same shape ran twice more in the same closure: `ok` in `error_primitives.dag` matched `fn ok(out: String)` in a spark witness test, and `w` matched another. WHY THIS SURFACED NOW rather than years ago. The bare census is gated on `source_declares_import_lines` — a file that declares any import is skipped by it entirely. On main almost every file declares imports, so the defect is dormant. The namespace cut deletes every import line in the corpus, which switches the bare census on for every file at once; the defect is not new, its population is. THE GUARD IS THE PRECEDING TOKEN, NOT THE ARROW. A match arm head is also `ident =>`, and binding it would suppress a real edge to the module declaring the variant. An arm head is preceded by `{`, `}` or a newline; a lambda parameter sits in argument position, after `(`, `,` or a named-argument `:`. Measured over the corpus, all 4397 sites of the form `[(,:] ident =>` are lambdas and no match arm head is preceded by any of the three. MEASURED, one command, three arms, `gunbc compile --source-root dag --source-root src/v1 --entry src/v1/compile.dag`: main, unpatched binary: 99 sources, 0 blocking, exit 0 main, patched binary: 99 sources, 0 blocking, exit 0 <- no regression cut branch, unpatched: 172 sources, 65 blocking cut branch, patched: 155 sources, 34 blocking Main is byte-for-byte unchanged in both closure size and diagnostics, so the narrowing removes only edges that were never real. On the branch where the defect is live it removes 17 modules from the seed's compile subject and half its blocking diagnostics. The two arms on main are the discriminating control in the honest direction: a change that narrows a closure can always be made to look good by narrowing it too far, and the main arm is what rules that out. * A module's own coproduct variant was read as a reference out, and pulled whatever module declared that name at top level Second instance of the same class as the previous commit, found by re-tracing after it landed. `bare_identifier_candidates` records binders; the global bare census indexes top-level declaration HEADS. A coproduct VARIANT is neither, so a module that declares a variant and then writes it was scanned as referencing someone else — and the whole pool was consulted. Four in one closure, every one absurd in the same way: std.cache_interface `type VisibilityScope = Repo | Org | Network | World` used `World` -> pulled dag/product/spatial_world.dag (`type World sole_constructor`) and the spatial corpus std.measure `| Volume` (its own Dimension) used in `Measure<Volume, Nano, Nat>` -> pulled gunbc.roadmap_model (`type Volume`) std.realization_sched `| Measured` used as `basis: Measured` -> pulled std.observation (`type Measured<T>`) std.spatial_frame `= ExtentInFrame { length: FrameLength }` -> pulled std.attribution (`type ExtentInFrame`) This is not a tiebreak between candidates and not a policy about which module is likelier. A name the module itself declares is BOUND BY THAT DECLARATION, so it cannot be a reference out; skipping it is the language's own scoping rule. That matters for how the change is read: the nearest-ancestor picker below is still a heuristic, and this commit does not improve it — it removes a population that should never have reached it. MEASURED, same command and arms as the previous commit: main, unpatched: 99 sources, 0 blocking, exit 0 main, both guards: 99 sources, 0 blocking, exit 0 cut branch, unpatched: 172 sources, 65 blocking cut branch, lambda: 155 sources, 34 blocking cut branch, both: 104 sources, 4 blocking Main is unchanged across every arm. The cut branch's seed closure converges to 104 against main's 99 — the remaining five are real edges the cut's own qualifications introduced, not over-pull — and the residue is 4 diagnostics. * A declaration's own type parameters were read as references — and the trace now says which arm resolved a name THIRD instance of the class, and it closes the third diagnostic. `dag/std/error_primitives.dag` declares `type Result<ok, err> = Ok { value: ok } | Err { value: err }`. `ok` and `err` are TYPE PARAMETERS. Scanned as references they reached the whole-pool arm and matched `fn ok(out: String) -> SshSessionExecResult` in `dag/test/claim/spark_serving_durability_witness_test.dag`, so `std.error_primitives` acquired closure edges to that witness and to `extdeps.ssh.session` — and `extdeps.dns.domain_name`'s `Ok { value: labels |> list_push(label) }` then typed `labels` as `SshSessionExecResult` and reported `list_push` unresolvable on it. Arms, one command throughout: main, unpatched: 99 sources, 0 blocking, exit 0 main, three guards: 99 sources, 0 blocking, exit 0 cut branch, unpatched: 172 sources, 65 blocking cut branch, two: 104 sources, 4 blocking cut branch, three: 94 sources, 2 blocking Main is unchanged across every arm. SECOND CHANGE, and it is the reason the third instance was findable: the resolution arm is now carried instead of discarded. let target_module = match resolve_in(&census) { Some(m) => Some(m), // scoped census hit None => resolve_in(&pool_bare_census(index)?), // WHOLE-POOL fallback }; collapsed both arms into one `Option<String>` one line after computing the distinction, so `GUNBC_BARE_PULL_TRACE` could report WHAT a name resolved to and never HOW. Those are different facts: a pool-fallback hit means the closure is depending on ambient pool membership rather than on anything the file's own tree provides, which is precisely the state a zero-import corpus can sit in while looking green. The arms already know which fired; carrying it costs nothing. First measurement it makes possible, on the cut branch: 37389 scoped against 733 whole-pool fallbacks, 508 of them (distinct file+name) from `src/v1`. Every one of the 23 distinct names whose fallback answer differs from the module main's import list named is a RE-EXPORT — `v1.std.core` imported and re-exported them from `std.syntax` / `std.types`, and `src/v1/00_core.dag` declares none of the 23 — so the fallback names the declarer where the import named the re-exporter. No wrong-provider selection was found. That is a measurement the previous trace could not express. * Collect the multiline coproduct form review 55386 caught being missed, and carry the census's own verdict REVIEW FINDING, verified and correct. `module_self_declared_names` opened a coproduct block only when the `type X =` head line carried its own `=`. The corpus also writes type FrameExtent = ExtentInFrame { length: FrameLength } | ExtentFrameUnregistered { .. } so `ExtentInFrame` and `Predicted` — two of the four variants the guard exists for — were never collected. The closure improvement I measured for `std.spatial_frame` and `std.realization_schedule` therefore came from those modules leaving the closure for an unrelated upstream reason, not from this guard. Right conclusion, wrong evidence, and the review caught it. The fix is one predicate: a bare `type X` head opens a pending coproduct too. The same-line extraction is re-gated on the LINE's own `=` rather than on the flag, so widening the flag cannot silently disable it — which it did in the first attempt at this repair. WHAT THAT FIRST ATTEMPT COST, recorded because the number is the whole argument: rewriting the scanner to gather each declaration as a BLOCK (with an alias guard, so `type List<element> = FreeMonoid<element>` would stop contributing `FreeMonoid`) is the obviously nicer design, and it measured WORSE — the branch arm went 94 sources -> 126 and 2 blocking -> 3, while main stayed at 99/0. Two distinct causes were found and fixed inside it (a blank line inside a coproduct ended the block, silently reopening the `Volume` -> `gunbc.roadmap_model` edge guard 2 closes) and it was STILL worse, so the alias distinction interacts with something not yet understood. The line-wise scanner is kept and the alias over-collection is left in place, marked, as a known under-pull needing its own arms rather than a rider on this one. REGRESSION TESTS, as the review asked — five, over the scanner paths this PR adds: every coproduct form including the multiline one and blank lines inside a block; record field labels NOT collected; a parenthesis-free arrow lambda parameter bound while a match arm head is NOT (the direction that matters, since binding an arm head is a silent under-pull); and the named-argument and comma-positioned lambda forms. SECOND CHANGE: the trace now carries the census's own verdict beside the arm. `resolve_in` returned `Option<String>` and discarded whether the lookup was `GlobalBareUniqueBinding` or `GlobalBareAmbiguousBinding` — the one authority on whether a bare name has competing declarations. Reconstructing that from source does not work: a line-leading `=`/`|` declaration scanner undercounts (it misses `type Connective = Conj | Disj | NoConnective | Arrow` entirely) and a permissive one overcounts (it reads alias targets and `data` initializer heads as variants), and the two answers differed by 30x on the same trace. The census already knows. First measurement it makes possible, cut branch under regen's roots: 35017 scoped unique 534 scoped service 9 scoped AMBIGUOUS 737 pool-fallback, every one unique So the nearest-ancestor picker is consulted nine times in this closure and never on the whole-pool arm. Those nine are `Json`, `owner`, `row` and `rm_force_command` — real competing declarations, decided by module-path proximity. Arms unchanged by this commit: main 99 sources / 0 blocking / exit 0; cut branch 94 sources / 2 blocking. * An alias target is a reference out, not a variant this module declares review 55399: `module_self_declared_names` collected the first identifier after a type `=` as a self-declared variant, alias targets included, so `type List<element> = FreeMonoid<element>` suppressed the closure edge to FreeMonoid's declarer. A fail-open under-pull, the dangerous direction. An earlier revision carried it as a marked KNOWN GAP with no owner and no trigger. That was the error under review, not just the scanner: a marked gap records how debt ENDS, it does not authorize creating it. The discriminator is the right-hand side's own shape — a coproduct alternates or carries a record payload, an alias does neither. The arm shape cannot decide (`type X = Foo`, bare capitalized RHS) is censused rather than assumed: 60 occurrences across dag + src/v2 + src/v1, every one an alias. 47 name a type in another module — the population this changes behaviour on. 13 name a same-file type and are inert either way, since a same-file `type` head is already self-declared via the declaration-head arm. Zero are single-alternative coproducts. Not claimed: that the ambiguity is impossible; the residual failure would be an over-pull. The closure is INVARIANT under this fix on this corpus — 94/2 before and after, because the 47 restored edges reach targets already pulled by another route — so the closure measurement cannot be evidence the fix works, and the two new tests carry the correctness claim alone. Executed, not argued: with the predicate forced to `false` the alias test FAILS and the payload test stays green. 7 pass in the lib target. Main unchanged at 99 sources / 0 blocking / exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Pay the seed-growth receipt #9102 owes, enumerated from the diff rather than from prose The bare-reference scanner adds nine hand-authored Rust declarations to the seed and shipped without the SeedGrowthJustification that DESIGN's forward-freeze policy requires. This is that receipt: one owning module plus one row in the closed roster, modelled on gunbc.whole_corpus_compile_admission. Every figure is measured against origin/main, not recalled. Nine added declarations - one production (module_self_declared_names, 115 lines, not the ~603 that circulated) and eight test scaffolding. Hand-LOC delta +413/-17, all in cli_run.rs. Three existing items are MODIFIED and are named in a separate not-growth note rather than netted into the addition census, which is the direction that makes a census look conservative while being wrong. Two corrections to what I had written from memory. "No other .rs changed" was false: five generated mirrors moved, and the row now names them and cites the generated-population exemption instead of asserting a clean slate. And a naive grep for added Rust declarations returns eleven extra hits that are fixture .dag source inside Rust string literals - data, not declarations - which is recorded so the next person re-running this census does not count a script's own echo as its output. What this does NOT do is admit the scanner. The scaffold presumption stands and is stated rather than argued away: the scanner re-derives parser-owned facts from raw text, and 115 lines rather than 603 changes how much debt is on the table, never whether the presumption applies. The realization disposition is an operator judgment and fails closed in its absence. A receipt that also granted its own admission would be the self-authorized dissolution failure DESIGN names - an author who can write a scaffold can equally write a row claiming it was approved. Verified by execution: gunbc compile over the roster entry, 0 blocking errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Reconcile the three counts the receipt would otherwise leave a reader to add up wrong My "eleven extra grep hits" was a LINE count reported where a reader would read an ITEM count, and it was measured with an ad-hoc filter. Re-measured against the authority instead of by grep: scripts/rust_item_census.py --diff origin/main reports EIGHTEEN added hand items, NINE real and NINE phantom, and the phantoms are now named individually rather than counted. Three denominators circulate and the receipt now says which is which. Eighteen is the census item total. Nine is the deduplicated phantom item count. Twelve is the fixture LINE count, larger because CostAccount is authored twice (bare and parameterized) and fn f twice (over List<Int> and List<Row>) - the census enrols an item once, the line scan sees each spelling. Nine plus eleven reconciled with nothing, and a reader doing that arithmetic would have concluded the receipt was wrong when it was the caveat that was. Stated as a CENSUS DEFECT rather than only a grep caveat, because the next author will run the census, get eighteen, and otherwise have no way to know why it disagrees with the enrolled nine. The census predicts this itself: seed_growth_g0_census_host_scaffold_note records that its extraction is regex, parallel to but outside the rust grammar authority. This is that prediction firing - and it is the exact class of the artifact this receipt admits, a raw-text recognizer miscounting language structure because it reads source as text instead of consulting the authority that owns it. That is the strongest available argument that the terminal construction the presumption note names is the right one for both. Also records that the census reads module_self_declared_names as 116 lines to the fn-body count's 115, since it includes the signature line. Reconciliation raised by deep-ant-102, who authored a competing receipt and discarded it unpushed rather than land a second authority for one fact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Measure the branch delta from the merge base — my correction was the error, not the claim it corrected The receipt said five generated mirrors moved on this branch. They did not. The branch touches exactly THREE files, measured from the merge base bd84f66: this module, the roster row, and cli_run.rs at +413/-17. The false population came from diffing against origin/main, which puts main on the LEFT — so main's own commits since the branch point rendered as deletions authored here. My ORIGINAL sentence, no other .rs changed, was correct, and I replaced it with a falsehood while believing I was repairing an overclaim. The consequence is the part worth recording. deep-ant-102 then independently verified the generated-file exemption for those five files, by reading their // Generated by v1 compiler banners with cli_run.rs as a discriminating control. That verification was sound and its subject was invented: five files this branch never touched. Two sessions agreed, both were wrong, and nothing in either method could have caught it because neither of us re-derived the population - we both took the diff direction on trust. So the row now carries the rule rather than only the corrected number: a branch delta is measured from the MERGE BASE, never from the moving tip, and a hand-LOC census that names a file must be reproducible by a command that states its base. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * State the seed-growth delta against main, not only against the merge base A byte-identical copy of module_self_declared_names landed on main via #9090 while this PR was open. Verified: absent at merge base bd84f66, present on main, md5-identical to the copy here. Git collapses identical additions at one location, so the merge yields exactly one definition and zero conflict markers - nothing breaks, which is why no reviewer, no gate and neither author noticed. The receipt said +9. That is correct for AUTHORSHIP, measured from the merge base, and it is the wrong question. A seed-growth receipt asks what MERGING adds to the seed, and against current main that is +8 - all test scaffolding, zero production items. Both numbers are now stated with their base and their question, because neither base is simply correct and using one where the other was meant is what produced this. This is the mirror of the correction I made earlier today. That one moved from origin/main to the merge base after main's commits rendered as deletions I had authored. This one adds main back for the question the merge base cannot answer. The lesson is not "use the merge base" - it is that a delta is meaningless until its base is named, and the two bases answer different questions. The consequence is recorded because it changes what the pending operator disposition can do: the main copy carries NO receipt - nothing in any .dag on main names the function - so a hand-written source scanner is already in the frozen seed unadmitted, and the only receipt that would admit it is behind this PR. RejectedForFinalConstruction no longer removes it from the seed; it removes the receipt and leaves the unadmitted copy standing. Refusal does not restore the prior state, and the operator must not decide believing it does. Not an accusation of anyone. Two sessions independently needed the same fact and independently wrote the same scanner. That is the terminal-construction argument made by the corpus rather than by an author: when parse does not publish a fact, the number of hand re-derivations is bounded only by the number of consumers who need it. Found by deep-ant-102, routed from an orphaned #9090 finding by smart-ram-730. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * The scanner already shipped via #9090 — this receipt is its accounting, not a proposal My previous revision said the delta was +8 against main, all test scaffolding. That assumed an independent duplicate. It is not one. #9090 merged THIS branch into its own (Merge commit eb2ad48... into session/witty-lark-109, carrying the five scanner commits by name) and squash-merged to main at 18:01:36Z as fd55f00. All NINE declarations are on main - the production item and all eight test items - so the figure against current main is ZERO, not +8. Squash carries CONTENT, NOT ANCESTRY. merge-base --is-ancestor reports none of those commits on main while git grep finds the function and every test fn there byte-identical. Both answers are true of different questions, and reading the ancestry answer as an authorship answer is what made the byte-identity look like a second author independently writing the same 115 lines. One author, one lineage. The receipt keeps +9 from the merge base as the AUTHORSHIP figure, because those declarations are what was written and naming them is what the receipt is for. It now states separately that the population already landed by another route. WHAT THIS INVERTS: refusing no longer removes the scanner from the seed. Nothing in any .dag on main names module_self_declared_names, so the seed today carries a hand-written source scanner with no receipt anywhere. Refusal deletes the accounting and leaves the code standing - the outcome that keeps the seed silently grown. Admission is what makes the seed honest about what already landed. The disposition is a decision about accounting, not about admitting code. This PR now contains two .dag files. The 413 lines of scanner are on main. Found and corrected by deep-ant-102, who also retracted their own independent- authorship reading rather than let it stand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Two defects, one rule, TWO retirement triggers — and a name that implied the judgment it refuses Both corrections raised by the side-chat reviewer, and both are real. 1. "one terminal construction retires both" was WRONG. The scanner and the rust_item_census share a CLASS - a raw-text recognizer misreading language structure - but not a terminal construction. The scanner retires when the .dag front end publishes binder and declaration facts. The census retires when a Rust grammar authority publishes real Rust item identities. A .dag binder surface does not retire a regex Rust-item census. Writing them as one trigger discharges the second defect by implication instead of giving it an owner, which is how a known defect goes quiet. What they actually share is the architectural rule: language structure comes from the authority for that language, never from a parallel raw-text recognizer - two consumers, two triggers. 2. bare_reference_scanner_admission_authority_disposition was named for the exact judgment its own reason refuses to make. Its value is Terminal and it dispositions the RECEIPT, while the open question is whether the SCANNER gets a realization disposition. A declaration named admission_authority_disposition sitting beside that open question invites a reviewer to conclude the admission already exists. Renamed to bare_reference_scanner_receipt_disposition. Not cosmetic: the live blocker is precisely that admission. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Record the operator disposition in the receipt: ScaffoldAdmitted, with its boundary The realization disposition this receipt deliberately refused to grant itself has been given: ScaffoldAdmitted, bound to 7bc6606, relayed by deep-ant-102. The scanner is a bounded bootstrap realization serving the v2 self-host namespace cut, NOT a retained kernel, and it deletes rather than surviving as a fallback when parse publishes the binder and declaration facts it re-derives. The receipt now carries the admitted scope and - more importantly - the SIX things that are NOT admitted and each need a new disposition: additional source-text recognizers, new callers, public-surface growth, the whole-pool fallback, nearest-ancestor ambiguity selection, and a general hand-written namespace parser. A receipt that recorded only the permission and not its edges would be read later as broader than it is. RECONCILED ONE DISCREPANCY rather than assuming it away: the verdict counts THREE separately recorded modifications and its boundary block names TWO. The third is project_roadmap_acceptance_event_history_from_authority_text_builtin. Count agrees, enumeration does not, which reads as an omission in the boundary rather than a narrowing - the verdict's own count includes it. Stated explicitly because a future scope question judged against a two-item boundary reaches a different answer than one judged against the three the verdict counted. Also records why the head moving from 7bc6606 to the merge does not require renewed admission: measured against main the delta is two .dag files, zero Rust, one definition of module_self_declared_names. No scope growth, so the next-reviewed-head clause applies rather than the renewed-admission clause. The relay is recorded AS a relay. This session cannot read the operator thread, so the carrier is named rather than the reading claimed first-hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Retire the four route-gap/freeze collisions #9049 authored, by the precedent the log already sets The required floor refuses RouteGapFreezeIntersection count=4: four identities claim LegacyFrozenPathDeferral - admitted as having NO EXECUTING CONSUMER - while carrying a floor_route_gap receipt that exists only because the required floor consumed the row. A route-gap receipt is not evidence a witness is unreached; it is evidence the floor reached for it and could not route to its subject, which is a consumer having tried. Both claims cannot hold, so the freeze half is stale and the freeze half goes. THIS IS NOT A NEW JUDGMENT. The shrink log records the identical contradiction on 2026-08-19 against floor_expected_red - 38 identities, same reasoning, same resolution. Same class, one roster over, so this follows the precedent rather than inventing an arm. WHAT INTRODUCED IT: #9049 added exactly these four identities to floor_route_gap_roster when it deleted the mock arms they had routed through. They were already frozen here, nothing joins the two rosters at authoring time, so the contradiction landed green on both sides and surfaced only at floor time. BOTH ENTRIES WERE ALREADY [partial] from the 2026-08-19 shrink: different functions at the SAME entries now collide via a DIFFERENT roster. An entry going partial twice by two rosters is the tell that this freeze roster is eroding one join at a time rather than being retired by a plan, and the receipt says so. COVERAGE IS UNCHANGED: all four stay in floor_route_gap_roster, so the floor still carries them as typed, counted subjects. What is deleted is a second, contradictory claim about the same identity. Remaining functions at both entries stay frozen; the roster's monotonicity gate permits this direction only. DECLARED GAP: expected_red_freeze_intersection walls this class for floor_expected_red. Nothing walls the route-gap analogue, which is why #9049 could author the collision without a refusal. Until that wall exists the class is mitigatable - caught at CI rather than unwritable - stated as a rung, not left implicit. Verified: gunbc compile over the edited carrier, 0 blocking errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Brian Searls <briansearls1@gmail.com>
…oster both walls read (#9145) Two walls read frozen_path_deferrals -- expected_red_freeze_intersection and route_gap_freeze_intersection, both in v1_compiler.cli_run -- each refusing an identity simultaneously frozen here and enrolled in a roster asserting the required floor executes it. They landed five days apart and only one landed against an empty population. NOT A NEW PRINCIPLE. It is DESIGN's own admission test seen from the arming side rather than the phase-enrolment side it is stated on: "the admission test is whether every red is closable by the author who caused it at the moment they caused it". A wall armed over an already-non-empty population fails it by construction -- the author who causes the red is whoever pushes next, and the row that makes it red was authored elsewhere. SATISFIED #8494 521d4cf 2026-08-19: 38 colliding rows deleted and the wall wired in ONE diff. VIOLATED #9114 96cd1bf 2026-08-24 14:10: armed over a population of 4. Retired by #9133. WHY THE VIOLATION IS NOT CARELESSNESS, which is the reason the row is worth reading rather than a caution to recall. #9049 (664b339) enrolled the four identities at 14:09 -- SIXTY SECONDS earlier -- and is an ancestor of #9114, so the arming commit's base already carried them. Sixty seconds is far less than a witnesses run, so #9114's green was established against a base WITHOUT the collision and it landed on a base WITH it. Neither author could see the contradiction from inside their own change: #9049 added rows to a roster no wall yet guarded, #9114 armed a wall it had correctly measured as empty. The union was red and neither half was. So the test is NOT "count the population before arming" -- that would have changed nothing here. It is that a wall's green must be established on the base the wall LANDS on. The satisfied instance got that for free by clearing and arming in one diff, leaving no interval in which another change could enter the population. RUNG AND TRIGGER (DESIGN 4b(2)), written as a trigger rather than a ceiling because an unnamed stall and a real ceiling read identically. The class sits at mitigatable, held by a one-diff discipline nothing enforces. No roster-shaped check can climb it: at the moment #9114 was authored and reviewed its governed population was genuinely empty, so no state in either roster could have been refused, and a check over authored rows would be a ratchet. The next-rung trigger is named at the layer the defect lives on -- up-to-date-with-base before merge, strict required checks or a merge queue, which makes "green on the base it lands on" true by construction. That is an operator decision and the row does NOT assert how it is configured today; the setting was not readable when it was written. HOME. Proposed for v2.workflow.required_floor; the walls are not there and it names neither, so a row there asserting how they landed would be authority substitution. This file is the one roster both walls read, already names one of them, and already carries both polarities as shrink-log entries -- so the row joins facts in the file rather than importing them, and avoids hand-LOC growth in the frozen seed. Re-derivable: join floor_route_gap_roster (110 entries) against frozen_path_deferrals (128 rows, 614 qualified identities), qualifying every frozen row through its own entry's module line and not its path string -- 4 of 614 at 8ab8a8e, matching the wall's count=4. Corroborated on this branch: frozen_path_deferral_identity_count returns 610, which is 614 less the 4 #9133 retired. Verified: the module parses and evaluates after the annotation. Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l-open: the premise falsified, and the join enrolled (#9123) * rust_runtime_bridge_name's Absent arm is the identity case, not a fail-open: the premise falsified, and the join enrolled The brief held that an unknown bridge name is emitted unchanged, so every mismatch becomes silently invalid Rust. Measured, that is false in two independent ways, and the arm is correct as written. rt_bridge_function_names is DERIVED, not authored: filter(f => f.name != f.bridge_name) over rt_function_registry, 9 rows of 57. A miss therefore means "this bridge's v1_rt symbol is spelled like its .dag name", which holds for the other 48. Returning the input unchanged is the total answer, not a fabrication. An unknown name also cannot reach either use of the result. emit_typed_call consumes runtime_name only inside `if is_rt`, and is_rt requires map_contains_key(rt_functions(), func); emit_rust_generic_method_call computes bridge_name only in the else of a guard that already refuses with the Rust error_type_template when rt_functions() misses. What lands is the evidence, not a repair. The witness joins registry to derivation by IDENTITY over every row -- rt_bridge_name(entry.name) == entry.bridge_name, no count and no literal, per DESIGN section 5's oracle rule -- so a future row whose override is dropped from the map goes red here instead of emitting a call to a v1_rt symbol that does not exist. Executed both ways: green on the tree as it stands; false when the derivation's filter is replaced by filter(f => f.name == "concat"), the control reverted with no diff. The section 4c annotation records the two guarded call sites, which a reader of the function alone cannot see. One further falsification, recorded here because it cost a build and would otherwise be re-derived: the same shape one arm over -- emit_typed_method_call's PlainMethodSemantics arm rendering recv.method(args) for a method with no rust_method_templates row -- is NOT a live fail-open either. 04_infer's method_existence_decision refuses first, typed and located, on every shape probed: a kernel-profiled receiver (method 'nope_not_real' not found on receiver type 'Container(List,Primitive(String))'), a propagated lambda parameter, and a bare type variable (ReceiverTypeUnestablished). Contrary readings came from the baked /usr/local/bin/gunbc, which resolves the entry file alone -- its own documented tell, and it emitted x.nope_not_real() with 0 diagnostics where the tree binary blocks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XewpecCnCUNjXbeQGz5obk * Retire the four route-gap/freeze collisions #9049 left on main: a frozen row naming a witness the floor already consumes The required floor refuses at RouteGapFreezeIntersection count=4 and this is a MAIN BREAKAGE, not a property of this branch. #9049 enrolled four identities in v2.workflow.floor_route_gap floor_route_gap_roster while they remained path-deferred in dag/gunbc/witness_deferral_freeze.dag frozen_path_deferrals as LegacyFrozenPathDeferral. Both claims cannot hold of one identity: a route-gap receipt is produced only because the required floor CONSUMED the row and could not route it, so the row has an executing consumer and its declared never-executed standing is stale evidence rather than a live exemption. Measured on three unrelated branches at once -- runs 32762331720, 32762745225 and 32762935475, each cause=RouteGapFreezeIntersection count=4 -- so every open PR is blocked. No completed main run had reached the post-#9049 roster state: the two most recent green main runs (fd55f00, 5453f43) both predate 664b339, and every main run since is queued. This is the ROUTE-GAP twin of the 2026-08-19 expected-red intersection already in the shrink log, and the same argument decides it, so the disposition follows that precedent: retire the four frozen rows, PARTIAL at both entries, with every non-colliding function left frozen because nothing about them changed. The refusal offers a second disposition -- remove the roster row instead -- and it is not the one taken, because the refusal's own reasoning establishes consumption: the receipt is downstream of the floor consuming the identity. Retirement removes no coverage; the floor already attempts these four. Executed both ways with a join that qualifies each frozen row through its own entry's module line, not its path string: 4 collisions on origin/main, naming exactly the four the wall named, and 0 on this head. 3932 files parse-clean. Unrelated to the falsification this PR carries; landed here because every branch is blocked by it and nobody had an open fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XewpecCnCUNjXbeQGz5obk * Revert "Retire the four route-gap/freeze collisions #9049 left on main: a frozen row naming a witness the floor already consumes" This reverts commit 5621cd9. * The witness was enrolled where the floor declines the whole arm: move it to a module that actually executes Measured on this PR's own green run (32778297671), the witness this PR adds is NOT executed by the required floor: test.claim.v1_source_audit_witness_test.rt_bridge_name_agrees_with_registry_on_every_row declined_live_tree It is one of declined_live_tree=902 of offered=12303 -- discovered, counted, folded never. Every identity in that module shares the disposition, because the module declares live_tree_disposition = ReadsLiveTree for its src_has file readers. So the green run said nothing about this witness, and a passing CI would have been cited as coverage for evidence that cannot run. That is the specification-without-execution trap (DESIGN section 5) and the decoration failure (section 4b): a check whose red is unreachable is worse than absent. Nothing about the FOLD needed the live tree. rt_function_registry is an in-corpus declaration and the join reads no file, so the decline was inherited from the module's blanket disposition rather than earned by the witness. The fix is therefore a home, not a rewrite: test.claim.rt_bridge_registry_witness_test, which declares no live-tree disposition and is planned. The identity join, its denominator and its argument are unchanged. v1_source_audit_witness_test.dag returns byte-identical to main -- this PR no longer touches it at all. Executed in the NEW home, both ways: returns true as the tree stands, and false when the derivation's filter is replaced by filter(f => f.name == "concat"), control reverted with no diff. 3939 files parse-clean. Found by reading the floor's own required_floor_disposition.tsv artifact rather than the check mark. A green witnesses run and a witness that never ran render identically at the check level, which is exactly why the disposition roster exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XewpecCnCUNjXbeQGz5obk * The annotation cited the witness's old module: the move broke the citation in the same PR that wrote it review 55574 (REQUEST_CHANGES) is correct. The previous commit moved the witness out of test.claim.v1_source_audit_witness_test -- whose whole arm the floor declines -- into test.claim.rt_bridge_registry_witness_test, and did not update the annotation on rust_runtime_bridge_name that names it. The citation was false on the day it was written, in the same diff that authored both ends. This is the DESIGN section 3 cite-the-symbol class, and worth recording that it is now caught by review rather than by a gate: the cited-symbol census was dropped from CI 2026-08-23 by operator directive, so the rung is review diligence. This is what that costs. Fixed the module name. Re-resolved every symbol the annotation names, rather than only the one reported: emit_typed_call, emit_rust_generic_method_call, rt_functions, rt_bridge_function_names in the emitters; rt_function_registry, rt_bridge_name in extdeps.languages.rust.emit; and the cited test fn in the new module. All six resolve. The surviving reference to v1_source_audit_witness_test is in the new module's own header, explaining why the witness is NOT there. That module exists and the sentence is about it, so it stays. 3939 files parse-clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XewpecCnCUNjXbeQGz5obk * The totality argument rests on the map being derived: say so where it can be invalidated Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XewpecCnCUNjXbeQGz5obk --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#9163) Two main reds in one evening, and a third pair before them, all one class: two PRs independently green, jointly broken. #9049 with #9114, #9057 with #9062, #8919 with #8992. No textual conflict in any of them -- the first change altered a semantic API and the second was checked against a base where the old API still existed, so both greens were true of trees that never existed together. The #9057/#9062 pair took the whole fleet red for about an hour and produced four uncoordinated repair PRs plus a fifth branch, two of which proposed deleting live code. No wall inside this repository can close that. The invariant needed is that the commit admitted to main is the exact composed commit that passed the required checks, and nothing in the substrate can constrain what GitHub admits. WHAT THIS CHANGE IS: the in-repository half, and it is INERT ON ITS OWN. witnesses.yml listens only to workflow_dispatch, push and pull_request, so enabling a merge queue today would create a merge-group commit for which the required check never schedules -- pending forever. This adds the trigger so the prerequisite exists BEFORE the setting is changed, with no window in between. With no queue configured the event never fires, so CI behaviour is unchanged by this diff. WHAT IT IS NOT: the repository setting. The ruleset currently carries strict_required_status_checks_policy false, no merge-queue rule, and an always-bypass role. That is an operator decision and this PR does not presume it. MergeGroup already exists in extdeps.github.actions WorkflowTrigger and the YAML emitter already renders it, so this is one row, not new modelling. EVIDENCE, including what I could NOT establish. Evaluating the workflow authority on this branch and on clean origin/main gives byte-identical diagnostic sets (2232 lines, zero diff), so nothing is introduced. I could NOT run the emitter to regenerate witnesses.yml: the local gunbc shim is a Jun 26 build that cannot resolve this corpus. The one yml line was derived by reading the emitter -- `MergeGroup => kv(key: "merge_group", value: YamlNull)` renders exactly as `workflow_dispatch:` does, in the position its entry occupies in the `on:` list. DESIGN records the generated-artifact drift gates as currently unguarded, so nothing will catch that line if I have it wrong, which is why it is stated rather than assumed. Regenerating in CI and diffing is the check I want on this PR. Co-authored-by: Brian Searls <briansearls1@gmail.com>
service shell.Execdeclared onemock_responsearm per operation — Run{ exit_code: 0, success: true, stdout: "", stderr: "" }, RunArgv and Check the same shape — keyed on an exit status the arm itself supplied. Not a function of the declared input, so under hermetic executionbash -srunningexit 1returned exactly whattruereturned. Every hermetically-executed claim downstream of a shell effect was asserting about the mock, and a claim whose subject is "this fails closed" could not be satisfied at all (DESIGN §5, no fabricated plausible output).Operator ruled deletion, 2026-08-23. The class was filed with its measured receipts in
gunbc.hermetic_mock_fidelity; this PR applies the remedy that carrier declined to apply itself.What changes
extdeps/shell/exec.dag: the three arms are deleted, with an annotation on the service recording why there is none, and why a re-added arm would have to vary with its declared input (and still could not answer reachability).gunbc.hermetic_mock_fidelity: rows flip toNoMockArm, defect prose moves to past tense, rung movesOutsideTheLadder→Mitigatable. Deliberately notStructurallyGuaranteed— nothing in the tree refuses a re-added unconditional arm, so the ingest-time check the module already names as its next trigger still stands. Deleting three arms is a repair, not a wall.Deleted rather than made input-varying, because the arm fabricated three facts with different remedies. An input-varying arm could honestly serve exit status and captured output; it cannot serve transport reachability, since consulting the transport is precisely what hermetic mode forbids. A remedy closing two of three while looking like a fix is what the fidelity carrier's typed
FabricatedFactsplit exists to prevent.Consequence, and the state of this PR. Every witness that reached these operations hermetically was passing (or being held red) against the fabrication, so each now surfaces as
HermeticEffectGround.NoMockResponse→ route gap.route_gap_unenrolledmust be 0 for a green floor, and the ten held reds this class was diagnosed from should leavefloor_expected_redas their subject stops being unreachable. That population is being read from this PR's own floor run per-identity output rather than hand-guessed from call sites — enrolment lands as a follow-up commit here. Reviewers: the diff is not complete until the floor is green.A route-gap row is not a licence to stop: the remedy for each enrolled identity is a route, never an edit to what the witness asserts.
🤖 Generated with Claude Code