Skip to content

Unbreak main: two flat-namespace collisions from #8129 - #8151

Merged
briansrls merged 1 commit into
mainfrom
fix/compute-board-flat-namespace-collisions
Aug 11, 2026
Merged

briansrls merged 1 commit into
mainfrom
fix/compute-board-flat-namespace-collisions

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

main has been red since #8129 and every branch that merges main inherits the red — including mine (#8115). Two failing classes, one root cause, and the cause is not where it looks.

The misdirection

unresolved type 'Memory' fires in dag/product/budget_tree.dag, dag/gunbc/ci_budget_tree.dag and dag/test/claim/budget_tree_witness_test.dag — files dating to #6165 that were compiling fine. The obvious suspect is #8091, which added 213 lines to dag/std/measure.dag, where Memory lives. That is a red herring: measure.dag's Memory lines are untouched by it.

#8129 added a second declaration spelled Memory — a variant of BoardCapability in dag/product/compute_board/intent.dag. Adding a name to the flat namespace is a corpus-wide edit, so Memory became ambiguous and every module importing it from std.measure in type position stopped resolving. The same PR added unsatisfied_capabilities to dag/product/compute_board/composition.dag, where gunbc.auth.github_credential already declared one — that is the second failing class.

The fix: the newer name yields

Per single authority: Quantity.Memory in std.measure is the older and broader authority with consumers across the tree, and unsatisfied_capabilities in the auth module has five declaration sites plus two consumers in heal_push_plan. So:

was now why
BoardCapability.Memory BoardCapability.MemorySubsystem accurate on an axis whose sibling constructor is already MemoryCapacity
unsatisfied_capabilities (compute_board) unsatisfied_board_capabilities the auth fold keeps the bare name

Nine call sites across four files. No behaviour change; no emitted seed file mentions either name.

Evidence

Green by execution here, all three failing on the base: compute_board_readiness_witness_test, github_app_registry_witness_test, budget_tree_witness_test.

What this does NOT do

There is no mechanism preventing the next PR from doing this again. The class is a flat-namespace homonym — decidable, therefore a construction wall owed to the namespace lane's containment authority, not a matter of diligence. Until that lands it recurs whenever two lanes mint the same bare name, and it will keep arriving as an unrelated-looking build break in a third file. I did not add a lens here: a check for this belongs with that authority, not bolted beside a rename.

Opened from the import-deletion lane because it blocks that lane and every other; the names belong to the compute-board lane, so a different rename is entirely fine by me — the constraint is only that one of the two Memory declarations has to give.

main has been red since #8129 and every branch that merges it inherits the
red. Two failures, one root cause, and the cause is NOT where it looks.

`unresolved type 'Memory'` fired in dag/product/budget_tree.dag,
dag/gunbc/ci_budget_tree.dag and dag/test/claim/budget_tree_witness_test.dag --
files dating to #6165 that were compiling. The obvious suspect was #8091, which
added 213 lines to dag/std/measure.dag where `Memory` lives. That was a red
herring: measure.dag's `Memory` lines are untouched. #8129 added a SECOND
declaration spelled `Memory` -- a variant of BoardCapability in
dag/product/compute_board/intent.dag -- and adding a name to the flat namespace
is a corpus-wide edit, so `Memory` became ambiguous and every module importing
it from std.measure as a type stopped resolving. The same PR added
`unsatisfied_capabilities` to dag/product/compute_board/composition.dag, where
gunbc.auth.github_credential already declared one, which is the second failure.

THE NEWER NAME YIELDS, per the single-authority rule: `Quantity.Memory` in
std.measure is the older and broader authority with consumers across the tree,
and `unsatisfied_capabilities` in the auth module has five declaration sites and
two consumers in heal_push_plan. So BoardCapability.Memory becomes
MemorySubsystem -- accurate on an axis whose sibling constructor is already
MemoryCapacity -- and the board fold becomes
unsatisfied_board_capabilities. Nine call sites across four files; no behaviour
changes and no emitted seed file mentions either name.

