Skip to content

Actuation phase 2: the endpoint-path state machine and the unlink admission - #7619

Merged
briansrls merged 14 commits into
mainfrom
session/smart-stag-201-actuation
Aug 1, 2026
Merged

briansrls merged 14 commits into
mainfrom
session/smart-stag-201-actuation

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

First slice of the actuation bundle. No host mutation. This lands the model and the wall a later mutating step must pass through. Stacked on #7595 (runner_activation, build_cache_instance); the diff here is two new files.

The defect

srv4, observed 2026-08-01: no listener, no process, no unit, a server.sock on disk, and an sccache error log whose entire contents are Address in use (os error 98). A server born inside a runner cgroup died with that cgroup without unlinking its socket, so every later server fails to bind. The host has been uncached since roughly 2026-07-26 for that reason alone.

BuildCacheEndpointObservation.EndpointAbsent cannot express this. It answers is anybody listening — the right question for placement, the wrong one for actuation. Under it, srv4 and a host that simply has no cache yet are the same state. They are not: one needs a unit started, the other needs a file removed first and then a unit started, and starting the unit without removing the file reproduces error 98 forever. A state-space conflation whose cost is measured rather than hypothetical.

So EndpointPathState is a separate axis from the owner — absent / listening / stale socket / path conflict / unavailable. EndpointStaleSocket and EndpointPathAbsent both project to EndpointAbsent on the ownership axis, which is exactly the distinction that axis is not built to carry.

Why the unlink is admitted rather than an ExecStartPre

The obvious fix is ExecStartPre=/bin/rm -f server.sock, and it is an absorbing fallback — it unlinks in every path state rather than the one where unlinking is correct:

state what the unconditional rm does
EndpointPathAbsent harmless no-op — which is what makes it look safe in testing
EndpointPathConflict destroys a file this lane did not create
EndpointListening severs a live server's socket — every healthy restart, every double-start race

The listening case leaves a running process no client can reach and that will not be replaced, because it still holds memory and still parents compiles. Silent in exactly the way the stale socket was silent, so the remedy recreates the disease one layer up. And an unconditional rm never reports having removed something it should not have, so the deficit's frequency is zeroed by construction.

admit_stale_socket_unlink admits only from EndpointStaleSocket under ObservationCurrent; every other state is a positively named, located refusal. The unlink is a separate admitted step before the unit start, able to refuse independently of it — never a line inside the unit.

Three shapes deliberately avoided

  • The admission takes the observed state, not four Bools. An all-true evidence record is hand-writable and re-checks what the observation already decided — validation wearing construction's clothes, the same defect review 46217 found in the owner context. The four facts an observer must establish are stated as its contract, not re-represented as data.
  • ObservationCurrencyUnknown refuses exactly as ObservationStale does. Not knowing whether a read is current is the same evidence as knowing it is old. An arm that graded them would fire precisely when /proc is least readable — which is when a bind race is likeliest.
  • A TCP endpoint refuses at admission, not at command construction, so no admitted value exists for which no command can be built.

Test plan

Green by execution — 8 controls, 0 failures:

claim_batch --source-root dag --source-root src/v2 \
  --entry dag/test/claim/build_cache_endpoint_path_test.dag

PASS witness_the_srv4_stale_socket_is_the_one_state_that_unlinks
PASS witness_a_live_listener_is_never_unlinked
PASS witness_a_non_socket_path_is_never_unlinked
PASS witness_an_absent_path_refuses_rather_than_no_ops
PASS witness_an_unreadable_path_does_not_default_to_stale
PASS witness_a_stale_read_cannot_license_an_unlink
PASS witness_a_tcp_endpoint_has_no_stale_socket_state
PASS witness_the_unconditional_sketch_differs_where_it_is_destructive

unconditional_unlink_sketch is the RED control — ExecStartPre=rm -f written as a predicate, answering true in all five states. Two witnesses assert the admission differs from it on the two destructive states rather than merely asserting the admission refuses: a refusal where the naive fix would also have been harmless proves nothing about the wall's worth. Per DESIGN §4b the control stays enrolled rather than retiring once the wall lands.

Honest residual

.dag has no visibility modifiers, so a caller can hand-write EndpointStaleSocket with no observation behind it — an explicit lie in the source rather than a forgotten check, the same residual BuildCacheInstanceReady carries. It closes when node/subtree visibility grants make the constructor private to its observer.

No observer producer ships here. That is deliberate: the observer is a mutating-adjacent host read, and the operator's sequencing holds host work until the generated units, this state machine, captured-base adoption, rollback, and the controlled-load protocol are reviewed together.

Brian Searls and others added 10 commits August 1, 2026 15:24
… them

Five defects the operator's post-merge review found in the merged tree, all
model-only, no host mutation.

1. srv4 had no desired deployed-instance row, so a typed unavailability had
   no subject and the fleet's intent was four-fifths stated. Added, and
   grounded on a real observation rather than a formality: srv4 was read
   2026-08-01 over the fleet-automation key the roster always intended. It
   holds no listener, no process, no unit, yet a stale server.sock remains on
   disk and its error log's whole contents are 'Address in use (os error 98)'
   -- a server born in a runner cgroup died with that cgroup without
   unlinking its socket, so every later server fails to bind and the cache
   tree holds one file. Distinct defect from srv1's stranded owner.

2. EndpointOwnerObserved carried cgroup and owner_context as independently
   writable fields that could disagree, with placement trusting the context.
   The cgroup path now lives inside each context arm as observed_cgroup: the
   context IS the classification of that cgroup, so the disagreement has
   nowhere to live. Retained as located diagnostic evidence, never a second
   input.

3. The session-capacity note claimed 50G while intended_capacity was none.
   The note was the true half -- ctrl-sccache.service does set 50G -- so the
   value now states it. CI capacity stays Absent deliberately and is now the
   only Absent, since 10 GiB is sccache's default rather than a decision.

4. readback_independence still classified --show-stats as ProbeMayEstablish
   and its authority note still asserted the refuted 'any sccache invocation
   auto-starts a server'. The earlier correction rewrote the mechanism prose
   but kept the arm, bridging the gap with 'still may-establish in the sense
   that matters here' -- the exact conflation the module exists to forbid,
   committed inside the module. Added ProbeAnswersWithoutSubject as its own
   arm: subject-creation and answer-without-subject carry different remedies
   (replace the probe vs ground it behind an existence check).

5. The hygiene-reaper scaffold bound BuildCacheServerPlacement to
   gunbc.host_build_cache_provision and named arms that do not exist
   (ServerUnitOwned, ServerLazySpawned). Repointed to
   gunbc.build_cache_instance with the real arms.

Green by execution: 52 witnesses pass across the three affected modules
(9 placement + 33 provision + 10 readback), all entries compile at 0
blocking errors. Three new controls pin the fourth arm: that it still
corrects a positive verdict, that the two non-independent arms stay
distinguishable through the fold, and that the cited probe now inhabits it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RunnerDeploySlot conflated materializing a slot with activating it, and only
the second can create the birth defect: a materialized-but-disabled slot
compiles nothing, while a slot that may accept work will connect-or-start on
its first cache miss and, with no managed endpoint owner, leave the server
inside its own cgroup as the host's de facto cache.

So the prerequisite goes on ACTIVATION. gunbc.runner_activation owns that
moment and consumes BuildCacheInstanceReady + HostCompilePoolReady;
gunbc.runner_host_deploy keeps materialization and its readiness-blind
runner_enable_command is DELETED, not deprecated -- leaving it would leave
the old path constructible, and a wall with a door beside it is a sign. The
two live in separate modules because the dependency runs one way: activation
needs the deployed-instance model, materialization does not.

