Skip to content

Wet-evidence convergence: one shared wet-admission authority, and the local route migrated onto it (7 refusals in, 7 typed causes out) - #9975

Merged
gunbai-bot[bot] merged 23 commits into
mainfrom
session/snappy-koi-879-wet-evidence-extraction
Sep 3, 2026
Merged

gunbai-bot[bot] merged 23 commits into
mainfrom
session/snappy-koi-879-wet-evidence-extraction

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

The local wet route stops being its own authority and joins the shared wet-evidence one. Four claims land together because the migration is not separable from the decoder that reads its rows, but they are separable to review, and reviewing them in this order is the cheapest path:

1. The shape migration (src/v2/workflow/local_repo_wet_terminal.dag)

LocalRepoWetScheduled becomes WetScheduledClaim, the qualified identity string becomes a WitnessIdentity { module_path, function } record, and expected: LocalRepoWetExpectPassed becomes expectation: ExpectedToHold {}.

The identity change removes a duplicated fact rather than adding a field: every row's old identity ended in "." + function, so the qualified name was authored twice per row and could disagree with itself. It is now derived by witness_identity_qualified_name, the same projection every other identity consumer already uses, and a witness asserts the two spellings agree across the whole roster.

2. The seed decoder (src/v1/stage0/src/cli_run/required_floor_runner.rs)

The Rust seed reads these rows, so the shape change breaks it unless ported in the same commit. Every refusal is preserved and one is strengthened: the old entry_module was derived by stripping ".{function}" off the identity, which silently accepted a row whose identity and function disagreed; it now compares identity.function to the row's function directly and refuses with "one member, two names". ExpectedRed still refuses — the executor has no arm for it and says so rather than treating it as pass.

3. The 57 namespace admissions are deleted, and the deletion is charged correctly

NAMESPACE_TRANSITION_ADMISSIONS held 57 rows for #10077's required_floor -> floor_terminal_ledger relocation. #10077 merged as a4a6db175d2, so main carries the relocation and no run after it can produce those deltas.

The wave phase's own refusal wording states the charging rule: consumed rows are "due for deletion on this roster-touching change". Re-tensing their trigger touched the roster, so the obligation landed on the change that went looking for it, and it is paid here rather than left as a debt for a stranger. The roster returns to empty, and empty is not permissive — a run with a real delta still refuses it as UNADJUDICATED.

The evidence that the deletion took nothing load-bearing

Read off the merge ref, which is what CI judges — a census on the branch tip would answer about a tree nobody merges. Run 33700975802, base=53934adc676 head=f5b67e3e677:

modules_compared=4571  binding_rows_compared=691488  closure_rows_moved=172  deltas=18
  12 ExplicitlyEvaluatedZeroDelta
   4 AuthoredReferenceResolution
   2 SameDeclarationIdentityRebind
   0 TargetChanged        0 NewUnresolvedness

The deleted rows were all TargetChanged, and this branch produces no TargetChanged delta. So the answer is structural rather than incidental: it is not that the roster happened to go unqueried, it is that every delta this branch produces is in a class that auto-admits and never needed a row.

One limit of that green, stated rather than left for a reader to assume. The phase reports unadjudicated, stale and consumed only in its refusal line; on ADMITTED it prints the verdict alone. So unadjudicated = 0 is measured — a nonzero is precisely what would have made that line a refusal. stale = 0 and consumed = 0 are zero by construction, not by measurement: with an empty roster no row can fail to match a delta and none can be satisfied at base. They are sound and they carry no information about the mechanism, so this run must not later be cited as evidence that the stale detector works.

4. Two rows ported across the migration, not chosen against

#10055 (992fd4423f6) added two host-probe rows to this roster on main while the branch was migrating its shape. That conflict is a shape change meeting new members — both sides right about different things — so the resolution re-authors their rows in the new shape and preserves their module_path and function verbatim. Which witnesses that lane admits is #10055's authority; how the roster is spelled is this branch's.

