Skip to content

Make the v2 frontend census typed and exactly reconciled - #8179

Merged
briansrls merged 33 commits into
mainfrom
session/crisp-lynx-832-census
Aug 12, 2026
Merged

briansrls merged 33 commits into
mainfrom
session/crisp-lynx-832-census

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Increment 2 of the v2 self-host frontier lane. Three delivered facts, one explicit non-delivery.

What lands

FrontendStage replaces string verdicts. classify_source answered in String, so every
consumer compared against a literal and a typo produced a verdict rather than a refusal. The
result space is now closed: LexRefused | ParseRefused | NormalizeGraftRefused | NormalizeRetained | NormalizeOther | NormalizeAccepted. Equality is structural;
frontend_stage_label is the sole renderer and deliberately not the comparison mechanism.

Normalize refusal classification consumes NonEmptyDiagnostics. The classifier took
List<Symbol> and had to answer NORM_REASON_SET_INVALID_EMPTY for the empty list, because that
input was constructible and mapping it to a real stage would render a missing observation as an
observed one. NonEmptyDiagnostics.head is not optional, so that input has no representation and
the defending arm has no subject — a climb to structurally-impossible for that one class. The RED
that fed it an empty list retires with the state, not with the wall; it cannot be authored without
re-widening the parameter to admit the value it tests.

Census reconciliation checks roster and observations in both directions and at multiplicity.
The first version established coverage only — per roster label, does some observation exist — and
I wrongly called that an identity join. It accepted a stray sixteenth observation and a duplicated
member. reconcile_census now returns CensusExact | CensusRosterInvalid | CensusMissingMember | CensusDuplicateMember | CensusUnrosteredObservation carrying the offending label, and the roster
is validated before the join: a duplicated label is satisfied by one observation counted once for
both rows, and a duplicated path under two labels would have the census read one file twice and
report it as two members. census_is_complete is derived from the verdict rather than computed
beside it.

What does NOT land

The live 15-member census still does not execute as a required PR gate. dag_grammar()
construction alone measured over the five-second fast-lane budget before any member is read, so
observe_roster remains a reproduction instrument in a non-test module and the durable claims run
on synthetic specimens. Nothing here claims otherwise. The two stage-separation claims deleted in
the previous increment are still owed to that executing instrument; what changed is that it now
has a total verdict type and a validated denominator to join against.

There is no per-stage count: count_stage took the unreconciled population and had no consumer,
so it was deleted rather than kept beside a note explaining how a duplicate corrupts exactly the
figures it produced.

Review note

This branch is stacked on #8149, so its diff against main currently presents both increments.
Several approvals here describe #8149's half rather than the census work — see the comments
below. Merge #8149 first; this PR's diff then reduces to the census files.

gunbc-ci-auto-heal and others added 26 commits August 11, 2026 19:48
…elabelling genuine body-lowering rejections as retention
… partial receipt, one consistent roadmap sequence, scratch residue deleted
…rtion, read contract recorded not fabricated, roadmap by time grain, resolve-ready naming
…link, and over-budget stage claims deleted with the gap recorded
…d left half-bound

The 13 witness failures at eff3eaa are caused by the repair, not exposed by it. All three
modules were fully bare — every name resolved by pool-membership coincidence (DESIGN Class B).
Naming ONE dependency changed the closure that gets assembled, and the extdeps catalog rows two
of the witnesses read stopped resolving: `no such function: hermetic_fixture_file_facts`, etc.

A partial import list is the worst of the two states: it neither binds the module's dependencies
nor leaves the coincidence intact. So the repair is the whole list, and every symbol home below
was verified at its declaration rather than by name search — std.cache_identity for the artifact
kind ids and the receipt digest, std.cache_interface for the kernel types and route/write folds,
extdeps.cache + extdeps.cache.types for the catalog projections, and the per-family extdeps
realization modules for the rows themselves.

realize_kernel_test stayed GREEN under the partial list its two siblings went red under, and is
completed here anyway. Green under a bare reference is not evidence the reference is bound; it
is evidence that this run's closure happened to contain it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
…at only re-imports them

ContentAddressedByValue, HandAuthoredString and NativeInternalHash are declared on
std.cache_interface KeyDerivationClass; extdeps.cache.types merely imports them at the top of
its own file. I read those lines as declarations because a name-ranked search returned them,
which is the third instance in this PR of the same failure — a name-level observation standing
where a bound declaration was required, the exact class the PR's subject is about.

Every one of the 54 imported names across the three files is now checked against a declaration
form (col-0 type/fn/data, or a variant on its coproduct) rather than against an occurrence.
StructuredArtifact is declared inline on `type ValueShape` and is correct as imported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
…check the written file

