Repository navigation
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 intoSep 3, 2026
Conversation
…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
…s own cycle argument
…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
…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
…9-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
Bot
deleted the
session/snappy-koi-879-wet-evidence-extraction
branch
September 3, 2026 02:43
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
6 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)LocalRepoWetScheduledbecomesWetScheduledClaim, the qualifiedidentitystring becomes aWitnessIdentity { module_path, function }record, andexpected: LocalRepoWetExpectPassedbecomesexpectation: ExpectedToHold {}.The identity change removes a duplicated fact rather than adding a field: every row's old
identityended in"." + function, so the qualified name was authored twice per row and could disagree with itself. It is now derived bywitness_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_modulewas derived by stripping".{function}"off the identity, which silently accepted a row whose identity and function disagreed; it now comparesidentity.functionto the row'sfunctiondirectly and refuses with "one member, two names".ExpectedRedstill 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_ADMISSIONSheld 57 rows for #10077'srequired_floor -> floor_terminal_ledgerrelocation. #10077 merged asa4a6db175d2, 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=53934adc676head=f5b67e3e677:The deleted rows were all
TargetChanged, and this branch produces noTargetChangeddelta. 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,staleandconsumedonly in its refusal line; onADMITTEDit prints the verdict alone. Sounadjudicated = 0is measured — a nonzero is precisely what would have made that line a refusal.stale = 0andconsumed = 0are 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 theirmodule_pathandfunctionverbatim. Which witnesses that lane admits is #10055's authority; how the roster is spelled is this branch's.Their
module_pathomits the.hostsegment their entry path carries, which looks like an entry-module disagreement and is not one: the file declaresmodule test.claim.host_cli_dependency_wet_witness_teston line 1, and every sibling indag/test/claim/host/likewise omits the segment — paths are discriminators, not gospel (DESIGN §3). Predicted statically, then confirmed by execution:Membership prose and roster agree at 13 rows in three groups.
Verification
Run
33700975802, head-matched onf227058a:lane=build phases_run=2 failed=0,lane=witnesses phases_run=3 failed=0, zeroFAILED PHASElines,namespace-wave-admission ADMITTED.route_gap_held=49is 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