Their module_path omits the .host segment their entry path carries, which looks like an entry-module disagreement and is not one: the file declares module test.claim.host_cli_dependency_wet_witness_test on line 1, and every sibling in dag/test/claim/host/ likewise omits the segment — paths are discriminators, not gospel (DESIGN §3). Predicted statically, then confirmed by execution:

[local-repo-wet] identity=test.claim.host_cli_dependency_wet_witness_test.observe_echo_wet_posix_command_v_check_does_not_crash expected=passed observed=passed
[local-repo-wet] identity=test.claim.host_cli_dependency_wet_witness_test.observe_npm_wet_posix_command_v_check_does_not_crash  expected=passed observed=passed

Membership prose and roster agree at 13 rows in three groups.

Verification

Run 33700975802, head-matched on f227058a: lane=build phases_run=2 failed=0, lane=witnesses phases_run=3 failed=0, zero FAILED PHASE lines, namespace-wave-admission ADMITTED.

required-floor: planned=3519 executed=3519 not_attempted=0 passed=3444 known_red_held=26 failed=0
                interrupted_cpu_deadline=0 interrupted_wall_deadline=0 stale_quarantine=0
required-floor: verdict=FloorClean unexpected_failures=0 verdict_incomplete=0

route_gap_held=49 is unchanged and is not this PR's subject — it belongs to #9725, the last PR in the sequence.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4

gunbc-ci-auto-heal and others added 13 commits September 1, 2026 23:30
…s before extracting

The anti-collapse count the convergence receipt requires, taken BEFORE any
extraction so the post-extraction cause count has something to be measured
against. 18 distinct causes deduplicated by remedy, with the three binding
mismatches kept apart as 7a/7b/7c.

It found the collapse in the opposite place from the one assumed: seven of the
eighteen remedies are typed causes in local_repo_wet_terminal and detail-string
payloads in floor_wet_route, and five more exist only in the Rust reader with no
.dag spelling. The extraction therefore widens the transported route to main's
grain and must not narrow main's seven causes to meet it.

It also records that the terminal vocabulary is neither module's to mint:
floor_terminal_ledger already owns the terminal, observation, expectation and
thirteen dispositions, is imported by the conflicted consumer, and already
derives PassedOverBudget/KnownRedNowPassing by expectation -- the same rule both
wet modules re-derived independently. Three arrivals at one distinction is the
proven coincidence DESIGN asks for before unifying vocabulary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
…th a typed binding

The shared interface both wet routes will consume, and the reason it mints no
terminal type of its own.

WHAT IT IS NOT. It does not introduce a third terminal vocabulary. Two parallel
ones is the fork being closed, so a third would be the same failure wearing a new
name. v2.workflow.floor_terminal_ledger already owns the raw attempt terminal, the
observed-verdict versus unreadable-observation split, the expectation, and thirteen
derived dispositions -- and floor_changed_witness, the fold both routes feed,
already imports it. This module imports that vocabulary and asks
claim_disposition whether a terminal meets its expectation rather than deriving it
again.

That reuse is grounded in a PROVEN COINCIDENCE, not in taste: four independent
arrivals at one distinction. The ledger derives PassedOverBudget and
KnownRedNowPassing from a completed-past-limit terminal by expectation; the floor's
seed keeps completed_over_cost_requirement apart from interrupted_before_verdict;
local_repo_wet_terminal un-collapsed LocalRepoWetCompletedOverBudget out of its
nonterminal arm; and floor_wet_route separated cost debt from no-verdict the same
week. None of the three read the others, and none imported the fourth.

THE LOAD-BEARING PIECE IS THE BINDING. local_repo_wet_terminal compared a
candidate: String with ==, which DID decide admission -- so it was a validity key,
not provenance -- but a bare String cannot say WHICH relation produced it. The
binding is now two arms, deliberately asymmetric: a co-resident execution binds to
the prepared subject and nothing else (executor identity and freshness are
meaningless for a run inside the floor), while a transported receipt binds to the
semantic subject AND the executor contract, because a receipt produced by a
different executor over the same tree is a different evidential fact. The executor
contract is a required field, not an optional one: an absent one has no constructor
and cannot be defaulted permissively.

