Skip to content

Guarantee probe observation surface: compile_dag_diagnostic_census, and the first floor-class measurement - #7575

Merged
briansrls merged 16 commits into
mainfrom
session/snappy-eagle-314
Aug 1, 2026
Merged

briansrls merged 16 commits into
mainfrom
session/snappy-eagle-314

Conversation

@briansrls

@briansrls briansrls commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

The observation surface ladder-probe-corpus needs before any wall control can migrate, plus the first floor-class measurement it makes possible. Scope amended by the lane owner (2026-08-01) to admit exactly this one surface — see "Why a compiler change was authorized" below.

The problem this closes

The only .dag-callable v1 compile was compile_dag_rust_emit_check: it counts diagnostics passing compile_clean_diagnostic_is_hard and returns false when that count is nonzero. Everything else is discarded inside the host. Measured against the tests Stage 1a is supposed to migrate, that costs three specific things:

  1. Class identity. The Rust RED asserts CallArgumentNameUnknown specifically. At Bool grain a probe refusing for any hard reason — a typo in the probe source included — reads as the wall firing. That is the proxy-assertion fail-open.
  2. Blocking severity, and this one is a live regression hole. The filter is is_interpreter_blocking_diagnostic, so demoting a landed wall from blocking to advisory turns its migrated RED silently GREEN. ct_call_shape_wall_witness_test pins exactly this in its second assert; a Bool-grain migration would delete that assertion.
  3. Advisories entirely, so a positive control cannot state zero-diagnostics-of-any-severity — the assertion review 45357 added after an advisory MethodExistenceUndecided passed unnoticed as a green control.

It is structural, not stylistic: Stage 0's ProbeObservation carries RefusedTyped { diagnostic_class, count } and AcceptedCounted { diagnostic_class, count }, and neither is inhabitable from a Bool. The Stage-0 vocabulary was uninhabitable on every v1 path.

