Repository navigation
Rename the self-host ArtifactIdentity: two concepts shared one name, and it only broke whole-tree - #8177
Merged
Conversation
…and it only broke whole-tree
`ArtifactIdentity` is declared twice in the corpus as two unrelated concepts:
`std.cache_interface` `ArtifactIdentity<T>`, a long-standing record
`{subject_digest, artifact_kind}` that the cache and realization corpus is built
on, and `v2.compiler.self_host.generation` `ArtifactIdentity`, a coproduct
`ArtifactMaterialized | ArtifactNotMaterialized` introduced by #8153. That is the
§3 nicknaming violation in its collision form — one name, two authorities.
WHAT IT BREAKS. Three import-less witnesses reference the name bare:
`dag/test/claim/realize_kernel_test.dag`, `reconcile_in_process_cache_test.dag`,
and `hermetic_fixture_realization_test.dag`. Once both declarations sit in one
pool the reference is ambiguous and resolves to nothing, so compile-clean refuses
with five `unresolved type 'ArtifactIdentity'` errors.
WHY IT STAYED HIDDEN, which is the part worth keeping. It manifests ONLY
whole-tree. #8153's own PR run and main's post-merge run both carried `.dag`-only
diffs, so compile-clean ran scoped to the affected shard-entry closures — where
only the cache declaration is reachable and the name is still unique. Executed
evidence for that reading rather than inference: on a branch that DOES contain the
new declaration, `realize_kernel_test` `witness_verified_hit_reuses` resolves and
returns `true` at entry-closure scope. The failure appeared the moment two
unrelated PRs touched a `.rs` file and thereby forced the whole-tree baseline; one
of them (gunbc#8173) changes no `.dag` at all, which is what proves neither PR
caused it.
THE FIX IS AT THE ROOT, not the call sites. The three witnesses did nothing wrong,
so patching them would leave the collision live for the next bare reference.
Renaming the newly-introduced type restores global uniqueness: 7 sites across 3
files, all landed today.
Green by execution: the renamed
`self_host_generation_identity_witness_test.dag`
`generation_identity_agrees_with_itself` returns `true`.
NOT PROVEN BY THIS PR'S OWN CI: this diff is `.dag`-only, so its compile-clean runs
SCOPED and does not exercise the whole-tree pool where the collision lives. The
whole-tree control is any PR touching a `.rs` file — gunbc#8167 and gunbc#8173 both
qualify and both currently red on exactly these five errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZrxLJrt8ASGLegiK72fAi
`git add -u` swept in a lockfile regeneration that has nothing to do with this change: main's `Cargo.lock` lists `serde`/`serde_json` under `v1-compiler-tests` and cargo removes them whenever it rewrites the file in a clean worktree. That is pre-existing drift on main, not a consequence of renaming a type, and a fix for a red tree should be exactly the fix — nothing bundled that a reviewer has to separately reason about (review 51314 spotted it). Reverted to main's `Cargo.lock`. If the drift is real it deserves its own change with its own justification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ZrxLJrt8ASGLegiK72fAi
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 12, 2026
All three conflicts are the ArtifactIdentity collision I healed in d3e42a1, which main healed independently in #8177. Main chose GeneratedArtifactIdentity where I chose SelfHostArtifactIdentity, so all three files resolve to main's side and my rename dissolves. That is the second time today my repair of a main breakage raced a parallel fix (the first was #8151's Memory rename). Both times the collision was real and unfixed when I checked, and both times someone else was fixing it in the same window — the check that would actually prevent the duplicate work is looking for an in-flight PR touching the same symbol, not just the state of main. Verified after resolution: the belt work is intact (revision admission, tick partition, multiplicity arm all present), and the three generated artifacts main touched resolved to main's side byte-for-byte. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 12, 2026
classify_source returns FrontendStage; p_hex_escape and its five siblings still declared -> String, so every one of them was a type error waiting for the next compile-clean run. I changed the classifier's result type and updated its structured consumers without checking the six one-line probes at the bottom of the same file. Found by the planning side reading the file, not by me re-reading what I had edited. Also merges origin/main, which now carries #8177 — the rename of the OTHER ArtifactIdentity. That PR touches self_host_generation_identity_witness_test, self_host_promotion_admission_ witness_test and self_host/generation.dag: exactly the files I retracted imports from on #8149, and none of the three I kept. It confirms the homonym split rather than superseding those edits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 12, 2026
main is broken, not this branch. #8176 added dag/gunbc/self_host_artifact_materialization.dag importing ArtifactIdentity from v2.compiler.self_host.generation; #8177 then renamed that declaration to GeneratedArtifactIdentity without updating this consumer. My copy of the file is byte-identical to main's, so the compile-clean failure here is inherited rather than introduced. Swept the import, the return type, and both witness files together. THIS IS THE THIRD MAIN BREAKAGE OF THIS CLASS TODAY — Memory (#8129), ArtifactIdentity (#8153), and now the incomplete repair of that same rename. Two were new bare names colliding with existing ones, and this one is a rename that moved a declaration without its consumers. All three share a mechanism: a change is checked against the module it edits, while the breakage appears in modules it does not. Under namespace-only resolution that is mechanically decidable from the containment tree — a symbol that resolved before an edit and does not resolve after it is a whole-tree fact a lens can compute. Worth a lens rather than a fourth repair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
added a commit
that referenced
this pull request
Aug 12, 2026
main is red. #8177 renamed `v2.compiler.self_host.generation`'s `ArtifactIdentity` to `GeneratedArtifactIdentity` and swept the consumers that existed when it was written. #8176 had added another one two minutes earlier, so the rename left it importing a name that no longer exists: dag/gunbc/self_host_artifact_materialization.dag:32:3: error: name 'ArtifactIdentity' not found in module 'v2.compiler.self_host.generation' Three lines in one file: the import, `artifact_identity_of`'s return type, and one prose mention that cited the retired symbol (a stale citation is the class DESIGN §3 names -- the name no longer resolves, so leaving it would rot silently). The two witness files need no change: they call `artifact_identity_of` qualified and match on `ArtifactMaterialized` / `ArtifactNotMaterialized`, which are VARIANT names a type rename does not touch. Discriminating control, both directions: pre-sweep -> exit 1, name 'ArtifactIdentity' not found in module post-sweep -> exit 0, PASS a_failed_build_does_not_materialize_even_with_a_good_digest Corpus scan: no other reference to the renamed symbol remains. The surviving `ArtifactIdentity` uses are `std.cache_interface`'s unrelated `ArtifactIdentity<T>` and `spark_serving_release`'s own `RuntimeArtifactIdentity` / `ModelArtifactIdentity` -- distinct concepts that share a word, which is what allowed the original collision. Not fixed here: #8176 and #8177 were each correct against their own base and each passed their own CI. The break exists only in their composition, so no affected set either author could compute would have shown it. Claude-Session: https://claude.ai/code/session_01XxLzhuMisV3GAPpP2mBtSG Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 12, 2026
Brings in the ArtifactIdentity collision repair (#8177 + #8187) that main's whole-tree compile-clean was red on, plus the intervening merges. This PR's red was that inherited defect, not its own content: the identical five `unresolved type 'ArtifactIdentity'` errors appeared on a sibling PR containing no `.dag` change at all, and then on main itself at #8146. DESIGN.md was the only conflict, and it is resolved to main's copy deliberately: it is a PROJECTION of `dag/gunbc/design_document.dag`, which merged cleanly. Hand -merging a generated artifact would author bytes no authority produced; the `heal_generated_artifacts` job re-projects it from the merged authority, as it already did on this branch in f7ddaef. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ZrxLJrt8ASGLegiK72fAi
6 tasks
briansrls
added a commit
that referenced
this pull request
Aug 12, 2026
…ssions (#8132) * PRESS-0 step 3: dispatch admission asks whether a PROCESS is running, not whether a tmux name exists A retained remain-on-exit pane — the exact artifact the spawn path creates — answered "live" to belt_node_session_live, so a click returned DispatchAlreadyLive through the accepted band: a positive acknowledgement, no agent, and no refusal anywhere to count. That is worse than the selection refusal beside it, because an accepted no-op has zero observable frequency by construction (DESIGN §5, the absorbing fallback wearing an idempotence label). No new observer was built. The pane probe, the pane-to-evidence projection (worker_process_from_attempt_pane) and the per-session join (worker_process_for_session) already existed and already carried the refusal semantics, and the sessions UI route was already using them via belt_sessions_observe_for_instance. The defect was that the two ACTUATION sites — belt_tick_for_instance and belt_dispatch_node_for_instance — read the ls-only observation whose worker evidence is the placeholder whose own reason reads "tmux name presence does not observe pane liveness or exit". One concept had two answers in one module (§3); this routes both actuation sites through the answer that observes. Liveness is four-valued, not Boolean: Running may suppress a spawn; StaleTerminal is refused and never suppresses the new turn; LivenessUnobserved refuses rather than spawning blind, because a spawn under unknown liveness is how one node acquires two workers; Absent is the ordinary spawn path. Collapsing Unobserved into either pole is the state-space conflation that produced this defect one level down. Witnesses: the prior witness_node_session_live_true asserted exactly the behaviour that is now wrong, so it is replaced rather than kept beside the new arms. The discriminating red is witness_retained_dead_pane_is_not_live, which returned true under the old check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix: render the exit code with to_string, the idiom this module and its sibling already use int_to_string exists only in src/v1/02_parse.dag, takes value: rather than n:, and no module under dag/gunbc/ calls it — the sole occurrence of int_to_string(n:) in that directory was the line this fixes. dispatch_process_cleanup_separation_note's own retained-pane message renders the same field as to_string(exit_code). Also restructures the liveness filter to the let-then-first form worker_process_for_session uses, rather than an inline parenthesized pipe-then-method that appears nowhere in the corpus. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Admit the two new dispatch arms into the wire-roster size pin dispatch_button_terminal_states maps over belt_dispatch_all_status_labels, so button totality and the terminal count both follow the roster by derivation and needed no edit. What failed is the third clause of witness_dispatch_button_states_total_over_wire: the absolute roster size, pinned so a new arm cannot enter the wire vocabulary without a conscious update to the surface that renders it. The subject genuinely grew by two (stale_session, session_liveness_unobserved), so the pin moves 8 -> 10. The pin is doing exactly its job here and is not being loosened: it is a controlled fixture over a hand-authored closed roster, and it stopped a wire-contract change from landing silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Announce a population budget refusal before the work that can swallow it A budget exhaustion killed the CI floor and said nothing. Run 31474198106 on PR #8132 ran the floor step for exactly 55m01s against gunbc_ci_ordinary_floor_budget_minutes = 55, exited 1, and emitted no OVER-BUDGET line anywhere in the attempt log. The step's own output stops mid-measurement three minutes before the process dies. The wall itself is correct and is not being weakened here — only its announcement is. A fail-closed mechanism whose diagnostic is missing is worse than a loud one: the refusal still fires, but readers cannot see why, so they reach for whatever number is nearby. In this incident that was a cgroup peak RSS reading sitting next to the silence, and the failure was diagnosed as a memory kill that never happened — by me, in the PR thread, before the operator corrected it. That is the cost being paid: the silence does not just withhold a cause, it manufactures a wrong one. TWO CANDIDATE LOSS MECHANISMS, both closed by this ordering without having to decide between them. The receipt write ran first and its path was interpolated into the message, so a stalled write under a loaded runner delays the only announcement past process death. And the message took an explicit stderr lock guard held across the write — a lock the main thread holds while streaming its own receipt phases, which is precisely what the floor was doing in the window this fired in. A watchdog must never wait on a resource held by the thread it polices. So the announcement now goes first: a ::error:: annotation on stdout so the cause reaches the run summary rather than only line ~7000 of a step log, the detail line on stderr, both flushed, and the receipt write afterwards where failing to write it can no longer suppress the diagnosis. NOT ESTABLISHED, stated so the next reader does not inherit a guess: which of the two mechanisms actually lost the message. Neither was reproduced. The fix is ordering that makes both harmless, not a repair of a located defect. Also merges origin/main (d3203de). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Heal main's two §3 name collisions from #8129 (compute_board) #8129 landed `Memory` as a `BoardCapability` variant and `unsatisfied_capabilities` in `product.compute_board.composition`. Both names already existed corpus-wide — `std.measure` `Memory` and `gunbc.auth.github_credential` `unsatisfied_capabilities` — so under namespace-only resolution the bare references stopped resolving uniquely and the whole-tree compile went red in four files: dag/gunbc/ci_budget_tree.dag unresolved type 'Memory' dag/product/budget_tree.dag unresolved type 'Memory' dag/test/claim/budget_tree_witness_test.dag dag/test/claim/github_app_registry_witness_test.dag 'unsatisfied_capabilities' not found It was invisible on main because every push run there was cancelled, so no floor run completed on the merge commit. The newer, narrower names move: `Memory` -> `MemoryCapability`, `unsatisfied_capabilities` -> `unsatisfied_board_capabilities`. The long-standing authorities keep their names. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * PRESS-0 step 1: dispatch reads back the deployed revision before it acts gunbc.fleet_desired_observe fleet_revision_standing already decided the revision cell three ways, and its only consumer was gunbc.fleet_converge_cli. So convergence knew srv1 was drifted while the served page dispatched against the drifted tree anyway and said nothing — a worker spawned from code the fleet never admitted, reported as an ordinary spawn. The standing is now consulted FIRST in belt_dispatch_node_for_instance, before node lookup and before session observation, because the roster, the signoff and the command all come from that tree; refusing later would mean refusing after acting on it. Both non-converged arms refuse: DispatchRevisionDrifted carries BOTH revisions (the membership-diff `from` ruling — a catch-up needs the prior) and DispatchRevisionUnobserved carries its cause. Neither reaches the ok band. Serve maps drift to 409 and unobserved to 503. The repo path is instance.repo_root, not the srv1_gunbc_repo_root constant converge uses — hardcoding srv1 would mint a second path authority and answer for the wrong host on any other instance. SCOPE: the new witnesses are projection-grain — never-ok, loud band, drift names both revisions, unobserved names its cause. Whether the gate FIRES on a live drifted srv1 is a wet fact and is owed a wet receipt; it is not established here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Extend the band-fold vocabulary pin to the four new dispatch labels Run 31528394784 red batch 4: witness_band_fold_pins_operator_vocabulary pinned belt_dispatch_label_band_rows().length() == 8, and dashboard_instance_dispatch_contract_keystone_holds failed only as its conjunct. The four arms added by step 3 (stale_session, session_liveness_unobserved) and step 1 (deployed_revision_drifted, deployed_revision_unobserved) each get their band assertion; all four are loud, so the ok set is unchanged and witness_ok_labels_derive_from_band_fold still holds. This is the second roster I missed after adding arms — I fixed d4_wire_contract_labels and the button wire pin but not this one. Swept the class this time: the only other pin over these rosters is roadmap_sandbox_witness_test line 75, which is relative (all_status_labels().length() - 1) and self-adjusts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Extract the revision-admission fold, refuse duplicate sessions, delete the Boolean collapse Handback items 2, 3 and 5 from review artifact 51237. EXTRACT THE GATE (item 4). The revision check lived inline in dispatch, so every witness constructed DispatchRevisionDrifted directly and asserted its label, band and JSON — which would stay green if the fleet_revision_standing match were deleted outright. A test that survives removal of the thing it tests is not testing it. belt_revision_admission is now a pure FleetRevisionStanding -> BeltRevisionAdmission fold with the effectful read left at the call site, driven through all three arms by witness. It is also the shared seam the tick path will consume. 0/1/MANY (item 3). belt_node_session_liveness filtered by node id and took first(), so two sessions claiming one node resolved to whichever tmux listed first: [running, stale] answered running, [stale, running] answered stale. Same observation, two orders, two verdicts, no signal a choice was made. It now refuses with BeltSessionMultiplicityConflict naming every session, routed to a new dispatch arm, loud band, 409. The discriminating witness asserts order-independence directly. DELETE THE BOOLEAN (item 5). belt_node_session_live answered Bool over a four-state lifecycle, mapping StaleTerminal, LivenessUnobserved and Absent all to false though the module's own note says those three have different remedies. It had no production caller. Its four witnesses are re-pointed at belt_node_session_liveness and are STRONGER for it: each now asserts the exact arm rather than mere falsity. Rosters swept together — exemplars, d4_wire_contract_labels, the band-fold pin and the button wire pin all read 13. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Make the scheduled belt tick lifecycle-aware and revision-gated Handback P0s 1 and 2 from review artifact 51237: the dead-pane fix and the revision gate reached the click path only, and the timer is a dispatch actuator exactly as much as the button is. The tick passed the whole observed session list into belt_reconcile, which maps EVERY present row to an observed member and counts live.length() as occupied. So a retained dead pane stayed a member (desired + present = Unchanged, no respawn) AND consumed a capacity slot. It also never consulted the deployed revision, so a drifted host refused a click while spawning on a timer. Both paths now consume belt_revision_admission. A refused revision blocks spawn and reap ONLY — verification and publication continue, because they read independently bound receipts and already run when tmux observation refuses; stopping them would widen a spawn-admission refusal into a work stoppage. Cleanup precedes start, deferred one tick. A stale pane leaves observed membership, so its node becomes a spawn candidate — but spawning it this tick would collide with the tmux session name that still exists, because spawn runs before teardown. So the stale node's spawn is withheld and its teardown planned; the next tick observes it Absent and spawns normally. That is why this is a partition plus a deferral, not a filter. LivenessUnobserved and MultiplicityConflict are neither cleaned nor spawned. An unobserved pane might be running, so teardown could kill live work and spawning beside it could double-run the node. Witnessed at the belt grain rather than on a helper Boolean: the partition the tick feeds to belt_reconcile puts running, stale and unobserved in three different buckets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix the witness import block I split in the wrong place (review 51277) CONFIRMED, and it was the worst possible failure for this PR. My edit inserted the fleet_desired_observe import in the MIDDLE of the roadmap_belt_actuate import list, closing that block one line early. So DispatchRevisionDrifted, DispatchRevisionUnobserved, belt_dispatch_result_ok, belt_dispatch_status_label, belt_dispatch_result_json_value, the label/band/exemplar roster accessors and the preflight symbols all sat inside `import gunbc.fleet_desired_observe { … }` while being defined in roadmap_belt_actuate.dag. The module could not resolve its imports, so NONE of the new evidence would have run — the revision-admission fold, the multiplicity conflict, the tick partition, all of it. A PR whose entire claim is "these gates are proven by execution" would have merged with zero executing witnesses. That is specification-without-execution exactly, and it is the failure mode I have been quoting at other people all session. fleet_desired_observe now exports only RevisionConverged / RevisionDrifted / RevisionUnobserved. The belt_actuate symbols are back in their own block, and I merged the two rather than leaving a split import authority for one module. Every imported symbol re-verified against its defining module by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Make the tick honor its own lifecycle note (review 51285) BOTH FINDINGS CONFIRMED, and the first is the worse kind: the note described behaviour the code did not have. `classified.refused` was partitioned and never read again, so only stale node ids were withheld from spawn. A ready node with an unobserved-but-present session therefore read as ABSENT to belt_reconcile and could be spawned beside a pane that might be running — the exact double-run the click path's DispatchSessionLivenessUnobserved arm exists to prevent, and exactly what my own note said was prevented here. The deferral set is now every non-running class. MULTIPLICITY IS A NODE FACT, NOT A SESSION FACT, which is why the first cut missed it: belt_tick_classify_sessions read each row's process state in isolation, so two rows for one node (one running, one stale) landed in two different buckets and the reconciler acted on an ambiguity dispatch refuses. The partition is now node-aware — more than one row for a node is a conflict whatever the rows say individually — so both actuation paths refuse the same ambiguity. Witnessed: an unobserved session yields no running row and no spawn candidate; two rows for one node classify neither as running. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Heal main's ArtifactIdentity collision (#8153), inherited via the main merge CI on press-0 failed with `unresolved type 'ArtifactIdentity'` in three files this branch never touched — realize_kernel_test, hermetic_fixture_realization_test, reconcile_in_process_cache_test. The cause is a §3 name collision, the same class as #8129's `Memory`. `dag/std/cache_interface.dag` has carried `ArtifactIdentity<T>` for a long time; #8153 added a bare `ArtifactIdentity` in `src/v2/compiler/self_host/generation.dag`. Under namespace-only resolution the long-standing bare references stopped resolving uniquely, so the consumers of the ORIGINAL type broke while the new declaration compiled fine. main still carries it and no open PR is fixing it, so this is not a duplicate of someone else's repair — I checked first, having duplicated #8151 earlier today. The newer, narrower name moves: `SelfHostArtifactIdentity`. The cache-interface authority keeps its name and its consumers resolve again. Swept its three files together — the declaration, both self_host witnesses, and the field and function signatures that referenced it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Sweep GeneratedArtifactIdentity into the consumer #8177 left behind main is broken, not this branch. #8176 added dag/gunbc/self_host_artifact_materialization.dag importing ArtifactIdentity from v2.compiler.self_host.generation; #8177 then renamed that declaration to GeneratedArtifactIdentity without updating this consumer. My copy of the file is byte-identical to main's, so the compile-clean failure here is inherited rather than introduced. Swept the import, the return type, and both witness files together. THIS IS THE THIRD MAIN BREAKAGE OF THIS CLASS TODAY — Memory (#8129), ArtifactIdentity (#8153), and now the incomplete repair of that same rename. Two were new bare names colliding with existing ones, and this one is a rename that moved a declaration without its consumers. All three share a mechanism: a change is checked against the module it edits, while the breakage appears in modules it does not. Under namespace-only resolution that is mechanically decidable from the containment tree — a symbol that resolved before an edit and does not resolve after it is a whole-tree fact a lens can compute. Worth a lens rather than a fourth repair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Pin HTTP status for all five new dispatch arms (review 51368) CONFIRMED. roadmap_serve.dag maps the five new arms explicitly — stale session, revision drift and multiplicity conflict to 409, liveness unobserved and revision unobserved to 503 — but witness_dispatch_status_total still pinned only the original eight. A regression mapping any new arm to 200 would have stayed green. Worse than an absent pin, because status_map_red_note claims each refusal variant maps to ITS status so a collapse reds exactly one row. A reader checking whether the mapping is guarded found a sentence saying yes while the assertion had stopped covering half the arms. WHY THIS ONE WAS MISSED when the label, band, exemplar and button rosters were all swept: it lives in a different witness module and is keyed by CONSTRUCTED variants rather than by a roster the compiler counts, so nothing goes non-exhaustive when an arm appears. That asymmetry is the real defect and it is recorded on the note — until this assertion derives from the exemplar roster the way the label set does, it has to be swept by hand beside it. 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 <11205878+briansrls@users.noreply.github.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 12, 2026
Four conflicts, all expected from the stack. The three self_host files are my SelfHostArtifactIdentity rename, which main healed independently as GeneratedArtifactIdentity in #8177 — main's name wins and mine dissolves, same resolution press-0 took. roadmap_belt_actuate.dag is the real one: #8132 carried the string-arm partition helper to main, and this branch replaces it with the coproduct version (review artifact 51309). Kept this branch's side, after checking that every main-only line it drops is something deliberately removed or corrected here rather than main content going missing — the dead belt_observe entry points and the stale "8th variant" roster note, both from the dissolve commit that landed on this branch rather than press-0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
pushed a commit
that referenced
this pull request
Aug 12, 2026
* wip: exact-head frontier probe + durable classification witness * Re-cut the native-selected-witness-bundle first_slice on exact-head re-observation * Home the reproduction instruments outside the test module, and stop relabelling genuine body-lowering rejections as retention * Wire the production '//' annotation channel into dag_lex_rules, with LexArtifact-grain controls * Record the two repairs against the exact-head observation they were measured from * Close both evidence gaps: a constructible rejection-propagation pair, and comment newline termination * Close the review findings: complete semantic-erasure controls, honest partial receipt, one consistent roadmap sequence, scratch residue deleted * chore: regenerate drifted generated artifacts (ci auto-heal) * Report the retention population as 4-at-observation and 3-after-repair, and render the rejection arm * Close the second review round: classifier controls, Rejected-arm assertion, read contract recorded not fabricated, roadmap by time grain, resolve-ready naming * Delete rejected_reasons, dead since the assertion moved onto the Rejected arm * Fix all three floor refusals: brief budget via cited note, doc-graph link, and over-budget stage claims deleted with the gap recorded * Bring the boundary brief back under its 100-word budget, and attribute two walls by minimal pair * chore: regenerate drifted generated artifacts (ci auto-heal) * Record the refuted attributions where they were asserted, not only where they were measured * Make two latent bare-reference dependencies explicit (Class B, exposed by this diff's compile-clean closure) * Complete the ArtifactIdentity bare-reference population: six files across four lanes * Retract three of six imports: two homonyms and a suffix match, not one semantic population * Complete the three partial import lists that a single named import had left half-bound The 13 witness failures at eff3eaa are caused by the repair, not exposed by it. All three modules were fully bare — every name resolved by pool-membership coincidence (DESIGN Class B). Naming ONE dependency changed the closure that gets assembled, and the extdeps catalog rows two of the witnesses read stopped resolving: `no such function: hermetic_fixture_file_facts`, etc. A partial import list is the worst of the two states: it neither binds the module's dependencies nor leaves the coincidence intact. So the repair is the whole list, and every symbol home below was verified at its declaration rather than by name search — std.cache_identity for the artifact kind ids and the receipt digest, std.cache_interface for the kernel types and route/write folds, extdeps.cache + extdeps.cache.types for the catalog projections, and the per-family extdeps realization modules for the rows themselves. realize_kernel_test stayed GREEN under the partial list its two siblings went red under, and is completed here anyway. Green under a bare reference is not evidence the reference is bound; it is evidence that this run's closure happened to contain it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK * Re-home three key-derivation variants I had imported from a module that only re-imports them ContentAddressedByValue, HandAuthoredString and NativeInternalHash are declared on std.cache_interface KeyDerivationClass; extdeps.cache.types merely imports them at the top of its own file. I read those lines as declarations because a name-ranked search returned them, which is the third instance in this PR of the same failure — a name-level observation standing where a bound declaration was required, the exact class the PR's subject is about. Every one of the 54 imported names across the three files is now checked against a declaration form (col-0 type/fn/data, or a variant on its coproduct) rather than against an occurrence. StructuredArtifact is declared inline on `type ValueShape` and is correct as imported. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK * Move two receipt-digest names into the block that declares them, and check the written file ExecutionReceiptDigest and execution_receipt_digest_of_value are declared on std.cache_identity; I wrote them into the std.cache_interface block. The previous commit's verification did not catch it because it checked a list of (name, intended module) pairs I held in mind, not the pairs the file actually contains — so it confirmed the homes I had already re-derived and said nothing about where the import statement put them. The check now parses the three files' import blocks and tests each (module, name) pair as written against a declaration in that module's source. 71 pairs, no remaining mismatch; the two StructuredArtifact flags are the checker's own blind spot for inline variant lists (`type ValueShape = RawBytes | StructuredArtifact | ...`), confirmed correct by direct read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK * Close the frontend probe's result space and make census completeness an identity join Increment 2 of the frontier lane, delivering the two structural halves and NOT the third. CLOSED RESULT SPACE. classify_source answered in String, so every consumer compared against a literal and a typo produced a verdict rather than a refusal. It now returns FrontendStage = LexRefused | ParseRefused | NormalizeGraftRefused | NormalizeRetained | NormalizeOther | NormalizeAccepted, with frontend_stage_label the single rendering authority. THE EMPTY-REASON-SET ARM IS GONE BECAUSE ITS INPUT IS. The classifier took List<Symbol> and had to answer NORM_REASON_SET_INVALID_EMPTY for the empty list. It now takes NonEmptyDiagnostics, whose head is not optional, so that input has no representation. The RED that fed it an empty list retires with the state rather than with the wall — the dissolution rule preserves a control when the invalid state stays writable, and this one cannot be authored at all without re-widening the parameter to admit the value it tests. Recorded in the file rather than left to be inferred from a deleted test. COMPLETENESS IS A JOIN. Fifteen hand-written m_* fns had no denominator: drop one and the remaining fourteen still answer, so 'all fifteen observed' only ever meant 'fifteen fns were present'. They are now rows on frontier_roster, and census_is_complete asks per roster label whether a matching observation exists. Three controls, including one that a count equality would pass — fifteen observations, none of them a roster member. NOT DELIVERED, and it is the increment's headline item: the live fifteen still cannot gate a PR. dag_grammar() construction alone exceeds the fast-lane budget before any member is read, so observe_roster stays a reproduction instrument in a non-test module and the durable claims run on synthetic specimens. The two stage-separation claims deleted last increment are still owed to the batched or realized instrument; what changed is that it now has a total verdict type and a denominator to join against. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK * Type the five empty list literals that fold_list's generic accumulator cannot infer compile-clean refused all five `empty: []` arguments: fold_list's empty parameter is the generic accumulator A, so a bare literal has no expected type to be a collection of. The corpus idiom is an explicit cast, used the same way in gunbc.package_delivery. Diagnostic returns to the witness import list — it was dropped as unused when the specimen helper stopped naming it, and the cast names it again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK * Reconcile the census in both directions: coverage was only the superset half census_is_complete asked, per roster label, whether SOME observation carried it. That is the covering direction alone, and I described it as an identity join, which it was not. A census of all fifteen members PLUS a stray sixteenth passed it, and so did one carrying a member twice — neither condition can make a per-label existence check fail. reconcile_census now joins both directions and at multiplicity, returning CensusReconciliation = CensusExact | CensusMissingMember | CensusDuplicateMember | CensusUnrosteredObservation with the offending label. The three failures are not one state: a missing member means coverage is short, an unrostered observation means the roster and instrument have drifted apart, and a duplicate silently doubles that member's contribution to every count_stage figure derived from the census. Collapsing them into one Bool is what would have made those figures unfalsifiable. census_is_complete is now DERIVED from the verdict rather than computed beside it, so there is no second Bool fold that could disagree with the coproduct. Four controls, two of which the previous version passed: the stray and the doubled census each contain every roster member. The notes claiming an identity join are corrected at the point of assertion rather than left for the next reader to discover. Found by a question from the planning side asking whether the census proved exact reconciliation or mere coverage. It proved coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK * Give the six minimal-pair probes the return type their callee now has classify_source returns FrontendStage; p_hex_escape and its five siblings still declared -> String, so every one of them was a type error waiting for the next compile-clean run. I changed the classifier's result type and updated its structured consumers without checking the six one-line probes at the bottom of the same file. Found by the planning side reading the file, not by me re-reading what I had edited. Also merges origin/main, which now carries #8177 — the rename of the OTHER ArtifactIdentity. That PR touches self_host_generation_identity_witness_test, self_host_promotion_admission_ witness_test and self_host/generation.dag: exactly the files I retracted imports from on #8149, and none of the three I kept. It confirms the homonym split rather than superseding those edits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK * Validate the denominator, make stage equality structural, and delete the unadmitted stage count Four repairs from an external review of the census increment. Three are defects I introduced. THE ROSTER WAS NEVER VALIDATED. reconcile_census checked observations against the roster and assumed the roster itself was sound. Two malformed rosters defeat that with CensusExact still reachable: a duplicated LABEL is satisfied by one observation counted once for BOTH rows, so fifteen rows with fourteen labels reconcile exactly against fourteen observations; a duplicated PATH under two labels is invisible at label grain entirely, and the live census would read one file twice and report it as two members. verify_roster now runs first and CensusExact is unreachable without FrontierRosterValid. It takes the rows rather than reading frontier_roster, because a validator that can only see the one live roster is unfalsifiable — it would report valid and nothing could establish it reports anything else. STAGE EQUALITY WAS THE RENDERER. frontend_stage_eq compared frontend_stage_label results, making presentation the semantic authority: a label rename would change which stages compare equal. Now structural; the pairwise-distinctness claim is about rendering only, which is what it should always have been. count_stage IS DELETED. It took the unreconciled population, so it would total a census missing a member, carrying a duplicate, or built on an invalid roster — the exact corruption the note beside it describes. It also had no consumer. Counting returns on a carrier only a reconciled census can construct, with the executing census that gives it a producer. The six manual probes now render through frontend_stage_label rather than returning FrontendStage. Their only consumer is a person invoking one by hand, so label text is the right interface; the typed vocabulary is for structured consumers. Four roster controls, including reordering, since row order is not a fact about the roster and must not become one about the verdict. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK * Enumerate frontend_stage_eq's arms: the wildcard form was unrostered non-fold residue The floor refused non_fold_residue_no_unrostered_or_stale. v2.lens.non_fold_residue counts a match on a FUNCTION PARAMETER whose type is a closed coproduct and whose body carries a top-level wildcard arm, in a non-test .dag. The structural equality I landed last push is exactly that shape — `match b { LexRefused => true _ => false }`, six times — so making equality structural introduced the residue in the same motion that removed the renderer from the equality path. Both facts are real and the resolution is neither a roster row nor a revert: enumerate the arms. All thirty-six positions are written out. The verbosity is the point rather than a cost being tolerated. Under wildcards, adding a seventh stage leaves this function compiling and quietly answering false for every comparison involving it — the same silent-widening the lens exists to catch. Enumerated, the addition fails to typecheck at all thirty-six positions, so a new stage cannot be introduced without deciding its equality against every existing one. Verified no match-on-parameter in the file carries a wildcard: the remaining ones scrutinise let-bound values or closure parameters, which the census does not count and which cannot hide a missing coproduct case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: gunbai-bot[bot] <289086189+gunbai-bot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
pushed a commit
that referenced
this pull request
Aug 12, 2026
#8177 renamed the self-host coproduct to GeneratedArtifactIdentity to end its collision with std.cache_interface's record. #8176 merged an hour earlier and added a consumer importing the old name, so neither PR could see the other and main went red on whole-tree compile-clean: dag/gunbc/self_host_artifact_materialization.dag:32:3: error: name 'ArtifactIdentity' not found in module 'v2.compiler.self_host.generation' Rename at the import, the return type, and the note that names the chain. Both witness families over the affected module pass by execution. 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>
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.
Main is red on whole-tree compile-clean. This is the root fix, and it is not caused by the PRs currently reporting it.
The defect
ArtifactIdentityis declared twice, as two unrelated concepts:std.cache_interfaceArtifactIdentity<T>— a long-standing record{subject_digest, artifact_kind}the cache and realization corpus is built on.v2.compiler.self_host.generationArtifactIdentity— a coproductArtifactMaterialized | ArtifactNotMaterialized, introduced by SH-A: Behavioral bootstrap and promotion authority #8153.One name, two authorities: the DESIGN §3 nicknaming violation in its collision form.
What it breaks
Three import-less witnesses reference the name bare —
dag/test/claim/realize_kernel_test.dag,reconcile_in_process_cache_test.dag,hermetic_fixture_realization_test.dag. Once both declarations share a pool the reference is ambiguous and resolves to nothing:Why it stayed hidden — the part worth keeping
It manifests only whole-tree. #8153's own PR run and main's post-merge run both carried
.dag-only diffs, so compile-clean ran scoped to affected shard-entry closures, where only the cache declaration is reachable and the name is still unique.Executed evidence for that reading, rather than inference: on a branch that does contain the new declaration,
realize_kernel_testwitness_verified_hit_reusesresolves and returnstrueat entry-closure scope. The failure appeared the moment two unrelated PRs touched a.rsfile and thereby forced the whole-tree baseline. One of them (#8173) changes no.dagat all — which is what proves neither PR caused it.The fix is at the root
The three witnesses did nothing wrong; patching them would leave the collision live for the next bare reference. Renaming the newly-introduced type restores global uniqueness — 7 sites across 3 files, all landed today.
Green by execution
The renamed
self_host_generation_identity_witness_test.daggeneration_identity_agrees_with_itselfreturnstrue.Not proven by this PR's own CI
This diff is
.dag-only, so its compile-clean runs scoped and does not exercise the whole-tree pool where the collision lives. The whole-tree control is any PR touching a.rsfile — #8167 and #8173 both qualify and are both currently red on exactly these five errors. They are the proof once this lands.🤖 Generated with Claude Code
https://claude.ai/code/session_018ZrxLJrt8ASGLegiK72fAi