Twelve typed causes, none collapsed. The three binding mismatches stay three, and
evidence of the WRONG KIND is its own cause rather than a digest that happens to
differ -- the case a String comparison could only ever report as "not equal".

EVIDENCE. Eight witnesses green by execution, each RED paired with a control
differing by exactly the fact under test. Then a mutation to prove they
discriminate rather than merely pass: short-circuiting wet_binding_causes to return
no causes turns exactly the three binding witnesses RED and leaves the other three
GREEN. Predicted before the run, three red three green observed. A suite that went
fully red would be entangled; one that stayed green would be inert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
A live defect in 3270fea, found in review and confirmed by count: `dry_source`
occurred EXACTLY ONCE in wet_evidence.dag -- its own field declaration. Nothing
read it, and wet_evidence_validate did not consume it.

Two consequences, both of which DESIGN names:

  `route` and `dry_source` varied independently, so the type admitted two states
  with no referent -- a co-resident execution after a deliberate wet decline, and
  a transported receipt after an observed hermetic route gap. The dry-source
  distinction was therefore COMMENTARY the realization could contradict, which is
  validation standing where construction was available (section 5).

  And a data row whose only function is to say something is the misplaced prose
  section 4c forbids. The field asserted an antecedent no executing consumer read.

THE REPAIR IS STRUCTURAL, NOT A CHECK. `WetRouteAntecedent` fuses the two facts
into one coproduct -- LocalRepoAfterHermeticRouteGap | SelfHostAfterDirectWetDecline
-- and the route becomes a total PROJECTION of it rather than a field beside it.
The invalid combinations now have no constructor, so there is nothing left to
validate: rung 4 rather than the rung 2 an exhaustive WetRouteDrySourceMismatch
refusal would have bought, and it costs no witness because no invalid state
survives to witness.

ONE WITNESS IS ADDED, for the residue the type cannot close. A TERMINAL's route is
an execution fact, not a schedule fact, so a terminal claiming a different route
than its schedule derives must still refuse -- a cause that had no witness before
this commit. Nine witnesses green by execution.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
Picks up #10000, which regenerated the stale gunbc.rust_source_type_bindings
stage0 mirror. The regen phase was failing on this branch for that reason and
not for anything this branch changed: main's own run 33589101464 (head 39b0eb8)
fails the identical phase on the identical file, and that run contains none of
this branch's code.
…ter independent of the antecedent

The first repair fused `route` and `dry_source` because they were adjacent IN THE TYPE. The third
fact entered through a different door -- as an ARGUMENT -- and fusing a coproduct does not close a
parameter.

`wet_evidence_validate` took `required: WetEvidenceBinding` with nothing joining it to the
schedule's antecedent, so this batch was constructible and ADMITTED: a schedule whose antecedent is
`LocalRepoAfterHermeticRouteGap`, a required binding of `TransportedReceiptSubject`, and terminals
carrying that same transported binding. Every pairwise check passed -- the terminal's route matched
the schedule's antecedent, and the observed binding matched the required binding -- so a co-resident
schedule was satisfied by transported evidence. The inverse was equally writable.
`WetBindingRelationMismatch` never fired because it compares OBSERVED against REQUIRED, and those
two agreed; what disagreed was ROUTE against REQUIRED RELATION, and they never met.

The repair is structural rather than a fourth validator: `WetEvidenceRequirement` is one authority
per batch, and route, antecedent and required binding are ALL derived from it totally. There is no
longer a way to say "local schedule, transported requirement" because the requirement IS the route.
`WetScheduledClaim` loses its `antecedent` field; the batch supplies it.