What lands

  • gunbc.compile_diagnostic_census — CompileDiagnosticCensusRow { diagnostic_class, subject_name, blocking, count } and the coproduct CensusObserved | CensusNotRunnable, with pure row selectors. No arm judges acceptance.

  • compile_dag_diagnostic_census — one measurement-only builtin. Same compile path as its sibling; (class, name) is compile_clean_diagnostic_histogram_key (§3 projection, not a second diagnostic vocabulary); blocking is the existing compile_clean_diagnostic_is_hard delegation carried as data, so the severity policy keeps one home. Nothing is filtered — callers filter. Aggregation keys on (class, name, blocking) deliberately: folding severities together would hide the very demotion this surface exists to keep visible.

  • CensusNotRunnable as a distinct arm, never an empty row list. An empty census is byte-identical to a clean compile, so could-not-measure would read as the subject passing (DESIGN §5, ⊤-as-answer vs ⊤-as-ignorance). Preserving the split at the host seam is what lets the probe layer map it to ProbeNotRunnable without guessing. A host panic is caught at the boundary and lands here with its cause.

  • dag/test/claim/compile_diagnostic_census_witness_test.dag — green by execution: class identity, blocking severity (the demotion detector — it reds if a wall is softened to advisory), occurrence counts rather than presence flags, a clean-source control proving the harness is not uniformly refusing, and the NotRunnable vs empty-observation split.

    Both severity directions have live specimens, and the route there involved two corrections, both recorded rather than tidied away. I first concluded no advisory specimen could be produced synthetically — three candidates each either compiled silent or refused blocking. Corpus prevalence said otherwise (the whole-tree census carries 1,981 where-refinement advisories), so the class was demonstrably reachable and a probe finding none was evidence against the probe. A working specimen followed, and AcceptedCounted gained a producer.

    I then attributed the original failure to the closure — fixture-only compiles carry no std. A discriminating control refuted that too, and a 2×2 finally pinned the real axis, which was neither thing guessed. Crossing declaration site (a locally-declared alias vs std's brand — structurally identical: type NonEmptyStr = String where non_empty and type Tight = String where non_empty) against cast subject (literal vs unknown parameter):

literal subject parameter subject
local alias silent fires
std brand silent fires

Declaration site is irrelevant — a user-declared refinement alias is judged exactly as std's brand is. The axis is literal vs non-literal, and the diagnostic names it: "where-refinement unenforced: predicate non_empty on Product(Tight) — non-literal value at refined position". So the original probe's silence is correct behaviour: it cast the literal "x", and a literal at a refined position is deliberately exempt (the ladder working — construction deciding what validation would otherwise check). No census row is filed for it; doing so would have seeded the wall-implementer population with a below-floor claim against correct behaviour. It becomes the dead-probe worked example instead — the failure mode in its least obvious dress, since the probe does cast, at a fn boundary, against a properly declared refinement.

Mandate adopted as lane policy on the strength of this record: every below-floor or silent-class row the baseline stage produces carries a probe-adequacy receipt — closure and shape and the prevalence cross-check where a population exists — required, not advisory. Three consecutive mechanism explanations here were refuted while the conclusions they explained kept surviving; mechanism stories do not survive contact, executed discriminating controls do. Every superseded claim is kept in the gap analysis rather than overwritten, because the sequence is the lesson.

Probe-adequacy receipts for the four below-floor findings

The rule above applies first to the rows that prompted it. All four were originally measured under a fixture-only closure and have been re-run under the full dag + src/v2 pool, with the refined-brand cast as the discriminating control proving that harness can surface a diagnostic — so a zero there is the compiler's silence, not the harness's.

Finding Closure Result Verdict
Generic instantiation full pool 0 diagnostics unchanged
Field through generics full pool 0 diagnostics unchanged
Parse: list separator full pool 0 diagnostics unchanged
Exhaustiveness, type-variable scrutinee full pool 0 diagnostics unchanged

All four declare only local types over kernel scalars, so the wider pool adds no machinery they depend on — but that is now measured rather than argued.

The measurement it makes possible

Six floor classes as probe pairs (deliberately-bad input plus legitimate control), v1 pipeline → Rust target, synthetic single module. Every control compiles clean.

Class Bad input Observed Control
Generic instantiation Boxed { inner: "not an int" } at Boxed<Int> silent, 0 diagnostics clean
Field through generics fn get_field<T>(t: T) -> Int { t.no_such_field } silent, 0 diagnostics clean
Parse: list separator dropped three elements, one comma missing silent, 0 diagnostics clean
Exhaustiveness, type-variable scrutinee match t { Red => 1 } on T silent, 0 diagnostics clean
Exhaustiveness, coproduct scrutinee missing arm on declared closed variant refuses NonExhaustiveMatch, blocking clean
Record completeness record literal omitting a required field refuses MissingField, blocking clean

Two §1c census rows corrected by execution, both in the direction the census's Unknown default protects against:

  • Record completeness was carried as Unknown — unmeasured; it measures R2.
  • Closed-match exhaustiveness is not one rung — it splits by scrutinee path. And the row's attribution of the silence to PatternLookupBlocked => [] is wrong on the carrier: pattern_subject_from_node reaches that arm only where the scrutinee's inferred type is_compiler_error, i.e. where a diagnostic already exists. The silent arm is PatternDynamic { span: _ } => []. PatternLookupBlocked's own silence is not established by these probes and is not asserted as though it were.

Why a compiler change was authorized

The brief's scope said no compiler change. I stopped and escalated rather than improvising, because the alternative readings differ materially: migrating at Bool grain would be a declared rung lowering that deletes landed assertions (DESIGN §4b meta-obligation 3), and deferring would stall the node's highest-risk slice. The lane owner amended scope to admit exactly this surface, under five guardrails: one measurement-only builtin, projection of existing authorities only, fail-closed at the host seam, a registry row carrying its sibling's dissolution trigger, and evidence parity before any ct_ deletion.

On the contested ground: builtin_function_registry is itself a §3 fork awaiting the PrimitiveDefinition identity join, per gunbc.plans.compile_clean_forcecheck. The new row carries a note binding it to exactly its sibling's dissolution — when the join dissolves the registry, the two migrate together. One additive row is linear cost against that dissolution; the alternative was a schema no v1 probe could inhabit. The 04_method.dag diff is one row plus that note, nothing else.

Not in this PR

No wall lands and no wall is strengthened — probes measure. No ct_ test is deleted here: guardrail 5 requires the migrated probe to reach assertion parity in the same PR that removes the original, and that is slice 1, which also needs gunbc.guarantee_measurement (gunbc#7572) on main for the class/path identities. Nothing here builds against a copied schema.

🤖 Generated with Claude Code

Brian Searls and others added 4 commits August 1, 2026 07:28
The .dag side could observe only THAT a synthetic v1 compile refused, never
which judgment fired, whether it blocked, or how many times. That made the
Stage-0 receipt vocabulary uninhabitable on every v1 path: RefusedTyped and
AcceptedCounted both carry a diagnostic class and a count.

One measurement-only builtin, projecting existing authorities:
compile_clean_diagnostic_histogram_key for (class, name), the existing
compile_clean_diagnostic_is_hard delegation for severity carried as data.
Nothing is filtered; callers filter. CensusNotRunnable stays a distinct
coproduct arm so could-not-measure never reads as the subject passing.

Aggregation keys on (class, name, blocking) deliberately: folding severities
together would hide the blocking-to-advisory demotion this surface exists to
keep visible.

Receipts: 5/5 witnesses PASS by execution, 42ms eval. Six floor classes
measured as probe pairs; four are silent, two refuse. Two sec-1c census rows
corrected against the carrier.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Representation-without-producer is lane-level state the claims-carrier stage
needs when it derives its first dispositions: the census admits an advisory
observation and the schema admits FrontierAccepted, but no probe produces one
today, so deriving that disposition from probe receipts would be deriving from
an empty population. Named beside the advisory-half dissolve-on rather than
left in a PR body.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title ladder-probe-corpus: migrate the landed wall controls onto stable guarantee class/path identities (Spine A, guarantee lane) Guarantee probe observation surface: compile_dag_diagnostic_census, and the first floor-class measurement Aug 1, 2026
Brian Searls and others added 2 commits August 1, 2026 07:54
The P-B publication wall (gunbc#7560) requires one PublicFilePublishGrant row
per public-root file first added after the cutover commit; the gate is
diff-status-based, so both new files need rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An earlier entry recorded that no advisory specimen could be produced
synthetically, so AcceptedCounted was representable but had no producer. That
was wrong, and wrong because of the closure rather than the compiler: the
candidate probes ran through a fixture-only compile whose single source root
contains no std, so the refinement never resolved TO a refinement and the
negative measured the closure.

compile_dag_diagnostic_census resolves against the witness roots, and the same
where-refinement source fires WhereRefinementUnenforced as a counted advisory
through it. What prompted the recheck was corpus prevalence: the whole-tree
census carries 1,981 where-refinement advisories, so a probe finding none was
evidence against the probe.

Records the wrong-then-corrected sequence rather than just the right answer,
and extracts the rule the baseline stage inherits: a synthetic-probe negative
is only as good as its closure.

Receipts: 6/6 witnesses PASS by execution, exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 1, 2026 07:59
Brian Searls and others added 2 commits August 1, 2026 08:11
A discriminating control refuted the closure diagnosis. Under the full
dag + src/v2 pool the where-refinement ALIAS shape is still silent (zero
diagnostics with std fully available), while a cast to std's refined brand
fires WhereRefinementUnenforced on the same harness. So the closure was never
the discriminator between the failed probes and the working one; the shape was.

Widens the rule the baseline stage inherits: a synthetic-probe negative is only
as good as the probe's ability to REACH THE JUDGMENT, which fails two
independent ways -- a closure too narrow for the judgment's machinery, or a
shape that never triggers it. A closure-only receipt would have passed a probe
that measures nothing.

Adds probe-adequacy receipts for the four below-floor findings: re-run under
the full pool, zero diagnostics, verdicts unchanged, with the refined-brand
cast as the control proving the harness surfaces a diagnostic on that setup.

Both the original claim and its first correction are kept rather than
overwritten: a plausible mechanism accepted without a discriminating control is
how a wrong explanation survives its own correction.

Receipts: 6/6 witnesses PASS by execution, exit 0.

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

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Brian Searls and others added 2 commits August 1, 2026 08:16
Answering whether the silent where-refinement probe was a dead probe or a
finding. It exercises the alias -- it casts, at a fn boundary -- and its
declaration is structurally identical to std's own NonEmptyStr. That reads like
a below-floor finding. It is not.

A 2x2 crossing declaration site against cast subject pins the real axis:
local+parameter fires, local+literal silent, std+parameter fires, std+literal
silent. Declaration site is irrelevant; a user-declared refinement alias is
judged exactly as std's brand is. The axis is literal vs non-literal, and the
diagnostic names it -- "non-literal value at refined position". So the probe's
silence is CORRECT BEHAVIOUR: a literal at a refined position is deliberately
exempt, which is construction deciding what validation would otherwise check.

Filing it would have seeded the wall-implementer population with a below-floor
claim against correct behaviour. It becomes the dead-probe worked example
instead -- the probe-adequacy failure mode in its least obvious dress.

Also retires the previous explanation (the predicate "resolves nowhere"), which
local+parameter firing refutes. That is the third mechanism story refuted here
while its conclusion survived, which is what buys the mandate now recorded
beside the rule: probe-adequacy receipts are REQUIRED, not advisory, for every
below-floor row the baseline stage produces.

Receipts: 6/6 witnesses PASS by execution, exit 0.

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

Two findings from codex review 46144, both real.

(1) The exported census_rows(c: CompileDiagnosticCensus) answered [] on the
NotRunnable arm, so could-not-measure and observed-nothing became the same
empty list at the API boundary -- the exact fail-open this module exists to
prevent, one layer up and in its own surface. A prose caution told callers not
to read a zero out of it, which is validation standing where construction was
available, and the NotRunnable witness ENCODED the fail-open as an assertion.

Fixed by construction: the selectors now take List<CompileDiagnosticCensusRow>,
so obtaining rows requires MATCHING the coproduct and the collapsing arm no
longer exists. The caution is deleted WITH the accessor rather than surviving
beside it -- a warning about a state that can no longer be written is dead text
(DESIGN 4b dissolution). Every witness now matches, with NotRunnable => false,
so could-not-measure fails a witness rather than passing it.

(2) The new hand-Rust surface (cli_run compile_dag_diagnostic_census,
v1_interpreter compile_diagnostic_census_value) had no qualifying receipt: the
04_method.dag note covers the REGISTRY ROW and inherits its sibling's
PrimitiveDefinition trigger, which does not reach the Rust projection. Adds a
registered Disposition = Scaffold row with its own dissolution trigger, per the
convention compile_clean_diagnostic_policy already uses -- dissolves when the
substrate can read v1 diagnostics without a host transport, explicitly NOT
merely when the registry is replaced.

Receipts: 6/6 witnesses PASS by execution, exit 0.

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

Both findings in review 46144 are real and are fixed by construction rather than argued down. Pushed, not just replied.

Finding 1 — the exported projection reintroduced the fail-open. Correct, and it is worse than the review states: the module whose entire purpose is keeping could-not-measure distinct from observed-nothing reintroduced exactly that conflation at its own API boundary. The prose caution I attached ("a caller asking whether the subject was accepted must match the coproduct itself, never read a zero out of these") is validation standing precisely where construction was available — and the NotRunnable witness encoded the fail-open as an assertion (count(census_rows(not_runnable)) == 0), so the test suite was pinning the defect in place.

Fixed structurally: census_blocking_rows, census_advisory_rows, and census_rows_of_class now take List<CompileDiagnosticCensusRow>, so obtaining rows requires matching the coproduct. census_rows(c: CompileDiagnosticCensus) and its collapsing arm are deleted — the bad read is unwritable, not warned against. The caution deleted with the accessor rather than surviving beside it (§4b: a warning about a state that can no longer be written is dead text). Every witness now matches, with NotRunnable => false, so could-not-measure fails a witness instead of passing it.

Finding 2 — no qualifying hand-Rust receipt. Also correct, and the distinction you drew is the right one: the 04_method.dag note covers the registry row and inherits its sibling's PrimitiveDefinition identity-join trigger, which does not reach the Rust projection at all. Added a registered Disposition = Scaffold row with its own trigger, following the convention gunbc.compile_clean_diagnostic_policy already uses for its census transport. It dissolves when the substrate can read v1 diagnostics without a host transport — v2 self-hosting far enough for a probe to call the modeled compile, or a floor-enrolled diagnostic lens subsuming the projection — and explicitly not merely when the registry is replaced, since the projection is the scaffold and the registry row is only its type declaration.

Receipts: 6/6 witnesses PASS by execution, exit 0; whole-tree compile census re-running against the changed signatures.

Worth noting for the record: three prior approving reviews passed over finding 1. It was caught here, and it was the defect this PR was least entitled to ship.

— sent from snappy-eagle-314

Brian Searls and others added 2 commits August 1, 2026 08:42
…rule

Lane policy adopted from this entry's own review incident: enrolled evidence
must be DISCRIMINATING, not merely present -- one executing RED per refusal
arm, and a refusal arm without its RED is unmergeable regardless of approval
tally.

The receipt is this module's own defect. Its exported accessor collapsed
CensusNotRunnable to [], and its witness asserted that collapse as contract. A
green witness pinning the wrong contract is worse than no witness: it defends
the defect against its own fix. Three approving providers passed over it; one
caught it.

Two consequences the carrier stage inherits: a review tally never substitutes
for an executed discriminating control on a spine carrier; and where
construction is available it, not reviewer vigilance, is the wall -- the
collapsing accessor was deleted rather than documented, so a reviewer missing
what cannot be written costs nothing.

Receipts: whole-tree census exit 0, 4677 advisories (unchanged from the
pre-fix baseline, so the API change added no diagnostics); 6/6 witnesses PASS.

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

Thanks — taking the note-duplication finding seriously, but I'm not acting on it, and here's the measured reason.

src/v1/stage0/src/v1_compiler_infer_method.rs is a generated file. Line 1 reads // Generated by v1 compiler -- do not edit. — it is produced from src/v1/04_method.dag by regen_stage0. So compile_dag_diagnostic_census_row_note() is not a hand-authored second copy of the note; it is the emitter's uniform projection of the single .dag data row into its Rust realization.

The precedent is in the same file and pre-dates this PR: filesystem_read_result_type_note() sits at line 23 on origin/main, generated the same way from data filesystem_read_result_type_note: String in 04_method.dag. Every prose data ... : String row in that module renders as a zero-arg function; mine is the second, not the first.

That makes this DESIGN §7 rather than §3. The .dag graph is the authority and Rust is one realization of it — a generated projection of a single authority is not a fork, or the whole seed would be one. There is exactly one place to edit this note, and it is the .dag row.

Both suggested remedies would be net-negative:

  • Deleting the Rust function by hand edits a generated artifact, so regen_stage0 --verify would report divergence and red RegenVerifyGate — and the next regen would restore it anyway.
  • Dropping the .dag-side note would delete the actual authority, and the generated function would disappear with it, taking the row's rationale out of the tree entirely.

The "no callers" observation is accurate and is also true of the sibling — these rows are documentation carried on the carrier per DESIGN §6 ("let the mark on the carrier be the authority; no parallel-ledger docs"), which is exactly why the note lives beside the registry row it explains rather than in a doc.

Non-blocking either way, and the rest of the review matches my own receipts. Leaving the diff as-is.

— sent from snappy-eagle-314

Brian Searls added 3 commits August 1, 2026 13:41
codex review 46264: the dissolve-trigger offered two open-ended
alternatives and named no roadmap row, so nothing could evaluate it.
An unevaluable trigger is indistinguishable from no trigger, which is
the untracked-stall shape DESIGN 4b forbids.

Trigger is now one row: v1-zero-hand-maintained-rust, the v1-exit
lane's fourth finish line. Early removal stays permitted but is no
longer stated as an alternative - a trigger fixes the point by which
the scaffold MUST be gone.

Enrolled as executing evidence rather than prose, per the precedent in
stage0_rust_source_lifecycle_scaffold_witness_test. That precedent also
asserts a ROADMAP.md line offset; this file deliberately does not copy
that half - a positional citation into a generated artifact is what
DESIGN 3 rules out. Row id only.

8/8 witnesses PASS. RED controls confirm string_contains discriminates
and the old wording is gone, so the new assertions are not vacuous.
# Conflicts:
#	src/v1/stage0/src/v1_interpreter.rs
codex review 46306: four section 1c rows (generic instantiation, field
through generics, closed-match exhaustiveness, record completeness)
listed 'ladder-probe-corpus probe pair' as their Evidence. That reads
as an enrolled probe. None exists - those are this lane's future slice
work - so the canonical authority was carrying rung claims with no
executing evidence behind them. Rung inflation, in the doc whose whole
job is to prevent it.

Not reverted to UnknownUnmeasured, because that would discard a real
result: the measurements WERE executed, with sources and outputs in the
section 10 eighth-pass ledger. The honest state is measured-but-not-
enrolled, so each Evidence cell now says exactly that - one-off
execution, not enrolled, nothing re-runs it, nothing reds if the
behaviour changes.

Filed section 11 item 10 for the enrollment obligation rather than
folding it into item 9: item 9 migrates REDs that exist but run dark,
these classes have no probe at all. A move versus authorship.
@gunbai-bot

gunbai-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

review 46306 is correct, and it caught the sharpest possible version of this lane's own failure mode. Fixed in 139f074c.

The four §1c rows listed ladder-probe-corpus probe pair, v1 CompileAccept in their Evidence column. That reads as a probe that exists and runs. It does not — those probe pairs are this node's future slice work. Compare the parse-separator row two lines down, which cites tidy-deer-730 probe on gunbc#7484 + review 45347: a landed, enrolled probe. Mine were forward-looking citations sitting in an evidence column, which is rung inflation in the canonical authority, filed by the lane whose entire purpose is to prevent it. Exactly four occurrences, exactly the rows named.

I did not take either of the two suggested remedies verbatim, because reverting to UnknownUnmeasured would throw away a real result. Those measurements were executed — via compile_dag_diagnostic_census, with the source snippets and outputs recorded in the §10 eighth-pass ledger, and they are reproducible today. What they are not is enrolled. So the honest state is neither "unmeasured" nor "probe pair exists"; it is measured-but-not-enrolled, and each Evidence cell now says precisely that: a one-off execution, NOT ENROLLED, nothing re-runs it, nothing reds if the behaviour changes. That is §4b meta-obligation 4 stated against my own rows rather than about someone else's.

I filed §11 item 10 for the enrollment obligation rather than folding it into item 9. They look similar and are not: item 9 migrates REDs that exist but run dark, while these four classes have no probe at all. One is a move, the other is authorship, and sizing them together would hide the difference. Item 10 also carries the bound while it stands — a rung whose only evidence is an unenrolled measurement is honest about the past and silent about the present, so it must not be cited as durable evidence — and its dissolution condition is each of the four carrying a probe pair on the class/path identities, at which point the Evidence cells name the executing probe the way the parse-separator row already does.

One scope note on what I deliberately did not touch: the misspelled-label row (§1c, the landed call-shape wall) cites probe pair pre 0 diagnostics → post two located refusals. That evidence is real but rides the dark Rust suite, which is a different defect with a different remedy — it is item 9's migration, and it is the first slice of this node. Rewriting it here would have conflated the two gaps your finding just correctly separated.

— sent from snappy-eagle-314

@briansrls
briansrls merged commit 87fb808 into main Aug 1, 2026
9 of 10 checks passed
@briansrls
briansrls deleted the session/snappy-eagle-314 branch August 1, 2026 17:05
gunbai-bot Bot pushed a commit that referenced this pull request Aug 1, 2026
…ordered union

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 1, 2026
#7575 updated denominators for compile_dag_diagnostic_census only;
parse_stage0_cargo_manifest_bins (#7562) left the roster one row ahead of
the witness expectations.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 1, 2026
#7575 bumped the exact-count witnesses for compile_dag_diagnostic_census but
missed #7562's parse_stage0_cargo_manifest_bins arm; measured denominators are
183 derived rows, 172 arm identities, and 160 authored spellings.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 1, 2026
Two commits landed genuine new derived arms on the same day without the
pin catching up. #7562 added free_call.parse_stage0_cargo_manifest_bins
inside v1_builtin_arms! (the single-authority macro whose expansions
generate both the dispatch match and the roster from one token list) but
never touched this file's pinned literals. #7575 added
free_call.compile_dag_diagnostic_census, also inside v1_builtin_arms!,
and did update the pin -- but by incrementing the value already in the
file (181->182) rather than re-measuring, so it silently carried forward
#7562's un-pinned addition instead of catching it.

Both arms are confirmed DERIVED, not native: both sit inside the
v1_builtin_arms! macro body (verified by locating the macro's open/close
brace and grepping both arm names between them), so both are exactly the
kind of change this census is designed to auto-pick-up, not a weaker-
surface regression.

True values measured by execution (local sccache-off build of
claim_batch, run directly against this file with a scratch probe module
binary-searching each of the five counts via equality witnesses, deleted
before this commit): derived_arm_count=183, declared_arm_count=2,
dispatch_site_count=7, distinct_arm_identity_count=174,
authored_spelling_count=161, row_count=185 (183+2, checked against
v1_interpreter_row_count() directly). All 24 witnesses in this file now
PASS by execution.

exact_count_witness_note updated in the same commit so its illustrative
numbers (rows/identities/spellings) match the code again -- fixing only
the code literals while leaving the note stale would repeat exactly the
defect that made this repair necessary.

No other file in the corpus transcribes these five counts (checked by
grep across dag/ and src/v2/); the only other reference is an unrelated
net-line-count note in v1_interpreter_primitive_surface.dag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Aug 1, 2026
* WIP: Repair main red: interpreter primitive surface census

* Re-pin the v1 interpreter primitive surface census: 183/2/7/174/161

Two commits landed genuine new derived arms on the same day without the
pin catching up. #7562 added free_call.parse_stage0_cargo_manifest_bins
inside v1_builtin_arms! (the single-authority macro whose expansions
generate both the dispatch match and the roster from one token list) but
never touched this file's pinned literals. #7575 added
free_call.compile_dag_diagnostic_census, also inside v1_builtin_arms!,
and did update the pin -- but by incrementing the value already in the
file (181->182) rather than re-measuring, so it silently carried forward
#7562's un-pinned addition instead of catching it.

Both arms are confirmed DERIVED, not native: both sit inside the
v1_builtin_arms! macro body (verified by locating the macro's open/close
brace and grepping both arm names between them), so both are exactly the
kind of change this census is designed to auto-pick-up, not a weaker-
surface regression.

True values measured by execution (local sccache-off build of
claim_batch, run directly against this file with a scratch probe module
binary-searching each of the five counts via equality witnesses, deleted
before this commit): derived_arm_count=183, declared_arm_count=2,
dispatch_site_count=7, distinct_arm_identity_count=174,
authored_spelling_count=161, row_count=185 (183+2, checked against
v1_interpreter_row_count() directly). All 24 witnesses in this file now
PASS by execution.

exact_count_witness_note updated in the same commit so its illustrative
numbers (rows/identities/spellings) match the code again -- fixing only
the code literals while leaving the note stale would repeat exactly the
defect that made this repair necessary.

No other file in the corpus transcribes these five counts (checked by
grep across dag/ and src/v2/); the only other reference is an unrelated
net-line-count note in v1_interpreter_primitive_surface.dag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 1, 2026
…7581)

* Measure M2 floor retention on real claim_executor path (Dispatch J).

Records whole-corpus falsifier batch receipts showing schedule-derived eviction
bounds peak RSS at 6.27 GiB for 842 entries, supporting handback (a): named M2
capacity follow-ups do not rank now.

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

* Remove M2 measurement shell script; document verbatim invocations in receipt.

Matches #7533 operator precedent: receipt is documentation, not shipped execution.

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

* Retract overstated handback (a); keep width>1 bar authoritative.

Codex review 46229: width-1 falsifier receipt cannot record handback (a)
or deprioritize the throughline's width > 1 exit metric. Receipt reframed
as incomplete width-1 evidence; follow-ups remain NEXT.

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

* Fix primitive surface witness counts after #7562 arm.

#7575 updated denominators for compile_dag_diagnostic_census only;
parse_stage0_cargo_manifest_bins (#7562) left the roster one row ahead of
the witness expectations.

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

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 1, 2026
…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>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 1, 2026
…560)

The conditional belt-timer membership in the previous commit was a
workaround, and review 46541 was right to red it. belt_actuate_workdir
is a literal projection of srv1_gunbc_repo_root and belt_run_once_cli
took no argument, so EVERY belt timer would actuate production's tree
whatever instance installed it. Denying the twin a member routed around
that instead of fixing it -- an author-side absorbing fallback (DESIGN
section 5) -- and it broke the one-for-one twin mirror that
a_twin_deployment_on_one_host_collides_with_nothing asserts as its
non-degeneracy guard.

review 46560 proposes dropping that count-equality conjunct. Declined:
the witness's own non_degeneracy_without_transcription_note argues
against exactly this edit, because `all` over an empty list is vacuously
true, so a twin whose owned set collapsed to nothing would satisfy the
disjointness claim perfectly. Weakening it would remove the check that
caught this bug.

Root fix instead:

- belt_run_once_cli reads GUNBC_BELT_SPAWN_WORKDIR, fail-closed on
  absent AND empty. Defaulting it back would silently restore the
  original defect with production's tree as the thing absorbed.
- The emitted belt unit carries Environment= with that instance's own
  repo_root.
- Membership is unconditional again: production and twin both carry 7
  owned artifacts, so the mirror holds because the twin genuinely has a
  correct timer, not because the asymmetry was argued away.
- belt_run_once / belt_run_once_json keep the literal default as
  hand-invoked inspection surfaces; both route through one
  belt_run_once_in so the two paths cannot drift.
- The env var name is homed in gunbc.host_layout, whose precedent is a
  name with consumers on both sides of a boundary. Homing it in the belt
  would put tmux, git worktrees and the Claude preflight behind every
  emission of a deploy script, for one string.

Finding 2: twin_belt_timer_actuates_its_own_tree_not_productions reads
the EMITTED BODY. The belt timer is the one member whose damage is not a
path collision -- both instances install their own unit at their own
path, and what selects the tree lives inside the unit. Deleting the
Environment= line or hardcoding either root reds it.

Finding 3: the note claiming srv1-lab gets its own timer was false when
written and is now true; it records that it was false and why.

Verified by execution, not compile -- the omission that let this reach
review. Whole-corpus run: PASS=2722 FAIL=11, of which 9 are pre-existing
(8 compile_diagnostic_census entry-resolve failures from #7575, plus
containment_path_clone_bound_positive) and this branch touches none of
their files. The other 2 were the committed goldens, regenerated here
and re-verified green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot mentioned this pull request Aug 1, 2026
6 tasks
briansrls added a commit that referenced this pull request Aug 1, 2026
* WIP: debug build flake

* Part A: repair the merged observation authorities before depending on 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>

* WIP: debug build flake

* WIP: debug build flake

* Part B: require managed build-cache readiness before runner activation

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>

* WIP: debug build flake

* Repair the interpreter primitive-surface census pins; drop the debug 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>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 1, 2026
* Give the belt a driver: a deploy-owned tick timer

belt_run_once_cli is a complete ProcessExit entrypoint and nothing
invoked it, so a dispatched roadmap item reached Verification and
stopped there. roadmap_serve's serve_publish_note already named the
consequence from the other side: POST /publish/{node_id} "exists
because belt_tick has none".

Not modeled as a host timer beside gunbc.host_hygiene_reaper, because
that precedent does not install anything: host_hygiene_reaper_script
has zero consumers and is a declared Scaffold, the only reference to
the reaper outside its own module is a residue row about a fold shape,
and its own header describes its precedent (ctrl-runner-reclaim.timer)
as "a hand-deployed host timer with no prior .dag model". Copying it
would have produced a second modeled timer that never runs -- the
specification-without-execution trap, reproduced while fixing an
instance of it.

So the timer rides live_deploy, the one host-effect path this repo can
show executing, as a BeltTimerUnit owned artifact. One member, two unit
files, exactly as GunbcSourceTree owns gunbc-tree-sync.service plus its
env file.

Membership is CONDITIONAL, and that is the load-bearing part.
belt_actuate_workdir is a literal (srv1_gunbc_repo_root) and
belt_run_once_cli is zero-arg, so a tick spawns into that root whatever
unit invoked it -- WorkingDirectory does not help. An unconditional
member would therefore give the srv1-lab twin a timer spawning agent
sessions into production's tree, which is the failure
deployment_service_handler_note records for the serve handler. The
member is present exactly when the instance's repo_root IS the root the
belt actuates in, compared against the single host_layout authority
that belt_actuate_workdir itself projects. Dissolves when the belt
takes its spawn root from the deployment.

Cadence uses OnUnitInactiveSec, not OnUnitActiveSec: measured from tick
END, so a slow tick cannot schedule overlapping spawns.

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

* Regenerate the deploy goldens for the belt timer member

Generated projections of gunbc.live_deploy.emit, regenerated via
main_wet on dag/tools/generated_artifact_gate.dag -- not hand-edited.

Produced with a freshly rebuilt binary. The first rebuild attempt went
through ctrl-build, which ran REMOTELY ("Uploaded 0 artifacts", "Remote
run completed") and left the local binary untouched at its previous
mtime; regenerating with it would have emitted goldens from a stale
compiler while looking authoritative. Caught by comparing the binary
mtime, then rebuilt with CTRL_BUILD_BYPASS_SHIMS=1.

Apply gains: both unit files written to the stage dir, installed 0644,
daemon-reload, then enable + restart of the TIMER (not the service --
the timer is what schedules, the oneshot is what it triggers).

Retract gains: disable --now the timer, remove BOTH unit files, then
daemon-reload. Removing both is why the member owns two files rather
than two members owning one each.

Every added command is install / systemctl / rm -- all whole-binary
NOPASSWD grants in fleet_posix_ci_runner_sudo_grants, so the emitted
script stays inside the deploy principal's granted set.

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

* WIP: Roadmap

* Make the belt spawn root a per-deployment parameter (review 46541, 46560)

The conditional belt-timer membership in the previous commit was a
workaround, and review 46541 was right to red it. belt_actuate_workdir
is a literal projection of srv1_gunbc_repo_root and belt_run_once_cli
took no argument, so EVERY belt timer would actuate production's tree
whatever instance installed it. Denying the twin a member routed around
that instead of fixing it -- an author-side absorbing fallback (DESIGN
section 5) -- and it broke the one-for-one twin mirror that
a_twin_deployment_on_one_host_collides_with_nothing asserts as its
non-degeneracy guard.

review 46560 proposes dropping that count-equality conjunct. Declined:
the witness's own non_degeneracy_without_transcription_note argues
against exactly this edit, because `all` over an empty list is vacuously
true, so a twin whose owned set collapsed to nothing would satisfy the
disjointness claim perfectly. Weakening it would remove the check that
caught this bug.

Root fix instead:

- belt_run_once_cli reads GUNBC_BELT_SPAWN_WORKDIR, fail-closed on
  absent AND empty. Defaulting it back would silently restore the
  original defect with production's tree as the thing absorbed.
- The emitted belt unit carries Environment= with that instance's own
  repo_root.
- Membership is unconditional again: production and twin both carry 7
  owned artifacts, so the mirror holds because the twin genuinely has a
  correct timer, not because the asymmetry was argued away.
- belt_run_once / belt_run_once_json keep the literal default as
  hand-invoked inspection surfaces; both route through one
  belt_run_once_in so the two paths cannot drift.
- The env var name is homed in gunbc.host_layout, whose precedent is a
  name with consumers on both sides of a boundary. Homing it in the belt
  would put tmux, git worktrees and the Claude preflight behind every
  emission of a deploy script, for one string.

Finding 2: twin_belt_timer_actuates_its_own_tree_not_productions reads
the EMITTED BODY. The belt timer is the one member whose damage is not a
path collision -- both instances install their own unit at their own
path, and what selects the tree lives inside the unit. Deleting the
Environment= line or hardcoding either root reds it.

Finding 3: the note claiming srv1-lab gets its own timer was false when
written and is now true; it records that it was false and why.

Verified by execution, not compile -- the omission that let this reach
review. Whole-corpus run: PASS=2722 FAIL=11, of which 9 are pre-existing
(8 compile_diagnostic_census entry-resolve failures from #7575, plus
containment_path_clone_bound_positive) and this branch touches none of
their files. The other 2 were the committed goldens, regenerated here
and re-verified green.

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>
briansrls pushed a commit that referenced this pull request Aug 1, 2026
…ission (#7619)

* WIP: debug build flake

* Part A: repair the merged observation authorities before depending on 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>

* WIP: debug build flake

* WIP: debug build flake

* Part B: require managed build-cache readiness before runner activation

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>

* WIP: debug build flake

* Repair the interpreter primitive-surface census pins; drop the debug 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>

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

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>

* Bind the compile-pool receipt to its host and slice; derive it

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>

* Repoint the two fleet-subsumption citations of the deleted enable path

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>

* Add the host field to the third HostCompilePoolReady fixture

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>

* Compare the observed slice ceiling against the derived pool size

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>

---------

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