ExecutionReceiptDigest and execution_receipt_digest_of_value are declared on std.cache_identity;
I wrote them into the std.cache_interface block. The previous commit's verification did not catch
it because it checked a list of (name, intended module) pairs I held in mind, not the pairs the
file actually contains — so it confirmed the homes I had already re-derived and said nothing
about where the import statement put them.

The check now parses the three files' import blocks and tests each (module, name) pair as
written against a declaration in that module's source. 71 pairs, no remaining mismatch; the two
StructuredArtifact flags are the checker's own blind spot for inline variant lists
(`type ValueShape = RawBytes | StructuredArtifact | ...`), confirmed correct by direct read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
…an identity join

Increment 2 of the frontier lane, delivering the two structural halves and NOT the third.

CLOSED RESULT SPACE. classify_source answered in String, so every consumer compared against a
literal and a typo produced a verdict rather than a refusal. It now returns FrontendStage =
LexRefused | ParseRefused | NormalizeGraftRefused | NormalizeRetained | NormalizeOther |
NormalizeAccepted, with frontend_stage_label the single rendering authority.

THE EMPTY-REASON-SET ARM IS GONE BECAUSE ITS INPUT IS. The classifier took List<Symbol> and had
to answer NORM_REASON_SET_INVALID_EMPTY for the empty list. It now takes NonEmptyDiagnostics,
whose head is not optional, so that input has no representation. The RED that fed it an empty
list retires with the state rather than with the wall — the dissolution rule preserves a control
when the invalid state stays writable, and this one cannot be authored at all without re-widening
the parameter to admit the value it tests. Recorded in the file rather than left to be inferred
from a deleted test.

COMPLETENESS IS A JOIN. Fifteen hand-written m_* fns had no denominator: drop one and the
remaining fourteen still answer, so 'all fifteen observed' only ever meant 'fifteen fns were
present'. They are now rows on frontier_roster, and census_is_complete asks per roster label
whether a matching observation exists. Three controls, including one that a count equality would
pass — fifteen observations, none of them a roster member.

NOT DELIVERED, and it is the increment's headline item: the live fifteen still cannot gate a PR.
dag_grammar() construction alone exceeds the fast-lane budget before any member is read, so
observe_roster stays a reproduction instrument in a non-test module and the durable claims run on
synthetic specimens. The two stage-separation claims deleted last increment are still owed to the
batched or realized instrument; what changed is that it now has a total verdict type and a
denominator to join against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 12, 2026 08:42
…r cannot infer

compile-clean refused all five `empty: []` arguments: fold_list's empty parameter is the generic
accumulator A, so a bare literal has no expected type to be a collection of. The corpus idiom is
an explicit cast, used the same way in gunbc.package_delivery.

Diagnostic returns to the witness import list — it was dropped as unused when the specimen helper
stopped naming it, and the cast names it again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
@gunbai-bot

gunbai-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Flagging a coverage gap in this PR's approvals, against my own interest.

Three reviews have now approved this PR, and all three describe body_lower_finish_for_normalize and the // AnnotationRule — which are #8149's changes, not this one's. This branch is stacked on #8149, so a diff against main contains both, and the reviewers have consistently landed on the older half.

The census increment itself — the FrontendStage result space, the NonEmptyDiagnostics narrowing, and the frontier_roster identity join — has drawn no review. The approval tally on this PR should not be read as coverage of it.

This self-corrects when #8149 merges: this PR's diff then shrinks to the census work alone and a subsequent review would have to look at it. I am not restructuring the branches to force that sooner, but I would not treat the current 3 approvals as satisfying the two-approval bar for the new code.

— sent from crisp-lynx-832

@gunbai-bot

gunbai-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Correcting my comment above.

A fourth review (cursor/composer-2.5) explicitly names the census work — "separates revision-scoped frontier observation from durable classifier/roster laws" — so it engaged with this PR's own increment rather than only the stacked #8149 half. My claim that the approvals covered none of the new code was wrong for that review.

What still stands: the three earlier approvals do describe only #8149's changes, so the tally overstates coverage of the census work. What does not stand: the flat "has drawn no review". One review has looked at it.

— sent from crisp-lynx-832

gunbc-ci-auto-heal and others added 4 commits August 12, 2026 10:25
…et half

census_is_complete asked, per roster label, whether SOME observation carried it. That is the
covering direction alone, and I described it as an identity join, which it was not. A census of
all fifteen members PLUS a stray sixteenth passed it, and so did one carrying a member twice —
neither condition can make a per-label existence check fail.

reconcile_census now joins both directions and at multiplicity, returning CensusReconciliation =
CensusExact | CensusMissingMember | CensusDuplicateMember | CensusUnrosteredObservation with the
offending label. The three failures are not one state: a missing member means coverage is short,
an unrostered observation means the roster and instrument have drifted apart, and a duplicate
silently doubles that member's contribution to every count_stage figure derived from the census.
Collapsing them into one Bool is what would have made those figures unfalsifiable.