`WetTerminalRow` KEEPS `route` as an independent execution observation. Deriving that one away
would make the foreign-route refusal unauthorable -- a check whose RED cannot be written, which
DESIGN section 4b calls a decoration rather than a weak wall. The schedule's route is derived; the
observation's route is carried; the join between them stays load-bearing.

Two consumer rules land in the module rather than only in review prose: validate one batch PER
REQUIREMENT and never concatenate the routes, because the two require different binding relations
and a merged call would have to weaken the requirement to admit both -- widening instead of
refusing. And `WetEvidenceRefused` is not read wholesale as "receipt invalid": the
verdict-not-expected arm means the evidence is VALID and the outcome is known, so a valid FAILING
attempt stays publishable, or latest-attempt degrades into latest-SUCCESS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
…d the ledger vocabulary

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
…sion/snappy-koi-879-wet-evidence-extraction
…hten two witnesses that claimed more than they asserted

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
…o longer describes the head

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

Both defects were authored today and both were found by execution rather than review:
the DESIGN 4c annotation-placement rule refuses a // block inside a declaration body,
and fold_list is declared (xs, empty, cons) rather than the (xs, init, step) I invented.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
@gunbai-bot gunbai-bot Bot changed the title FLOOR-ROUTE-GAP-SELF-HOST: publish the executing route family that discharges the self-host behavioral witnesses from route_gap_held (49 required / 112 full) Wet-evidence convergence: one shared wet-admission authority, and the local route migrated onto it (7 refusals in, 7 typed causes out) Sep 2, 2026
gunbc-ci-auto-heal and others added 10 commits September 2, 2026 17:41
…serving every wall

The seed's local_repo_wet_schedule reads the .dag roster through the interpreter and
decodes it by type and field NAME, so the migration broke it in four places. CI caught
this on both heads; no .dag witness could, because they construct fixtures directly.

Walls preserved rather than ported: the identity/function agreement check is now a
direct comparison of the two spellings the row carries rather than a suffix-strip, and
the expectation arm still refuses everything but the one arm this lane realizes --
ClaimExpectation has two arms where its predecessor had one, and a wider type is not a
wider capability.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
…d by identity

The wave-admission phase refused both attempts with 57 unadjudicated deltas -- every one
a TargetChanged binding reading base {v2.workflow.required_floor} -> head
{v2.workflow.floor_terminal_ledger}, verified as the only shape in the report. That is
this PR's own move, and the roster's documented closure is to author a row per delta.

Enumerated, not patterned: 57 deltas, 57 rows, each naming its module, declaration and
spelling. The rows go stale the moment this merges and MUST be deleted in the first PR
after it lands -- a stale row refuses every unrelated PR in the repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
…9-safety-vocabulary-move

# Conflicts:
#	src/v1/stage0/src/namespace_wave_admission.rs
…sion/snappy-koi-879-wet-evidence-extraction
Main's seventeenth dissolution swept this roster to empty. My conflict resolution kept
main's receipt prose but re-opened the const with the whole of my side's row list, which
still carried the four dissolved rows -- so it reinstated a permission nobody re-authored
and the wave phase reported them CONSUMED and refused. That is precisely the failure a
conflict invites: a resolution in my favour restoring what the other side deliberately
removed.

The 57 rows themselves adjudicated correctly in that same run: 0 unadjudicated deltas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
…sion/snappy-koi-879-wet-evidence-extraction
The trigger read '#10077 MERGING ... once that PR is in main'. That was accurate when
authored and became ambiguous the moment #10077 landed (a4a6db1, 18:55:08Z): a
future-tense trigger gives a later reader no way to tell whether it has fired, and the
next toucher pays for the ambiguity rather than its author.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
…red and this change is the roster-touch that owes it

#10077 merged as a4a6db1, so main carries the relocation and no run after
it can produce those deltas. Run 33694346070 (on the merge commit 2b3b841)
reported all 57 CONSUMED while still ADMITTING, because that commit does not
touch this roster; the SJT-1 cohort's refusal states the charging rule exactly
-- consumed rows are "due for deletion on this roster-touching change".