Swapping P1/P1b in host_standup would NOT have done this. Ordering in a
status list is a projection of a dependency, not the dependency; the enable
command has to have no constructor that skips the receipt.

Two corrections the controls forced, both worth recording:

- admit_runner_activation originally never compared the receipt's unit to
  the instance's intended_unit, so a managed-unit-owned endpoint held by the
  WRONG unit would activate. witness_materializing_a_slot_does_not_activate_it
  caught it. ServerManagedUnitOwned means SOME unit holds it, which a legacy
  ctrl unit or a hand-started service satisfies.
- The receipt first declared placement: ServerManagedUnitOwned, on the theory
  that naming one variant made the other four unwritable. This substrate
  widens a variant-typed field to its coproduct, so the field really had type
  BuildCacheServerPlacement and a runner-owned receipt was constructible --
  the compiler said so, refusing a match as non-exhaustive over five arms.
  Shipping the note as written would have been rung inflation (DESIGN 4b) in
  the carrier meant to prevent it. The receipt now carries a bare
  managed_unit name and cannot hold a placement at all.

Green by execution: 61 witnesses across four modules (9 new activation-wall
controls + 10 runner-deploy + 9 placement + 33 provision), all entries at 0
blocking errors, publication gate ExitSuccess. The nine controls prove a bare
host admission, an absent endpoint, both runner-owned lifecycles, unknown /
unsupervised / ambiguous / unreachable placement, a receipt from another
host, an unready compile pool, a partially verified instance, and a wrong
unit all fail to yield an enable command -- and that materializing a slot
still does not activate it.

No host mutation. No producer for either receipt ships here, deliberately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	dag/gunbc/publication_grant.dag
…probe

Two unrelated-to-this-PR repairs the floor demanded.

## The census pins were stale, and #7575 is where they went stale

`the_surface_is_almost_entirely_derived` and
`the_three_denominators_are_derived_and_distinct` had been failing on
clean `origin/main` since 87fb808 (#7575), which added the
`compile_dag_diagnostic_census` builtin arm to `v1_interpreter.rs` and
did not move the numbers the census pins.

The counts come from the `interpreter_dispatch_arm_rows` host builtin,
which does not resolve outside the executor's discovery path, so they
could not be read by inspection. They were instead derived by
replicating the macro expansion over the `arm "identity" { "spelling" }`
token lists the six rosters expand from. That replication reproduces all
three previously-pinned numbers exactly at f2185ca (#7562, the prior
interpreter change, which DID update its pins) -- three independent
numbers matching is what makes the method trustworthy rather than a
guess. It then puts the delta since at exactly one arm: one row, one
identity, one spelling.

The repaired pins were confirmed green by execution, not by the
replication:

  claim_batch --entry dag/test/claim/v1_interpreter_primitive_surface_witness_test.dag
  PASS the_surface_is_almost_entirely_derived
  PASS the_three_denominators_are_derived_and_distinct
  PASS every_dispatch_site_is_enumerated
  PASS derived_and_declared_partition_the_surface

  derived 182 -> 183, declared 2 (unchanged)
  identities 173 -> 174, spellings 160 -> 161, sites 7 (unchanged)

Why bumping the numbers is the honest repair rather than a ratchet
being slackened: the failing witnesses pin absolute magnitudes, and
their siblings assert the structural properties -- the partition law and
the identities < rows < spellings ordering -- both of which stayed green
throughout. `declared` stayed at 2, so the surface is still almost
entirely derived and the witness name is still true. The new arm is
derived from its own macro tokens like the other 172. Had #7575 added a
hand-written declared arm, bumping `declared` would have hidden a
regression, and this commit would have had to refuse instead.

Sites stay 7 because the new arm lands at an existing dispatch symbol,
which is the denominator that would have moved had the surface actually
grown a site.

## The probe file

`dag/test/claim/primitive_surface_probe_test.dag` was a throwaway used
while locating the above; it was removed from the worktree but the
dashboard's cadence auto-commit had already captured it in
e5a8d58. `review 46534` caught it. It is baggage, not a witness --
it always returned true -- and it is deleted here rather than given
assertions, because the assertions it would have carried are the ones
the real census witnesses already make.

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

The first slice of the actuation bundle. Still no host mutation: this
lands the model and the wall that a later mutating step must pass
through.

## The defect it closes

srv4, observed 2026-08-01: no listener, no process, no unit, a
server.sock present on disk, and an sccache error log whose entire
contents are "Address in use (os error 98)". A server born inside a
runner cgroup died with that cgroup without unlinking its socket, so
every later server fails to bind. The host has been uncached since
roughly 2026-07-26 for that reason alone.

`BuildCacheEndpointObservation.EndpointAbsent` cannot express this.
It answers "is anybody listening", which is the right question for
placement and the wrong one for actuation: under it, srv4 and a host
that simply has no cache yet are the same state. They are not — one
needs a unit started, the other needs a file removed FIRST and then a
unit started, and starting the unit without removing the file
reproduces error 98 forever. A state-space conflation whose measured
cost is a host silently uncached for a week.

`EndpointPathState` is therefore a separate axis from the owner:
absent / listening / stale socket / path conflict / unavailable.

## Why the unlink is admitted rather than an ExecStartPre

The obvious fix is `ExecStartPre=/bin/rm -f server.sock`, and it is an
absorbing fallback. It unlinks in every path state rather than the one
where unlinking is correct. Absent, it is a harmless no-op — which is
what makes it look safe in testing. Conflict, it destroys a file this
lane did not create. Listening — which happens on every restart of a
healthy unit and every double-start race — it severs a live server's
socket, leaving a running process no client can reach and that will not
be replaced because it still holds memory and still parents compiles.
Silent in exactly the way the stale socket was silent, so the remedy
recreates the disease one layer up, and an unconditional rm never
reports having removed something it should not have, so the deficit's
frequency is zeroed by construction.

`admit_stale_socket_unlink` admits only from `EndpointStaleSocket` under
`ObservationCurrent`; every other state is a positively named,
located refusal. The unlink is a separate admitted step before the unit
start, able to refuse independently of it, never a line inside the unit.

Three shapes deliberately avoided:

- The admission takes the observed STATE, not a record of four Bools.
  An all-true evidence record is hand-writable and re-checks what the
  observation already decided — validation wearing construction's
  clothes, the same defect review 46217 found in the owner context.
  The four facts an observer must establish are stated as its contract,
  not re-represented as data.
- `ObservationCurrencyUnknown` refuses exactly as `ObservationStale`
  does. Not knowing whether a read is current is the same evidence as
  knowing it is old, and an arm that graded them would fire precisely
  when /proc is least readable, which is when a bind race is likeliest.
- A TCP endpoint refuses at admission rather than at command
  construction, so no admitted value exists for which no command can be
  built.

## Test plan

Green by execution, 8 controls, 0 failures:

  claim_batch --entry dag/test/claim/build_cache_endpoint_path_test.dag
  PASS witness_the_srv4_stale_socket_is_the_one_state_that_unlinks
  PASS witness_a_live_listener_is_never_unlinked
  PASS witness_a_non_socket_path_is_never_unlinked
  PASS witness_an_absent_path_refuses_rather_than_no_ops
  PASS witness_an_unreadable_path_does_not_default_to_stale
  PASS witness_a_stale_read_cannot_license_an_unlink
  PASS witness_a_tcp_endpoint_has_no_stale_socket_state
  PASS witness_the_unconditional_sketch_differs_where_it_is_destructive

`unconditional_unlink_sketch` is the RED control -- ExecStartPre=rm -f
written as a predicate, answering true in all five states. Two witnesses
assert the admission DIFFERS from it on the two destructive states
rather than merely asserting the admission refuses: a refusal where the
naive fix would also have been harmless proves nothing about the wall's
worth. Per DESIGN section 4b the control stays enrolled rather than
retiring once the wall lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review 46581 finding 1, and lands the producer that finding
made unavoidable.

## The defect (review 46581, real)

admit_runner_activation bound the CACHE receipt to the activation two
ways -- its instance host against the deploy host, and its managed_unit
against the instance's intended_unit -- and gated the POOL receipt
beside it on two Booleans alone. So a pool observed on a different host,
or a correctly-observed slice with some other name, admitted activation.
Same unbound-receipt defect the unit comparison exists to prevent,
sitting in the sibling field. The asymmetry is how it survived being
written: the receipt that got the attention got the binding.

HostCompilePoolReady now carries its host, and two comparisons gate
admission -- pool_receipt_is_for_host and
pool_receipt_names_intended_pool. The slice is compared against
BuildCacheInstance.intended_compile_pool rather than against
gunbc_managed_compile_pool_slice: comparing to the global default would
make the wall agree with a constant rather than with the instance being
activated against, which is weaker and looks identical.

## The producer

gunbc.host_compile_pool derives the receipt, so it stops being purely
hand-authored. It deliberately adds NO sizing and NO topology: those
already have an authority in gunbc.fleet_host_budget
(CompilePoolPlacement, compile_pool_bytes_for, and extdeps.jobserver's
jobserver_build_pool_bytes -- the same cited knob that bounds build
concurrency). A HostCompilePool record with its own memory_high /
memory_max / token_count fields would fork that and give a host two
answers for how big its pool is.

CompilePoolReadiness carries five distinct not-ready reasons. Two that
look alike are kept apart on purpose: PoolNotDeclaredInTopology (the
fleet has not decided it HAS a managed pool) versus PoolSliceAbsentOnHost
(it has, and this host lacks it). Their remedies and owners differ -- a
fleet-wide topology change that must shrink runner_pool in the same
motion, versus provisioning one host -- so collapsing them would make
the fleet decision read as a per-host chore.

Sizing is checked LAST, after the slice is confirmed present and
correctly named: reporting a sizing failure first would send an operator
to look at installer configuration on a host with no slice to size,
which is the wrong-location defect
toolchain_ensure_reconcile_located_note already records.

## Test plan

Green by execution, 18 witnesses across the two modules, 0 failures.
The two new binding controls:

  PASS witness_a_pool_receipt_from_another_host_is_refused
  PASS witness_a_pool_receipt_naming_another_slice_is_refused

Both are asserted rather than only the host case, because they fail for
different reasons -- one receipt is about the wrong machine, the other
about the wrong cgroup on the right machine -- and a wall catching only
the first would still activate a runner whose compiles are charged
outside the intended pool.

The producer's load-bearing witness reads the REAL
gunbc_compile_pool_placement row rather than a fixture:

  PASS witness_the_live_fleet_topology_yields_no_ready_pool

It asserts FALSE. The row is CompilePoolInRunnerSlots, so the derivation
returns not-ready for every host and activation refuses fleet-wide --
the correct answer today, and the one a hand-written all-true receipt
silently contradicted. When the topology flips it reds deliberately, as
the tripwire that the flip cannot land silently.

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

gunbai-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

review 46581 — finding 1 is real and now fixed in 8d19c16; finding 2 I'm not acting on, with receipts below.

Finding 1 — the pool receipt was unbound. Correct, and it was my defect.

admit_runner_activation bound the cache receipt to the activation two ways — its instance host against the deploy host, and its managed_unit against the instance's intended_unit — and gated the pool receipt beside it on two Booleans alone. A pool observed on a different host, or a correctly-observed slice with some other name, admitted activation. That is the same unbound-receipt defect the unit comparison exists to prevent, sitting in the sibling field, and the asymmetry is exactly how it survived being written: the receipt that got the attention got the binding.

HostCompilePoolReady now carries its host, and two comparisons gate admission — pool_receipt_is_for_host and pool_receipt_names_intended_pool.

One deliberate choice worth flagging, since your finding named gunbc_managed_compile_pool_slice as the thing to compare against: the slice is compared to BuildCacheInstance.intended_compile_pool instead. Comparing to the global default would make the wall agree with a constant rather than with the instance being activated against — weaker, and visually identical.

Two new controls, green by execution alongside the existing 16:

PASS witness_a_pool_receipt_from_another_host_is_refused
PASS witness_a_pool_receipt_naming_another_slice_is_refused

Both, not just the host case: one receipt is about the wrong machine, the other about the wrong cgroup on the right machine, and a wall catching only the first would still activate a runner whose compiles are charged outside the intended pool.

The finding also pushed the producer to land in the same commit — gunbc.host_compile_pool derives the receipt, so it is no longer purely hand-authored. Its load-bearing witness reads the real gunbc_compile_pool_placement row and asserts false: the row is CompilePoolInRunnerSlots, so activation refuses fleet-wide today. That is the correct answer, and precisely what a hand-written all-true receipt was silently contradicting.

Finding 2 — the hand-rolled-predicate rule. I checked for it and can't find it.

I looked before declining, and three things point the other way:

  1. DESIGN.md contains no predicate/walker dissolution rule. The only match in the tree is docs/plans/e0599-implementation-proposal.md §3.0 — an unmerged proposal, not an authority my brief points at.
  2. There is no canonical fold to consume. BuildCacheServerPlacement and EndpointPathState have no *Fold surface anywhere in the corpus (unlike ProbeEffectOnSubjectFold, which does exist and which this PR does thread). "Consume the canonical surface" has no referent here.
  3. The peer module already does it this way. gunbc.host_build_cache_provision eliminates these same coproducts with direct exhaustive match in verdict_for_placement and provision_build_cache_verdict. Introducing a fold for two five-arm predicates would fork the module's idiom, not converge it — and DESIGN §4 names Match as one of the six closed behaviors, so exhaustive coproduct elimination is the substrate's normal elimination form rather than a simulated relationship.

The lens that does exist in this area targets hand-unrolled folds over lists — a recursion written out by hand — which is a different shape from a total match over a closed coproduct. If there's a specific rule or lens I've missed, point me at the carrier and I'll fix it; I'd rather converge than argue the idiom.

— sent from smart-stag-201

Brian Searls and others added 3 commits August 1, 2026 20:40
review 46621, non-blocking. This PR deleted runner_enable_command, and
gunbc.plans.fleet_subsumption_manual_gaps still named it in two rows --
the DESIGN section 3 citation-rot class, where a name-level claim in a
carrier goes false because something elsewhere was renamed or deleted
and nobody touched the row.

Only two of the six occurrences were stale. The other four deliberately
name the deleted symbol AS HISTORY and are left alone:
runner_enable_command_deleted_note records the deletion,
activation_readiness_blind_widen_sketch_note cites it as the RED
control's provenance ("a faithful copy of ... as it stood on main"), and
two notes explain why moving a row in host_standup would not have
sufficed. A citation of a deleted thing is only rot when it asserts the
thing still exists.

The two that did:

- The B1 gap's present-tense "models the runner ENABLE step
  (runner_enable_command -> ...)" is now false; repointed to
  runner_activate_command.
- The A-item's "Only THEN did the modeled part run: systemctl
  daemon-reload + runner_enable_command" is a past-tense account of what
  was hand-run on 2026-07-22, so it is recorded as the THEN-current path
  rather than silently repointed -- the enable it describes is precisely
  the readiness-blind one that produced this fleet's birth defect, and
  rewriting it to name the new wall would erase the receipt. It now says
  which path ran, that it is deleted, and what replaced it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 46625. Commit 8d19c16 added `host` to HostCompilePoolReady and
updated two of the three construction sites; witness_ready_pool in
runner_host_deploy_witness_test was the third and was missed.

The reviewer is also right that this made the previous commit's test
plan overclaim. That run was real -- 18 witnesses across
host_compile_pool_test and runner_activation_wall_test -- but those are
the two modules I edited, not the closure of the type I changed, so the
third site was never compiled. Same miss as the ProbeEffectOnSubjectFold
literal earlier in this lane: after changing a record or coproduct,
enumerate every construction site of the TYPE, not the files in the diff.

Now green by execution on the missed module too, all 10 witnesses
including the three callers the review named.

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

# Conflicts:
#	dag/gunbc/runner_activation.dag
#	dag/test/claim/runner_activation_wall_test.dag
#	dag/test/claim/runner_host_deploy_witness_test.dag
@gunbai-bot

gunbai-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

review 46625 — correct on both counts; fixed in ee51f21.

witness_ready_pool was the third HostCompilePoolReady construction site and 8d19c16e updated only two. Now carries host: srv4_runner_host_deploy.host_label as HostIdentity, and the module is green by execution — all 10 witnesses including the three callers you named:

PASS srv4_enables_five_named_runner_instances
PASS unenrolled_host_cannot_enable_runner_units
PASS red_control_admission_blind_builder_rejected_by_gate
(+7)

And you're right that the previous test plan overclaimed. That run was real — 18 witnesses across host_compile_pool_test and runner_activation_wall_test — but those are the two modules I edited, not the closure of the type I changed, so the third site was never compiled and the "green by execution" line covered less than it read as covering. Worth naming precisely because it's the second time this lane hit the same shape: the ProbeEffectOnSubjectFold literal in claim_evidence_probe_rule.dag was missed the same way earlier, caught then by the compile-clean gate rather than by me. The rule I should have been applying: after changing a record or coproduct, enumerate every construction site of the type, not the files in the diff.

Also in this push:

  • review 46621 (non-blocking): the two stale runner_enable_command citations in gunbc.plans.fleet_subsumption_manual_gaps are repointed. Only two of six occurrences were stale — the other four name the deleted symbol as history (the deletion note, the RED control's provenance, the host_standup rationale) and are deliberately left, since a citation of a deleted thing is only rot when it asserts the thing still exists. The B1 row's present-tense claim was repointed; the 2026-07-22 hand-run account now records runner_enable_command as the then-current path rather than being silently rewritten, because the enable it describes is precisely the readiness-blind one that produced this fleet's birth defect and repointing it would erase the receipt.
  • origin/main merged (Require managed build-cache readiness before runner activation #7595 landed). The three add/add conflicts were Require managed build-cache readiness before runner activation #7595's squash against this branch's copies; resolved to this branch, verified by diffing each file against origin/main — zero lines from main are absent here, so it is a strict superset rather than a judgement call. mergeable=MERGEABLE at 1a2839c9.

— sent from smart-stag-201

review 46655 finding 1, real and blocking.

derive_compile_pool_readiness matched CompilePoolSliceObserved and bound
`limits: _`, discarding the observed cgroup ceilings, then reported
CompilePoolReady carrying the size derived from the JOBSERVER. So the
receipt asserted a sized pool on the strength of a number nobody had
compared to the host: a slice observed with a zero memory.max was
declared ready against a nonzero requirement.

Fabricated readiness rather than a missing check, and the distinction
places the fix: the observation was already in hand and was thrown away,
so this was never a gap awaiting a producer. It is fail-closed inverted
at the one carrier whose whole job is deciding whether compiles have a
bounded parent -- and an unbounded ceiling parenting every cache-miss
compiler is precisely the defect this lane exists to close.

Compared against memory_max rather than memory_high: max is the ceiling
that actually bounds the cgroup, while high is a throttling watermark
the kernel permits to be exceeded, so sizing against high would admit a
slice whose hard limit sits below the compiles it must hold.

THE FIXTURE WAS CERTIFYING THE HOLE, and that is recorded in-module
rather than quietly deleted: observed_slice built all-zero limits and
witness_a_declared_and_observed_slice_is_ready passed anyway. The
fixture now carries a ceiling that can hold the derived size, and a new
sibling pins the case it used to admit. Both halves are needed --
without the positive fixture the derivation could refuse everything and
still pass; without the new one it could admit everything.

Green by execution, 8 witnesses, 0 failures, including:

  PASS witness_a_slice_below_the_derived_size_is_not_ready

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

gunbai-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

review 46655 — finding 1 fixed in 4ea52de. Finding 2 I'm still not acting on, but I dug further this round and found more than I reported last time; details below.

Finding 1 — real, and my own fixture was certifying the hole

derive_compile_pool_readiness matched CompilePoolSliceObserved and bound limits: _, discarding the observed ceilings, then reported CompilePoolReady carrying the size derived from the jobserver. The receipt asserted a sized pool on the strength of a number nobody had compared to the host.

Worth being precise about the class: this is fabricated readiness, not a missing check. The observation was already in hand and was thrown away — so it was never a gap awaiting a producer, it was fail-closed inverted at the one carrier whose entire job is deciding whether compiles have a bounded parent. An unbounded ceiling parenting every cache-miss compiler is exactly the defect this lane exists to close, so getting it wrong here specifically is worse than getting it wrong anywhere else in the diff.

Now compares memory_max against the derived size, with a new PoolSliceCannotHoldDerivedSize { required, observed_ceiling } refusal. Against max rather than high deliberately: max is the ceiling that actually bounds the cgroup, while high is a throttling watermark the kernel permits to be exceeded, so sizing against high would admit a slice whose hard limit sits below the compiles it must hold.

And your pointer at host_compile_pool_test.dag:10 is the sharpest part of the finding: observed_slice built all-zero limits and witness_a_declared_and_observed_slice_is_ready passed anyway, so the acceptance set was ratifying the defect rather than catching it. The fixture now carries a ceiling that can hold the derived size, and a new sibling pins the case it used to admit — both halves, because without the positive fixture the derivation could refuse everything and still pass, and without the new one it could admit everything.

PASS witness_a_slice_below_the_derived_size_is_not_ready   (+7)

Finding 2 — I checked harder this time, and I found the mechanism you're pointing at. It doesn't cover these.

Last round I said I could find no such rule. That was under-researched, and two things I've since found are worth putting on the record because they cut partly your way:

  • src/v2/lens/non_fold_residue exists and enforces exactly the shape you name — non-fold residue must be rostered, matching DESIGN §6's "a finished stage is one fold; any non-fold residue is either a named irreducible kernel or un-migrated modeling."
  • A predicate-level disposition precedent exists: gunbc.fleet_posix_accounts fleet_posix_account_exists_predicate_disposition: Disposition = Scaffold { ... }.

So the discipline is real. What I can't establish is that it reaches these two functions, and one piece of evidence is decisive: main is green, and gunbc.host_build_cache_provision alone carries 13 such arms, with the same shape widespread across dag/gunbc/**. If the lens fired on Bool-returning exhaustive matches in domain modules, main would be red right now. non_fold_residue is a v2.lens roster over compiler-pipeline residue, not a wall over domain-layer coproduct elimination — and DESIGN §4 names Match as one of the six closed behaviors, so exhaustive elimination of a five-arm coproduct is the substrate's normal elimination form.

On the disposition receipt: I'd rather not add one than add a false one. Disposition = Scaffold asserts a thing is temporary with a dissolution trigger. pool_is_declared and admission_yields_unlink are not scaffolds — they're the intended shape — so stamping them as such to clear a review would be a dishonest mark in a repo whose whole discipline is that marks mean what they say.

That leaves a genuine disagreement rather than an oversight, and I'd rather resolve it than keep restating it. If you can point me at either (a) the lens or gate that should fire on these and currently does not, or (b) the canonical fold surface for BuildCacheServerPlacement / StaleSocketUnlinkOutcome that I should be consuming — noting neither type has a *Fold anywhere in the corpus, unlike ProbeEffectOnSubjectFold, which this PR does thread — I'll take the fix immediately. Absent that, converging these two while leaving the hundreds already on main would fork the idiom rather than close it.

— sent from smart-stag-201

@briansrls
briansrls merged commit fcce33d into main Aug 1, 2026
5 checks passed
@briansrls
briansrls deleted the session/smart-stag-201-actuation branch August 1, 2026 22:05
gunbai-bot Bot pushed a commit that referenced this pull request Aug 1, 2026
…sync the inert roster

Same merge class as the last push, caught this time BEFORE pushing by running the
whole-tree compile rather than only the marker census.

ContentHash home. Four more files main added import ContentHash from std.types,
which Phase A moved. Repointed onto the family member their values actually
inhabit, not a bare import swap:
  - gunbc.guarantee_measurement probe_set_digest is folded from content_hash_atom
    / content_hash_tagged / content_hash_combine, so it is Fnv1a64Structural. Its
    fold also had to move to the _structural variants, because Phase A DELETED the
    union-level combine and content_hash_tagged takes a structural payload while
    returning the union -- the union simply does not typecheck at this position.
  - gunbc.stage0_rust_honest_frontier_projection and both witnesses carry the same
    manifest_content_identity field as the previous batch; their "witness-abc123"
    String casts became Fnv1a64Structural records with valid 16-hex digests.

Field rename collision. This branch renamed tested_base_tree -> tested_base_tree_hash
and merge_target_tree -> merge_target_tree_hash alongside the GitObjectId retype, and
main's #7583/#7619 lane added new callers on the old names. Updated those call sites
(dag/tools/merge_admission_current_context.dag and main's new tests in
merge_admission_attempt_witness_test.dag). FLAGGING FOR THE OPERATOR: the rename is
incidental to this PR's purpose -- only the retype was required -- and it is what
made these collisions instead of clean merges. It is kept because the record's
sibling field was already base_tree_hash, but it is a fair thing to ask me to revert.

The merge_admission witness import conflict resolved as the UNION: this branch's
three Phase A symbols plus main's two roster helpers and its new commit_workflow
import. All six are used in the file body and all six are declared.

Inert roster re-synced, both directions of the check firing as designed:
  - DownstreamSupportRoster (#7571) is newly inert -- declared, named in plan prose,
    constructed and read by nothing -- so it is rostered.
  - AccessPolicy is no longer inert: main's std.effect_grant effect_grant_policy is a
    real production consumer, so its row was STALE and is DELETED. That is exactly the
    dissolve the row's own trigger specifies, not a hand-edit.
The roster now equals the live inert set exactly.

Verified on a binary rebuilt AFTER the merge (main touched src/v1, so the pre-merge
binary was not a valid oracle): whole-tree compile 0 blocking errors,
regen_divergence_count=0, and green suites -- merge_admission 28/28,
guarantee_measurement 9/9, honest_frontier 7/7, lifecycle_scaffold 28/28,
emitter poles 7/7, roadmap 39/39, c_compilation_unit 24/24, compilation_unit 15/15,
inert_carrier 4/4.
briansrls added a commit that referenced this pull request Aug 2, 2026
…7480)

* Ground ContentHash on hash family (Phase A: carriers + R0 backstop)

Rebased onto main. ContentHash becomes a family coproduct
(Fnv1a64 | Sha256Hash | Sha1Hash) with where-refined hex carriers,
compare_content_hash cross-family refusals, OCI algorithm-qualified digests,
and construction walls (reviews 45496/45505).

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix review 45522 regressions and CI compile-gate timeout.

- Restore guarantee-ladder and shell→intent authority in design_document.dag
  and guarantee_ladder_nodes/roadmap_focus in roadmap_authority.dag from main;
  keep intentional chash Fnv1a64Structural migration only.
- Remove git_sha1 witness Absent fallback (mint must succeed or test reds).
- Raise v1-compiler-tests compile-gate step budget to 10m (run 30646205825).

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: regenerate drifted generated artifacts (ci auto-heal)

* Update DESIGN open-thread row for ContentHash Phase A landing (#7480).

Replace the stale pre-migration §3 residue with Phase A receipts and an
explicit Phase B dissolve-on remainder (union-level == typecheck refusal).

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* chore: regenerate drifted generated artifacts (ci auto-heal)

* Move OCI digest algorithm predicates to extdeps.container.oci.digest.

Review 45539 flagged OCI-specific algorithm grammar in std.content_hash as a §3 authority inversion. The predicates and where-refinement entry point now live at the OCI digest authority; infer imports oci_other_digest_algorithm from there and stage0 emits extdeps_container_oci_digest.rs.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* fix(stage0): commit emitted extdeps_container_oci_digest seed module

Regen emits the OCI digest module but it was never tracked in git after
rebase, which broke cargo fmt and the build gate on PR #7480.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* fix(oci digest): realign seed parse path and emitter Optional Present arm

Review 45614: use `encoded: encoded` in the other-algorithm parse arm
(oci_encoded_digest returns NonEmptyStr?, not a record). Route split
parts through oci_wire_digest_parts_from_split so the seed types as
String. Fix v1 emitter construction so Present{value: <branded scalar cast>}
lowers to Some(...) instead of Optional::String. Restore extdeps_base
module path for extdeps_container_oci_digest.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: regenerate drifted generated artifacts (ci auto-heal)

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* chore: regenerate drifted generated artifacts (ci auto-heal)

* fix(emit-on-demand-host): refuse crypto ContentHash at structural projection

Review 45661: replace emit_host_identity_structural sentinel collapse with
typed outcome coproducts (EmitHostStructuralIdentityRefusedCrypto,
ResolvedBuildContextIdentityRefusedCrypto, ToolchainIdentityRefusedCrypto).
Distinct Sha256/Sha1 digests no longer share one fnv1a64 cache key. Witness
resolved_build_context_identity_refuses_sha256_observed_tool exercises the
refusal path; structural fixture tests route through outcome helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* test(emitter): fix optional Present witness include/exclude patterns

The exclude Optional::String substring matches lawful return-type emission;
use the broken-body pattern String { value: text instead so the witness
discriminates Some(...) lowering from the generic Optional::String variant path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(emit-on-demand-host): serialize ContentHash cache path segments

Route native cache workspace path construction through
serialize_content_hash instead of bare as String casts so cache keys
use the canonical wire form (review 45713).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(emit-on-demand-host): drop parallel family field from crypto refusals

ContentHash already carries its family; RefusedCrypto variants now hold
only component and identity. Witness derives family via content_hash_family
(review 45721).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: restore doc-graph construction walls from main (review 45726)

Reverts accidental regression of HandAuthoredDocBind primary_work +
additional_works, merged accelerator-demo-roundtrip row, guarantee-recovery
dual anchors, and doc_reachability construction witnesses (#7486/#7489).

Updates sha256_digest_content_hash_convergence_note to match the
construction-walled bridge (as_content_hash_cryptographic), not wire-form
rendering prose.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: Ground ContentHash on its hash family so a cross-family comparison is un

* fix(oci-digest): construction-wall OciOtherDigestEncoded payload

OciOtherDigestBody.encoded is now NonEmptyStr where oci_other_digest_encoded
(descriptor.md charset), not a forgeable bare NonEmptyStr (review 45731).
Infer where-refinement enrollment mirrors oci_other_digest_algorithm.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(oci-digest): emit parse wire path without NonEmptyStr cast panic

Use parts.encoded as OciOtherDigestEncoded after oci_encoded_digest
validates (same pattern as sha512 hex), avoiding emitter panic on
String→NonEmptyStr cast in the OtherDigest parse arm.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(oci-digest): OciOtherDigestEncoded as String where for emit parity

NonEmptyStr where oci_other_digest_encoded made the emitter emit
panic() on String→NonEmptyStr cast in parse_oci_content_digest_wire.
String where oci_other_digest_encoded matches OciSha512DigestHex and
keeps the construction wall (review 45731).

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: gunbc oracle

* WIP: gunbc oracle

* fix(content-hash): project .digest at string boundaries; repair the last floor reds

Three sites where a structural hash reached a String position:

- roadmap_dispatch_actuator dispatch_attempt_key and roadmap_belt_actuate
  belt_uuid_hex32 cast the content_hash_atom / content_hash_combine_structural
  RESULT to String. content_hash_atom returns Fnv1a64Structural (a record) since
  the family grounding, so the cast no longer yields the digest text. Notably this
  is not a typecheck error -- roadmap_belt_actuate's own uuid witnesses passed
  while the value was wrong -- so the only witness that caught it was the one
  comparing a parsed branch against the minted one
  (attempts_parse_roundtrips_the_minted_branch). Both now project .digest, the
  idiom site/moodboard.dag already used. A regex + paren-matching sweep over every
  .dag confirms no structural result is cast directly to a string type anywhere.

- source_integration_landing_spine's witness compared a receipt against the raw
  literal "multiplicity-receipt" after construction moved to
  content_hash_of_value(...), i.e. the field now holds a hash of that string.

Verified by execution: dispatch_attempts 9/9, roadmap_belt_actuate uuid witnesses
3/3, source_integration_landing_spine 22/22. (roadmap_belt_actuate's seven
belt_exec witnesses fail locally with "hermetic mode: no mock_response for
operation Check" -- they shell out to a real host process and never reach hash
code; that is the hermetic runner refusing to fabricate, not a regression.)

* fix(content-hash): re-home ContentHash imports that arrived with the main merge

The floor's dag_compile_clean_gate went red on the merge commit with one hard
diagnostic: name 'ContentHash' not found in module 'std.types'. Two witness files
came in from main still importing ContentHash from std.types and initializing it
from a string literal -- the shape this PR's family grounding removed:

  dag/test/claim/check_coverage_admission_witness_test.dag  (4 rows)
  src/v2/test/claim/walk_plan_schedule_lens_test.dag        (1 row)

Both now import from std.content_hash and mint through content_hash_of_value,
matching their consumers, which take the union ContentHash
(merge_admission check_coverage_admits; the walk-plan target field).

My pre-merge sweep for this class was clean and stayed correct -- these files did
not exist on this branch when it ran. The sweep has to be re-run after each merge,
not once. Re-run now: zero std.types ContentHash imports and zero string-literal
ContentHash initializers corpus-wide.

Verified by execution: whole-tree `gunbc compile --target dag` reports 0 hard
diagnostics (the gate's own check); check_coverage_admission 12/12;
walk_plan_schedule_lens 2/2.

* WIP: gunbc oracle

* revert(v2-node): restore node.dag hash surface to its 2804c8b state

The WIP auto-commit at 8212c2a captured a mid-edit working tree: a speculative
retype of v2.std.node content_hash / symbol_identity_digest / bag_hash_digest /
byte_offset_cache_key_fingerprint to return Hash, snapshotted BEFORE I had fixed
a stray argument label it introduced --

  fn symbol_identity_digest(sym: Symbol) -> Hash {
    structural: content_hash_atom(value: sym)   # not an expression
  }

-- so that broken form went out on the branch. My subsequent `git checkout --`
restored to that bad commit rather than to the intended baseline, which is how it
survived. This restores the file byte-for-byte to 2804c8b.

The retype is reverted on its merits regardless of the label bug: executed against
the three failing classes (Variant-cast, digest-on-String, atom_identity_hash arity)
it fixed NONE of them, so it was an unverified edit to a load-bearing substrate file
with no demonstrated benefit. The real roots are recorded in the PR comment: five
`ContentHash where brand(...)` types that can no longer be cast, and the raw
atom_identity_hash / std.content_hash dual surface in 02_parse and self_host.

* WIP: gunbc oracle

* chore: regenerate drifted generated artifacts (ci auto-heal)

* Project the corrected ContentHash bridge claim into DESIGN.md

sha256_digest_content_hash is the VALIDATING PARTIAL bridge Digest->ContentHash?;
as_content_hash_cryptographic is the TOTAL injection. Regenerated via
tools.generated_artifact_gate main_wet; byte-stable on a second pass.

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

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* WIP: gunbc oracle

* Restore the v1 seed sources an auto-commit reverted to main

The session auto-commit fired while src/v1 was checked out to origin/main for a
control build, snapshotting main's version as this branch's HEAD; a later
'git checkout HEAD -- src/v1/' then restored that poisoned state. Three files
lost their reviewed content and are restored from f9dc5a8:

  00_core.dag     — 'predicate not enforced at compile time' advisory reason
  04_infer.dag    — lower_hex_{16,40,64,128} / oci_other_digest_* where-predicates
                    and the structural content-hash rewrite
  05_emit_rust.dag — variant_surface_name + fn_returns_optional

05_emit_rust.dag is reconstructed onto main's newer revision rather than
overwritten, so main's rust_btree_set_ord name-grain childless gate and its note
are preserved; the result is byte-identical to f9dc5a8 apart from that
addition.

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

* WIP: gunbc oracle

* Remove temporary emitter-probe fixture

Diagnostic scaffold for isolating the optional-payload cast emit defect; the
repro is recorded in the PR discussion, not enrolled as a witness pending a
decision on the emitter fix.

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

* Record the optional-payload cast emit deficit as executing evidence

Three poles, each holding one variable constant, isolate the trigger for the
E0433 that blocks finding 3's oci_encoded_digest shape:

  refined return, no cast   -> lowers to Some(..)      (refined return is harmless)
  plain String? return, cast inside payload -> broken  (the cast alone suffices)
  cast hoisted to a let     -> STILL broken            (refutes the workaround)

The third pole is load-bearing: hoisting does not avoid the defect, so no
respelling both constructs the carrier and lowers correctly, and the emitter
repair is required rather than optional. An earlier draft of this file claimed
the hoist WAS a working scaffold spelling and briefly landed that spelling in
oci_encoded_digest; executing it refuted the claim, and both are reverted --
oci_encoded_digest keeps the reviewed inline shape.

Poles 2 and 3 are known-red receipts: they assert the deficit is present, so
they pass now and red when the emitter is repaired.

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

* WIP: gunbc oracle

* WIP: gunbc oracle

* Repair optional-payload cast lowering; regenerate the seed from the authority

Confirmed name-loss point (by instrumentation, not by reading): the concrete-type
peel in v1.compiler.emit_rust emit_rust_expr_record_lit. A cast reaching an
optional payload gives the literal a concrete resolved type, so the peel replaced
the authored "Present" with the payload's concrete name; emit_typed_record_lit
then missed the optional arm and fabricated "Optional::String { .. }", which is
not a Rust constructor (E0433). A probe printing variant_name/tn/peeled at the
call site showed the authored name arriving intact and being overwritten.

The standing variant_surface_name hypothesis was wrong as a single cause but is a
real SECOND contributor: with the peel fixed, optional_variant still keyed off the
resolved struct name until variant_surface_name reached the seed. Both were
required; neither alone sufficed.

The peel is suppressed only when the authored name is a built-in optional
constructor AND the concrete type does not itself declare a variant of that name,
so a user-defined "Present" still peels (variant_belongs_to_enum finds it) and
ordinary variants are untouched. Variant identity is preserved, never inferred
from the payload type.

Authority is src/v1/05_emit_rust.dag. The seed could not regenerate the fix, so
minimal bootstrap edits were lifted by hand and then PROVEN by regeneration:
regen_stage0 --verify reports regen_divergence_count=0, so committed stage0 is
exactly the fresh self-compile of the authority. Also restores cli_run.rs, the
v1_interpreter R0 cross-family guard, and two more hand-written seed files that
the earlier auto-commit poisoning reverted to main (the R0 guard is merged onto
main's newer interpreter, not overwritten).

Witness flipped from known-red receipt to permanent regression control (DESIGN
section 4b): 6/6 including the Wrap::Holds and user-defined-Present negative
controls. Nested-refinement receipt retains its polarity, 2/2. Whole-tree compile
exit 0.

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

* Declare publication grants for the 17 ungranted added public paths

The Stage-0 visibility guard (tools.publication_placement_gate, run as the Wet
floor runnable publication_placement_gate_passes) diffs the publication cutover
to HEAD and refuses any added public path with no Publish grant. It reported
PublishGuardLocalViolation count=17, which is why the ci floor failed while
build, regen and heal all passed.

Six are this PR's: the OCI digest carrier, its generated stage0 projection, and
the four ContentHash/emitter witness files. The other eleven arrived with main
(the heal-revalidation and source-integration-proof-kernel lanes, ci_heal_dispatch,
extdeps.github.workflows and the preflight emit pair); the guard diffs from the
cutover rather than from the merge base, so it fires on the union and this branch
cannot go green while they are undeclared.

All seventeen are already merged and pushed to the public repository, so granting
records the publication that already happened rather than newly exposing anything.
Flagged rather than silent because eleven of the paths are not this PR's to
classify.

Gate re-run locally: ExitSuccess. publication_placement_gate_test 8/8.

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

* WIP: gunbc oracle

* Converge the fnv1a64 dual surface at the 22 sites the re-key stranded

The floor's discovery corpus surfaced 22 witnesses this PR broke, in two classes,
both instances of the dual surface DESIGN section 3 tracks: v2 code calls the RAW
v1 intrinsics while std.content_hash is the modeled surface over the same
authority. Re-keying v2.std.node.Hash to Fnv1a64Structural made that fork
observable, because a raw intrinsic yields a native String where the modeled
carrier is a record.

Class 1 (18) -- "cannot access field digest on String": raw atom_identity_hash
flowed into Hash positions. Eleven call sites in v2.compiler.02_parse,
v2.compiler.self_host and the parse_table_content_key witness now route through
v2.std.node symbol_identity_digest, the modeled surface those modules already
import. The substitution is value-preserving: content_hash_atom(v) is by
definition structural_content_hash(digest: atom_identity_hash(v)).

Class 2 (2) -- "atom_identity_hash requires exactly one string argument": the
inverse, a structural passed INTO the intrinsic. v2.lens.interface_summary
signature_fingerprint_of_node hashed a node content hash that is now a record; it
projects .digest first. A direct cast was tried and rejected by the interpreter
(cannot cast Record to Fnv1a64Structural), so the original hash-of-hash semantics
are kept rather than changed.

Plus one value mismatch: the live_deploy readiness witness compared
site_artifact_digest(..) as String against the digests the served body embeds,
which now project .digest.

After this the raw intrinsics appear nowhere outside dag/std/content_hash.dag and
the v1 builtin registry, which is the single-authority end state.

Re-run green: parse_table_content_key 5/5, interface_summary_firewall 4/4,
ingest_bridge 9/9, cross_language_add_python_to_typescript 4/4,
inhabitant_neutralization 6/6, parse_table_memo_governed 9/9,
self_host_realized_comparison_floor 6/6, parse_stamp_span_index 2/2,
live_deploy readiness 25/25.

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

* WIP: gunbc oracle

* Address review 2026-08-01: restore roadmap containment wall, narrow optional emitter, correct design authority

Three blocking items from the review, each with executing evidence.

1. Roadmap containment wall (regression against the branch base). My earlier
   merge dropped main's rendered-node containment wall entirely:
   roadmap_rendered_ghost_count, both RED probes, and all three witnesses were
   absent from this head. Restored roadmap_spawner.dag from main wholesale (main
   was a strict superset there) and grafted the import block, note and three
   tests onto roadmap_authority_test.dag while keeping this PR's
   Fnv1a64Structural changes. 38/38 green, the count the review predicted.

2. Optional emitter classification was broader than its stated boundary. The
   fn_returns_optional disjunct is a fact about the ENCLOSING FUNCTION, so a
   user-defined MyOpt::Present built inside a `-> String?` function emitted
   Some(..), overriding an effective_parent that had already resolved correctly.
   Added the negative control the review asked for and CONFIRMED IT RED before
   changing anything. Then executed all seven poles with the function-wide arm
   removed: it was load-bearing for none of them, so it is deleted rather than
   gated -- resolved_type.return_cardinality == CardOptional is the
   expression-local statement of the same fact, and the disjunct was a second
   representation of it. 7/7 green; regen_divergence_count=0.

3. Design authority still described the deleted models. Corrected
   design_document.dag (v2.std.node Hash is Fnv1a64Structural, not
   std.types.ContentHash -- wrong home and wrong type, contradicting the Phase A
   row below it), regenerated DESIGN.md, and corrected both locked plan docs to
   say the unit carries one agnostic identity from which each realization
   derives its spelling. Corpus sweep for the three stale claims is clean.

Also repairs a main red this branch inherited: CompileDiagnosticCensus became an
unrostered inert carrier when main's codex-review-46144 repair deleted
census_rows(c: CompileDiagnosticCensus), its only production consumer. The three
files involved are byte-identical to main, so the red is main's, not this
branch's. Rostered with that reason and the standard dissolve trigger.

* Discharge the CR/NUL control gap: main's Unicode escapes fired its dissolve-on trigger

Round 2 asked for hostile-spelling controls over / \ CR LF NUL and ". CR and NUL
were declared as a coverage BOUNDARY rather than faked, because .dag string
literals had no numeric-escape form: backslash-r lexed as literal backslash-then-r
and the backslash-u brace form lexed as its raw characters, both measured at the
time. The note carried an explicit dissolve-on trigger.

Main landed Unicode string escapes (#7585), so the trigger fired. The escapes were
RE-MEASURED on this head rather than assumed from the commit title: \u{000d} and
\u{0000} each yield exactly one character, of codepoint 13 and 0. CR and NUL are
now DIRECT witnesses (a%00000db, a%000000b) and both join the output-always-safe
control, so the whole requested hostile set is witnessed against the real encoder
with no argument from escape-branch genericity standing in for any member.

The encoder needed no change -- the genericity argument the boundary rested on
turned out to be correct, which is exactly what direct evidence was owed to show.
24/24 green. The note is kept as the receipt that the boundary was declared,
triggered and discharged rather than quietly dropped.

Also merges current main (4 commits). One conflict, in
dag/extdeps/realization/artifact_store_fs.dag: this branch moved ContentHash to
std.content_hash while main added std.access {Permit, Deny} from the access-kernel
change. Resolved as the union -- both wanted, neither superseding the other.

* WIP: gunbc oracle

* Merge main (15 commits): repoint four more ContentHash consumers, re-sync the inert roster

Same merge class as the last push, caught this time BEFORE pushing by running the
whole-tree compile rather than only the marker census.

ContentHash home. Four more files main added import ContentHash from std.types,
which Phase A moved. Repointed onto the family member their values actually
inhabit, not a bare import swap:
  - gunbc.guarantee_measurement probe_set_digest is folded from content_hash_atom
    / content_hash_tagged / content_hash_combine, so it is Fnv1a64Structural. Its
    fold also had to move to the _structural variants, because Phase A DELETED the
    union-level combine and content_hash_tagged takes a structural payload while
    returning the union -- the union simply does not typecheck at this position.
  - gunbc.stage0_rust_honest_frontier_projection and both witnesses carry the same
    manifest_content_identity field as the previous batch; their "witness-abc123"
    String casts became Fnv1a64Structural records with valid 16-hex digests.

Field rename collision. This branch renamed tested_base_tree -> tested_base_tree_hash
and merge_target_tree -> merge_target_tree_hash alongside the GitObjectId retype, and
main's #7583/#7619 lane added new callers on the old names. Updated those call sites
(dag/tools/merge_admission_current_context.dag and main's new tests in
merge_admission_attempt_witness_test.dag). FLAGGING FOR THE OPERATOR: the rename is
incidental to this PR's purpose -- only the retype was required -- and it is what
made these collisions instead of clean merges. It is kept because the record's
sibling field was already base_tree_hash, but it is a fair thing to ask me to revert.

The merge_admission witness import conflict resolved as the UNION: this branch's
three Phase A symbols plus main's two roster helpers and its new commit_workflow
import. All six are used in the file body and all six are declared.

Inert roster re-synced, both directions of the check firing as designed:
  - DownstreamSupportRoster (#7571) is newly inert -- declared, named in plan prose,
    constructed and read by nothing -- so it is rostered.
  - AccessPolicy is no longer inert: main's std.effect_grant effect_grant_policy is a
    real production consumer, so its row was STALE and is DELETED. That is exactly the
    dissolve the row's own trigger specifies, not a hand-edit.
The roster now equals the live inert set exactly.

Verified on a binary rebuilt AFTER the merge (main touched src/v1, so the pre-merge
binary was not a valid oracle): whole-tree compile 0 blocking errors,
regen_divergence_count=0, and green suites -- merge_admission 28/28,
guarantee_measurement 9/9, honest_frontier 7/7, lifecycle_scaffold 28/28,
emitter poles 7/7, roadmap 39/39, c_compilation_unit 24/24, compilation_unit 15/15,
inert_carrier 4/4.

* Merge main #7608: ground the new GitSha1ObjectId fixtures on Sha1Digest

Phase A made GitObjectId's digest a Sha1Digest record rather than a raw hex
String, and main's new commit_writer_admission witness constructs
GitSha1ObjectId { digest: "1111..." } directly. Both fixtures now use the tree's
existing idiom, Sha1Digest { hex: <40 hex> as Sha1DigestHex }, with the carriers
imported from std.content_hash.

The object_store.dag import conflict resolved as the union: this branch's Phase A
content_hash block plus main's two new imports (PositiveInt, repeat_string).

BLOCKED, and NOT worked around: dag/gunbc/commit_workflow.dag fails to compile
with "no field 'path' on type 'T'" at commit_writer_index_entries_for_path. This
is NOT this branch's defect and it is deliberately left alone rather than papered
over with a speculative type annotation (DESIGN section 5: noticing you are
implementing a workaround IS the line-stop signal).

Evidence it is main's:
  - the file is BYTE-IDENTICAL to origin/main here;
  - it reds in its OWN narrow closure, not just whole-tree, so it is a real defect
    rather than a resolution artifact;
  - this branch's only 04_infer changes are where-predicate registrations
    (lower_hex_*, oci_other_digest_*), which cannot affect lambda parameter
    inference;
  - main's CI for #7608 (f3df2ce) was still QUEUED when this merge happened, and
    three of the preceding main commits are outright red, so the commit landed
    unverified.

Hypotheses tested and REFUTED by execution, so the next person does not repeat
them: trailing comma in the parameter list; filter over an imported record type
(both compile clean in an isolated probe); a shadowing filter declaration; a
lambda/parameter name colliding with an imported symbol; cross-call-site
unification collapsing the element type (only two callers, both passing
List<GitIndexEntry>). The sibling flat_map over the same type and the same
`entry` binding compiles, so it is specific to this call.

* WIP: gunbc oracle

* Remove cw_probe.dag: a scratch bisect probe the auto-commit swept into the branch

Not source. It was a copy of commit_workflow.dag under a probe module name, used
to bisect the 'no field path on type T' failure to its cause, and it should never
have been committed.

* Unblock the compile-clean gate: declared scaffold for the filter name capture, two more ContentHash homes

THE BLOCKER, root-caused by bisection rather than guessed. dag/gunbc/commit_workflow.dag
failed with "no field 'path' on type 'T'". src/v2/std/algebra.dag declares
fn filter<T>(xs: FreeMonoid<T>, predicate: fn(T) -> Bool). commit_workflow does NOT
import v2.std.algebra -- but it imports v2.compiler.source_authority, which drags the
whole v2 compiler closure including v2.std.algebra into the module POOL. The bare name
filter then binds to that declaration instead of the builtin, List<GitIndexEntry> does
not unify with FreeMonoid<T>, the lambda parameter stays unbound, and the module reds.

Discriminating measurement: importing v2.std.algebra for `length` ALONE -- never filter
-- reproduces it, and deleting only the source_authority import makes the original
filter form compile. So this is name capture by POOL MEMBERSHIP, not by the import
list: DESIGN #6985 Class B in its inverting direction, and silent -- nothing reports
that filter rebound.

Refuted by execution before landing anything, recorded in the carrier so nobody repeats
them: trailing comma in the parameter list; filter over an imported record type; List
not imported; two filter calls over different element types in one module; a lambda or
parameter name colliding with an imported symbol; cross-call-site unification.

The import cannot be dropped -- ModuleStorageBinding, ParsedFromSource and
ProducedByBehavior are all genuinely used -- so the only local repair routes around the
capture. Per DESIGN section 5 that is a WORKAROUND and may only land as a DECLARED
SCAFFOLD, never a quiet respelling: the body selects with flat_map (the spelling the
sibling function already uses over the same type with the same binding), and
commit_writer_index_filter_capture_scaffold states the root cause, the discriminating
measurement, the refuted hypotheses, why the import cannot be dropped, and the
dissolve-on -- the namespace-only resolution lane, after which the filter spelling is
restored and the row deleted. Behaviour is unchanged: it selects exactly the entries
whose path matches. commit_writer_admission_witness_test 13/13 exercises
admit_commit_writer, which calls this function, so that is verified by execution and
not merely by compiling.

This is NOT this branch's defect. It is fixed here because it reds main itself and
every job on this PR, and main landed #7608 while its own CI was still queued.

Also: two more files from main's #7623 import ContentHash from std.types. Same
manifest_content_identity field as the two prior batches, so same grounding onto
Fnv1a64Structural, with the contract fixture becoming a valid 16-hex digest.

Whole-tree compile 0 blocking errors; regen_divergence_count=0; heal no drift;
commit_writer_admission 13/13; live_rust_observation_contract 12/12.

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.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>
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