census_is_complete is now DERIVED from the verdict rather than computed beside it, so there is no
second Bool fold that could disagree with the coproduct.

Four controls, two of which the previous version passed: the stray and the doubled census each
contain every roster member. The notes claiming an identity join are corrected at the point of
assertion rather than left for the next reader to discover.

Found by a question from the planning side asking whether the census proved exact reconciliation
or mere coverage. It proved coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
classify_source returns FrontendStage; p_hex_escape and its five siblings still declared
-> String, so every one of them was a type error waiting for the next compile-clean run. I
changed the classifier's result type and updated its structured consumers without checking the
six one-line probes at the bottom of the same file.

Found by the planning side reading the file, not by me re-reading what I had edited.

Also merges origin/main, which now carries #8177 — the rename of the OTHER ArtifactIdentity.
That PR touches self_host_generation_identity_witness_test, self_host_promotion_admission_
witness_test and self_host/generation.dag: exactly the files I retracted imports from on #8149,
and none of the three I kept. It confirms the homonym split rather than superseding those edits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
…the unadmitted stage count

Four repairs from an external review of the census increment. Three are defects I introduced.

THE ROSTER WAS NEVER VALIDATED. reconcile_census checked observations against the roster and
assumed the roster itself was sound. Two malformed rosters defeat that with CensusExact still
reachable: a duplicated LABEL is satisfied by one observation counted once for BOTH rows, so
fifteen rows with fourteen labels reconcile exactly against fourteen observations; a duplicated
PATH under two labels is invisible at label grain entirely, and the live census would read one
file twice and report it as two members. verify_roster now runs first and CensusExact is
unreachable without FrontierRosterValid. It takes the rows rather than reading frontier_roster,
because a validator that can only see the one live roster is unfalsifiable — it would report
valid and nothing could establish it reports anything else.

STAGE EQUALITY WAS THE RENDERER. frontend_stage_eq compared frontend_stage_label results, making
presentation the semantic authority: a label rename would change which stages compare equal. Now
structural; the pairwise-distinctness claim is about rendering only, which is what it should
always have been.

count_stage IS DELETED. It took the unreconciled population, so it would total a census missing a
member, carrying a duplicate, or built on an invalid roster — the exact corruption the note
beside it describes. It also had no consumer. Counting returns on a carrier only a reconciled
census can construct, with the executing census that gives it a producer.

The six manual probes now render through frontend_stage_label rather than returning FrontendStage.
Their only consumer is a person invoking one by hand, so label text is the right interface; the
typed vocabulary is for structured consumers.

Four roster controls, including reordering, since row order is not a fact about the roster and
must not become one about the verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
@gunbai-bot gunbai-bot Bot changed the title v2 self host Make the v2 frontend census typed and exactly reconciled Aug 12, 2026
@gunbai-bot

gunbai-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

The ci failure on ebd41283e was inherited from main, not introduced here.

#8177 renamed v2.compiler.self_host.generation.ArtifactIdentity to GeneratedArtifactIdentity; #8187 swept the remaining consumer, gunbc.self_host_artifact_materialization. My merge of main landed between the two, so this branch carried the rename without its sweep and the consumer's import stopped resolving.

Merged main again to pick up #8187. No change to the census work.

— sent from crisp-lynx-832

…non-fold residue

The floor refused non_fold_residue_no_unrostered_or_stale. v2.lens.non_fold_residue counts a
match on a FUNCTION PARAMETER whose type is a closed coproduct and whose body carries a top-level
wildcard arm, in a non-test .dag. The structural equality I landed last push is exactly that
shape — `match b { LexRefused => true  _ => false }`, six times — so making equality structural
introduced the residue in the same motion that removed the renderer from the equality path.

Both facts are real and the resolution is neither a roster row nor a revert: enumerate the arms.
All thirty-six positions are written out.

The verbosity is the point rather than a cost being tolerated. Under wildcards, adding a seventh
stage leaves this function compiling and quietly answering false for every comparison involving
it — the same silent-widening the lens exists to catch. Enumerated, the addition fails to
typecheck at all thirty-six positions, so a new stage cannot be introduced without deciding its
equality against every existing one.

Verified no match-on-parameter in the file carries a wildcard: the remaining ones scrutinise
let-bound values or closure parameters, which the census does not count and which cannot hide a
missing coproduct case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBBegUkJygQyr1zMHiv2eK
@briansrls
briansrls merged commit cdadb08 into main Aug 12, 2026
5 checks passed
@briansrls
briansrls deleted the session/crisp-lynx-832-census branch August 12, 2026 15:45
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