Re-tensing the trigger touched the roster, so the obligation landed on the
change that went looking for it. The roster returns to empty, which is not
permissive: a real delta still refuses as UNADJUDICATED.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
…o host-probe rows across the roster's shape migration

main's #10055 added two rows in the OLD shape (`LocalRepoWetScheduled`, a
qualified `identity` string, `expected: LocalRepoWetExpectPassed`) to the same
roster this branch migrates to `WetScheduledClaim` / `WitnessIdentity` /
`expectation`. The conflict is that shape change meeting new members, so the
resolution ports both rows into the new shape rather than choosing a side:
their authored module_path and function are preserved verbatim, since which
witnesses that lane admits is #10055's authority and not this branch's.

The membership prose auto-merged to THIRTEEN MEMBERS IN THREE GROUPS and the
roster now holds thirteen rows, so the count and its prose agree. No consumer
hardcodes the old count; the roster's identity/function agreement witness
covers the new rows unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvVoivi7L449wbh6rjeJY4
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 3, 2026 02:30
@gunbai-bot
gunbai-bot Bot merged commit 2ef77f5 into main Sep 3, 2026
7 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/snappy-koi-879-wet-evidence-extraction branch September 3, 2026 02:43
@briansrls
briansrls restored the session/snappy-koi-879-wet-evidence-extraction branch September 3, 2026 02:47
gunbai-bot Bot pushed a commit that referenced this pull request Sep 3, 2026
One file changed on both sides: src/v1/stage0/src/namespace_wave_admission.rs.
That collision is guaranteed rather than unlucky -- every relocation PR touches
the admission roster by construction, so two relocation lanes in flight always
meet here.

RESOLVED ONTO MAIN'S VERSION, KEEPING ONLY MY OWN CONTRIBUTION. Main's #9975
had already deleted the 57 consumed `ledger safety vocabulary relocation
gunbc#10077` rows and recorded the EIGHTEENTH DISSOLUTION, on better reasoning
than the version this branch carried: it re-tensed the rows' own trigger, which
touched the roster, so the obligation landed on the change that went looking for
it. My branch's prose claimed that deletion as its own. Keeping it would have
been a false statement about another lane's work, so it is dropped entirely and
main's account stands. What re-applies here is only the 17 TransitionAdmission
rows for this PR's relocation, plus their entry.

THE ORDINAL WAS CHECKED, NOT INCREMENTED. This branch had written FIFTH
TRANSITION; main already has a FIFTH (gunbc#9675, 2026-08-29). The ledger's
transition ordinals already collide -- FOURTH and FIFTH each name two
transitions, the second FOURTH being #10077 reusing it deliberately as a
back-reference. FIFTEENTH is the highest in use, so this entry is SIXTEENTH, and
says why it took the next UNUSED ordinal rather than the next in sequence: a
third duplicate would make the entry uncitable by its own name.

Verified: no conflict markers, 17 rows, cargo check -p v1-compiler --lib clean,
and none of this branch's ten .dag files were touched by the merge.
witness_layer_roots is unchanged at ["dag", "src/v2"], so the new enforcement
witness's import still resolves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CksG1GV7gm1uQh62UeV1jE
gunbai-bot Bot added a commit that referenced this pull request Sep 3, 2026
…ling-keyed (census only) (#10180)

* Census the seed's spelling-keyed decode of .dag authorities, and find a second already-fired instance

The seed reads .dag values through the interpreter and addresses them by SPELLING -- type
name, variant name, field name, entry-function name, all string literals in Rust. Seed and
authority share no Rust type, so a rename has no compile-time link to the decoder that
depends on it. #9975 found this by accident. This is the population.

Five decode primitives, all bottoming out in four InterpContext methods; two of them (P3
resolve-and-match-arm, P5 file-local wrappers) are invisible to a grep written from the
#9975 sym_eq specimen and carry 97 further sites in 9 files. 25 unambiguously-attributed
.dag authorities are decoded; the mint side carries 262 more sites in the other direction.

Second already-fired instance, found by the census rather than by accident: cli_run.rs calls
run_in_context "roadmap_acceptance_event_history", which no declaration in the corpus
carries -- gunbc.roadmap_authority renamed it to _load and changed the result shape. The
only test over the route is #[ignore]d AND pins the authority text to the pre-rename SHA, so
it can never observe the break.

Rung found at 1: every decode site read fails closed with a typed Err, so the class is not
silent-wrong-answer but silent-at-COMPILE-time. Ceiling 3, trigger stated as the capability:
emitted typed decoders and constructors sufficient to leave no hand-written .dag spelling in
the seed. Census only -- no repair, no ledger enrolment.

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

* RETRACT instance 2: the decoder's subject is revision-addressed, and the join key is (name, revision)

I reported cli_run.rs's run_in_context "roadmap_acceptance_event_history" as a live break
because no declaration of that name exists AT HEAD. That was a defect in my census method,
not in the seed.

The decoder is the carrier-introduction bootstrap: it fires only when the JSONL carrier is
ABSENT at the merge-base, and it decodes git.Core.Show of the merge-base revision's
roadmap_authority.dag -- not the worktree's. The two facts were bound in one commit: at
bfaaf3e^ (#7791) the function exists and the carrier is absent; at bfaaf3e the
carrier exists and the function is gone. So the arm's own guard implies the old spelling is
present in the text it is about to read. Correct for exactly the revisions where it can
fire, unreachable everywhere else.

The probe question, answered rather than assumed: the pin to 9ce6526 is LEGITIMATE and
stays. That revision is an ancestor of bfaaf3e^, carries the function, and has no
carrier -- a faithful representative of the revision class the decoder serves. A live probe
that reddens on a rename would assert a property the code never claimed. The #[ignore] is
separately declared (live-corpus) and hid nothing.

The class this actually names is a census-method class: a spelling-keyed decode whose
subject is a REVISION-ADDRESSED text must be joined against the revision it reads, not
against HEAD. It narrows to P4; P1/P2/P3/P5 decode values from the current resolved graph,
so the HEAD join is right for them. Recorded because a reader copying the method would
repeat the error.

Also states what the rung refinement costs: priced by deferred detection, not corrupted
output.

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

* Second pass: P4 residue closed by subject-revision attribution, two more primitives found, and the shape axis named

ATTRIBUTION. All 42 literal-entry run_in_context sites -- the first pass said 30; a stricter
re-extraction finds 42 -- attributed by reading each caller's context construction.
17 HEAD/live-worktree (ctx from default_source_roots/workspace_root), 24 in-file synthetic
source (the module text and the entry name are authored in the same expression, so a rename
edits both and they are not exposure at all), and exactly 1 revision-addressed: the
carrier-introduction bootstrap already adjudicated. The residue is closed; all 17 HEAD-subject
names resolve today.

TWO MORE PRIMITIVES, found by this pass rather than the first sweep, taking the set 5 -> 7.
P7: entry/function names passed as subprocess ARGV (--entry/--function) -- 35 sites in 3
files, larger than P4 and completely invisible to a run_in_context grep. All HEAD-subject,
all 11 names resolve. P6: str::replace rewriting a live-HEAD .dag file's own text, 2
producers / 6 call sites, and the ONLY primitive in the set that can fail OPEN -- replace
returns the input unchanged on a miss. Its two arms differ: a missed module-path rewrite hits
the module-path collision wall (loud); a missed /tmp-path rewrite makes the witness write to
the shared path instead of its scratch dir (silent). Scored per arm, not per primitive.

THE SHAPE AXIS, named as residue on the design authority's ruling. The class is any change to
a .dag schema element consumed reflectively by host code, not renames alone -- including
changing optionality, cardinality or shape while preserving every identifier. All seven
primitives are NAME-keyed, so that axis passes every one of them and still breaks the decode:
the population measured here is the NAME-KEYED SUBSET. Not hypothetical -- the retracted
instance was half a shape change, since the .dag side also moved List<T> to a Load coproduct.
The ceiling is unaffected: a generated typed decoder makes a shape change a type error exactly
as it makes a rename one. The ceiling is right; only the census is narrow.

Also adopts the four routes by which a compiler-silent class becomes operationally silent:
the path does not run, a fixture bypasses it, a default absorbs the mismatch, or a stale
artifact answers instead.

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

* Close P6's fail-open: every scratch rewrite of a live .dag now refuses on an absent pattern

str::replace returns its input UNCHANGED on a miss, so an ordinary edit to a .dag literal
made interp_recorded_fixture_witness's scratch copy silently unrewritten -- DESIGN section 5's
failure arm that widens instead of refusing. Two arms, only one loud: a missed module-path
rewrite produced a same-name duplicate the module-path collision wall refuses, while a missed
/tmp rewrite made the witness write to the SHARED path instead of its per-run scratch
directory, unreported, landing as cross-run interference in another session's witness.

Being caught by a neighbouring wall is a property of that wall, not of this rewrite, so all
five substitutions across both producers now route through `substituted`, which refuses when
the pattern is absent and names the PATTERN and the SOURCE FILE -- whoever trips it will be
editing the .dag with no reason to know a Rust harness depends on its literal text.

Evidence, 3 passed remotely: a discriminating RED (pattern edited out -> refuses, asserting
the refusal names both), a positive control (pattern present -> substitutes), and a
COUNTERFACTUAL running bare str::replace on the identical input and asserting it answers with
the source unchanged and no error. The counterfactual is load-bearing: `substituted` did not
exist before, so the RED alone would show only that a function which refuses, refuses.
Placement stated honestly in the doc -- these run under cargo test --workspace, not the
required lane, which is --lib only.

The census also records that the primitive is not repository-specific: the script adding that
counterfactual used a Python str.replace whose pattern did not match, silently changed
nothing, exited 0, and produced a green remote run of the two tests already present. `2
passed` rather than 3 was the only tell.

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

* Record the class reproducing on the author, inside the repair, within the hour

Promoted from a parenthetical to its own section, because it is the only observation in the
document that shows the class ARISING rather than being inventoried, and it happened to the
person writing the inventory.

Adding the counterfactual test to the P6 repair -- the repair whose entire subject is
str::replace failing open on a missed pattern -- the editing script used a Python str.replace
whose pattern did not match, because \n and \" escaping differed between script and file.
Every signal agreed with success: the call returned a string (the file, unchanged), cargo fmt
reported "modified 1 file" from an unrelated reformat, the command exited 0, and the remote
run came back GREEN because it ran the two tests that already existed. `2 passed` where 3 was
expected was the entire discriminating signal, caught by reading the run's test NAMES rather
than its exit code.

Three consequences, none about Python. The primitive is not .dag- or seed-specific: it
reproduced in a different language against a different file one hour after the paragraph
explaining why it is dangerous was written, which is the strongest available evidence that the
population estimated here is a FLOOR rather than a ceiling -- the class needs only a
substitution whose failure arm widens, not the seed/authority seam. Assert the pattern is
PRESENT before replacing; the edits that worked did, the one that vanished did not, and that
is the whole difference. And a green run is not evidence a test EXISTS -- a run reports the
tests that are there, never the ones you meant to add, so a vanished edit and a passing suite
are indistinguishable by colour, exit code, or any single-number summary. Same shape as a
vacuous `0 passed; 0 failed; N filtered out`; both are caught by arithmetic, not suspicion.

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

* Record two review-instrument findings, and reframe the census as one seam of a non-seam-specific primitive

The census began as a census of a seed defect. The P6 incident shows the class is not
seam-specific -- it needs only a substitution whose failure arm widens, and reproduced in a
different language against a different file within the hour, on the author repairing it. So
what is measured is ONE SEAM of a primitive that is not seam-specific, and the population is
a FLOOR rather than a ceiling. Stated up front rather than left as an inference from the
incident section.

Separately, docs/plans/review-instrument-observations.md records two findings about the
review and merge-readiness tooling, kept out of the census proper because they are facts
about the instrument rather than about the seed.

(1) stale_provider_count does not fire even where both operands are local. One payload
carried the approving review's sha, the dashboard's own head_sha, and gh's headRefOid as
THREE DIFFERENT VALUES, with stale_provider_count 0 and meets_approval_rule true. The lag
reading -- stale=0 means "not yet noticed", not "judged current" -- is true and worth knowing,
but incomplete: the approval sha differs from the dashboard's OWN head_sha in the same object
and staleness still reads 0. So comparing dashboard head_sha to gh headRefOid is necessary but
NOT sufficient; the reader must compare reviews[].sha to headRefOid themselves. A softer form
is also recorded: an approval can be superseded in PREMISE rather than in sha, as this PR's
was ("census-only, no code" over a head that later added code).

(2) A refused review burns its sha slot invisibly. Review 59066 failed with a worktree
freshness refusal -- correct behaviour, but the slot is consumed and never retried, leaving no
trace in approvals, request_changes or stale_provider_count. The pairing is the finding: one
half of the system refuses to review unless its checkout matches the PR head exactly, while
the other half reports staleness as 0 across a three-sha spread.

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

* Correct two internal contradictions the census introduced when it grew from five primitives to seven (review 59095)

Both findings are real and both were introduced by APPENDING P6/P7 without revisiting the
sentence and column that described the set of five.

(1) "All of them ultimately bottom out in four InterpContext methods" was true of P1-P5 and
false the moment P6 and P7 were added: P6 is a str::replace over file text and P7 is a
subprocess argument vector, and neither touches InterpContext. Corrected -- and the correction
carries the reason rather than just the fact, because it is the census's own thesis: every
earlier sweep was keyed on the interpreter surface, so a name crossing into .dag by any other
route was outside the search BY CONSTRUCTION. That is exactly why P6 and P7 were missed. The
primitives are grouped by the ROUTE a name takes, not by a shared implementation.

(2) The P6 row read "2 producers, 6 call sites" in a column whose other rows count
substitution/decode sites, next to prose saying "all five substitutions". Those are two
different quantities -- five substitutions inside two producer functions, which are reached
from six call sites -- but the column made them read as a contradiction. The row now states
the substitution count in the column's own unit (5, as 3 + 2) and names the other two
quantities inline; the prose and the residue entry agree with it.

Verified against the code: 8 `substituted(` occurrences = 1 definition + 3 in
unique_fs_witness_entry + 2 in closure_scale_witness_entry + 2 in the tests.

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

* Correct my own overstated claim about stale_provider_count: a later payload refutes it

I wrote that the staleness comparison "does not fire even when it has everything it needs",
on one payload. A later payload on the same PR reports stale_provider_count: 1, correctly
marking the provider whose latest review is behind head. So the field is not inert and that
sentence is wrong as written.

Both observations are now tabulated, and the hypothesis that fits them is labelled as a
hypothesis rather than a measurement: staleness is likely evaluated at review INGEST against
the head known then, and stored, while head_sha is read live at query time -- under which the
first observation is a stored verdict that went false underneath rather than a comparison
that failed to run. Distinguishing the two would need a payload sampled at a known ingest
boundary, which has not been done.

The operative rule is unchanged, which is why the correction does not disturb it: a
stored-and-gone-stale verdict and a non-firing comparison are indistinguishable to a reader,
and both report 0 on an approval that is not on the current head. Compute
reviews[].sha == headRefOid yourself.

Also records the same shape for request_changes_count: a codex REQUEST_CHANGES vanished from
the counter after the next push. Its findings WERE addressed in that push, but the counter
would read 0 either way, because a REQUEST_CHANGES is superseded by any push regardless of
whether anything was fixed. The commit and the reply are the evidence; the zero is not.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.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.

0 participants