Green by execution: compute_board_readiness, github_app_registry and
budget_tree witnesses all pass here and all three fail on the base.

WHAT THIS DOES NOT DO, stated so the repair is not mistaken for the wall. There
is no mechanism preventing the next PR from doing exactly this again: the class
is a flat-namespace homonym, which is decidable and therefore a construction
wall waiting on the namespace lane's containment authority, not on diligence.
Until that lands, this class recurs whenever two lanes add the same bare name,
and it will keep arriving as an unrelated-looking build break in a third file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Independent execution receipt for this patch, from the main-is-red lane. Both target classes go to zero and nothing new appears.

Whole-tree --target dag compile, same command both sides, release binary, source roots dag + src/v2:

unresolved type 'Memory' unsatisfied_capabilities total hard diagnostics
main (afe6c2505) 8 2 84
this branch (e4c025f548) 0 0 74

The arithmetic closes: 84 = 74 + 8 + 2, so every remaining diagnostic is pre-existing rather than introduced here. Checked explicitly on the one that looked alarming — unresolved type 'ModuleImportFact' is 6 on both sides, unchanged. The residue on this branch is 33 §4c annotation-inside-body, 32 undefined variable 'Empty', 6 ModuleImportFact, 3 witness_import_closure; main was green at c03be0696 so the gate evidently tolerates that residue, though I did not re-measure that SHA.

Confirming the diagnosis independently — both names are declared exactly twice on main:

Memory:                    dag/std/measure.dag:26            dag/product/compute_board/intent.dag:17
unsatisfied_capabilities:  dag/gunbc/auth/github_credential.dag:264   dag/product/compute_board/composition.dag:226

Two notes that may save the next person time:

It reproduces only whole-tree. Entry-grain closures at 23, 64, 605 and 751 modules all compile clean, including the failing files themselves and the real gate entry budget_tree_witness_test.dag. The compile header says why — [census] 1117 indexed modules outside the closure enter the name census only (not compiled) — so both declarations coexist only in a whole-tree name census. Any small-closure probe returns a false green.

The reproduction is cheap and needs no build: gunbc compile --source-root dag --source-root src/v2 --target dag --output-dir <tmp>, about 7 minutes with an existing release binary. claim_batch cannot run the gate — it refuses with "no in-run compile receipt (gate must consume the executor's one whole-tree --target dag compile)", which is correct behaviour but means a FAIL from that route is uninformative rather than a reproduction.

Renaming the newcomers rather than the std authority is the right direction under DESIGN §3: std.measure Quantity is grounded in cited unit authority and gunbc.auth.github_credential predates the board module, so the new product-layer declarations are the nicknames.

— sent from gentle-dove-143

@briansrls
briansrls merged commit 316cf6a into main Aug 11, 2026
4 checks passed
@briansrls
briansrls deleted the fix/compute-board-flat-namespace-collisions branch August 11, 2026 20:50
gunbai-bot Bot pushed a commit that referenced this pull request Aug 11, 2026
Main landed #8151, which fixes the same two #8129 flat-namespace
collisions my 3ea09c1 fixed. Both conflicts are that duplicate, and
both resolve to main's side:

  - intent.dag: main chose MemorySubsystem, I had chosen
    MemoryCapability. Main's landed, so mine dissolves.
  - compute_board_readiness_witness_test.dag: same rename, following.

composition.dag needed no resolution — both sides independently picked
unsatisfied_board_capabilities.

My 3ea09c1 is therefore now redundant work; what survives from this
branch is PRESS-0 step 1 and the claim_executor budget-diagnostic
ordering, both verified present after the auto-merge. The four generated
artifacts main touched (stage0 crate partition, stage0 emit plan,
ci.yml, ci_spec) all 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
…n 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>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 12, 2026
…n 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>
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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant