Repository navigation
Seed-growth receipt for the bare-reference scanner that shipped via #9090 - #9102
Conversation
…ed the module that happened to declare its letter
`bare_identifier_candidates` records which identifiers a file BINDS so they are
not mistaken for references to other modules' declarations. It recognises the
parenthesised lambda form `(a, b) => ...` and the pattern form `{ .. } => ...`,
both through `destructuring_bound_spans`. It does not recognise the
single-parameter form with no parentheses:
algebra_templates_for_profile(profile: profile) |> any(t => t.name == name)
`t` is a binder. It was scanned as a reference, resolved against the WHOLE POOL
by `bare_reference_pull_paths_for_source`, and matched `fn t(component, terminal)`
in `dag/test/claim/pcb_footprint_witness_test.dag` — so `dag/std/algebra.dag`
acquired a closure edge to a PCB witness test, and through it to the PCB product
corpus, spatial frames, orthogonal topology, observation and attribution.
The same shape ran twice more in the same closure: `ok` in `error_primitives.dag`
matched `fn ok(out: String)` in a spark witness test, and `w` matched another.
WHY THIS SURFACED NOW rather than years ago. The bare census is gated on
`source_declares_import_lines` — a file that declares any import is skipped by it
entirely. On main almost every file declares imports, so the defect is dormant.
The namespace cut deletes every import line in the corpus, which switches the
bare census on for every file at once; the defect is not new, its population is.
THE GUARD IS THE PRECEDING TOKEN, NOT THE ARROW. A match arm head is also
`ident =>`, and binding it would suppress a real edge to the module declaring the
variant. An arm head is preceded by `{`, `}` or a newline; a lambda parameter
sits in argument position, after `(`, `,` or a named-argument `:`. Measured over
the corpus, all 4397 sites of the form `[(,:] ident =>` are lambdas and no match
arm head is preceded by any of the three.
MEASURED, one command, three arms, `gunbc compile --source-root dag
--source-root src/v1 --entry src/v1/compile.dag`:
main, unpatched binary: 99 sources, 0 blocking, exit 0
main, patched binary: 99 sources, 0 blocking, exit 0 <- no regression
cut branch, unpatched: 172 sources, 65 blocking
cut branch, patched: 155 sources, 34 blocking
Main is byte-for-byte unchanged in both closure size and diagnostics, so the
narrowing removes only edges that were never real. On the branch where the
defect is live it removes 17 modules from the seed's compile subject and half
its blocking diagnostics.
The two arms on main are the discriminating control in the honest direction: a
change that narrows a closure can always be made to look good by narrowing it too
far, and the main arm is what rules that out.
…led whatever module declared that name at top level
Second instance of the same class as the previous commit, found by re-tracing
after it landed. `bare_identifier_candidates` records binders; the global bare
census indexes top-level declaration HEADS. A coproduct VARIANT is neither, so a
module that declares a variant and then writes it was scanned as referencing
someone else — and the whole pool was consulted.
Four in one closure, every one absurd in the same way:
std.cache_interface `type VisibilityScope = Repo | Org | Network | World`
used `World` -> pulled dag/product/spatial_world.dag
(`type World sole_constructor`) and the spatial corpus
std.measure `| Volume` (its own Dimension) used in `Measure<Volume,
Nano, Nat>` -> pulled gunbc.roadmap_model (`type Volume`)
std.realization_sched `| Measured` used as `basis: Measured`
-> pulled std.observation (`type Measured<T>`)
std.spatial_frame `= ExtentInFrame { length: FrameLength }`
-> pulled std.attribution (`type ExtentInFrame`)
This is not a tiebreak between candidates and not a policy about which module is
likelier. A name the module itself declares is BOUND BY THAT DECLARATION, so it
cannot be a reference out; skipping it is the language's own scoping rule. That
matters for how the change is read: the nearest-ancestor picker below is still a
heuristic, and this commit does not improve it — it removes a population that
should never have reached it.
MEASURED, same command and arms as the previous commit:
main, unpatched: 99 sources, 0 blocking, exit 0
main, both guards: 99 sources, 0 blocking, exit 0
cut branch, unpatched: 172 sources, 65 blocking
cut branch, lambda: 155 sources, 34 blocking
cut branch, both: 104 sources, 4 blocking
Main is unchanged across every arm. The cut branch's seed closure converges to
104 against main's 99 — the remaining five are real edges the cut's own
qualifications introduced, not over-pull — and the residue is 4 diagnostics.
Second commit: the same class again, found by re-tracing after the first landed
This one is not a tiebreak and not a policy: a name the module itself declares is bound by that Full arm table, both guards
Main is unchanged in every arm. The cut branch's seed closure converges to 104 against main's 99, and On the review's wording, because it matters for what this change claimsThe review calls the arrow-lambda guard a heuristic. The lookahead is a lexical test, not a One thing worth stating because the guard could have been written the wrong way round and the wrong Correction to something I said elsewhere, not in this PRIn a status message I cited — sent from crisp-crab-430 |
… trace now says which arm resolved a name
THIRD instance of the class, and it closes the third diagnostic.
`dag/std/error_primitives.dag` declares `type Result<ok, err> = Ok { value: ok }
| Err { value: err }`. `ok` and `err` are TYPE PARAMETERS. Scanned as references
they reached the whole-pool arm and matched `fn ok(out: String) ->
SshSessionExecResult` in `dag/test/claim/spark_serving_durability_witness_test.dag`,
so `std.error_primitives` acquired closure edges to that witness and to
`extdeps.ssh.session` — and `extdeps.dns.domain_name`'s
`Ok { value: labels |> list_push(label) }` then typed `labels` as
`SshSessionExecResult` and reported `list_push` unresolvable on it.
Arms, one command throughout:
main, unpatched: 99 sources, 0 blocking, exit 0
main, three guards: 99 sources, 0 blocking, exit 0
cut branch, unpatched: 172 sources, 65 blocking
cut branch, two: 104 sources, 4 blocking
cut branch, three: 94 sources, 2 blocking
Main is unchanged across every arm.
SECOND CHANGE, and it is the reason the third instance was findable: the
resolution arm is now carried instead of discarded.
let target_module = match resolve_in(&census) {
Some(m) => Some(m), // scoped census hit
None => resolve_in(&pool_bare_census(index)?), // WHOLE-POOL fallback
};
collapsed both arms into one `Option<String>` one line after computing the
distinction, so `GUNBC_BARE_PULL_TRACE` could report WHAT a name resolved to and
never HOW. Those are different facts: a pool-fallback hit means the closure is
depending on ambient pool membership rather than on anything the file's own tree
provides, which is precisely the state a zero-import corpus can sit in while
looking green. The arms already know which fired; carrying it costs nothing.
First measurement it makes possible, on the cut branch: 37389 scoped against 733
whole-pool fallbacks, 508 of them (distinct file+name) from `src/v1`. Every one
of the 23 distinct names whose fallback answer differs from the module main's
import list named is a RE-EXPORT — `v1.std.core` imported and re-exported them
from `std.syntax` / `std.types`, and `src/v1/00_core.dag` declares none of the
23 — so the fallback names the declarer where the import named the re-exporter.
No wrong-provider selection was found. That is a measurement the previous trace
could not express.
Third commit (
|
| tree | binary | closure | blocking | exit |
|---|---|---|---|---|
| main | unpatched | 99 | 0 | 0 |
| main | three guards | 99 | 0 | 0 |
integration/namespace-cut-fresh |
unpatched | 172 | 65 | 1 |
| two guards | 104 | 4 | 1 | |
| three guards | 94 | 2 | 1 |
The second change is why the third instance was findable
let target_module = match resolve_in(&census) {
Some(m) => Some(m), // scoped census hit
None => resolve_in(&pool_bare_census(index)?), // WHOLE-POOL fallback
};Both arms collapsed into one Option<String> one line after computing the distinction, so
GUNBC_BARE_PULL_TRACE could report what a name resolved to and never how. Those are different
facts: a pool-fallback hit means the closure is depending on ambient pool membership rather than on
anything the file's own tree provides — precisely the state a zero-import corpus can sit in while
looking green. The arms already know which fired; carrying it costs nothing and needs no new
mechanism.
First measurement it makes possible (cut branch): 37389 scoped, 733 whole-pool fallbacks, 508
of them distinct file+name rows under src/v1.
Of those 508: 318 selected exactly the module main's import list named; 157 are names that import list
never named at all (List, Bool, Map, Set and other kernel-ish spellings); and 33 selected a
different module. Those 33 are not wrong answers. All 23 distinct names in them are re-exports —
main's import named v1.std.core, the fallback named std.syntax or std.types, and
src/v1/00_core.dag declares none of the 23 (checked individually). The fallback names the
declarer where the import named the re-exporter.
Worth recording as a method note, since it nearly went the other way: an import list is not a valid
oracle for "expected provider." It is evidence of where a name was reached from, not where it lives.
Any scoped-vs-fallback census has to compare against the declaration.
This commit does not change the fallback arm's behaviour — it only stops the arm's identity being
discarded. Whether that arm should refuse instead of widening is a separate question, deliberately not
in this PR.
— sent from crisp-crab-430
…, and carry the census's own verdict
REVIEW FINDING, verified and correct. `module_self_declared_names` opened a
coproduct block only when the `type X =` head line carried its own `=`. The
corpus also writes
type FrameExtent
= ExtentInFrame { length: FrameLength }
| ExtentFrameUnregistered { .. }
so `ExtentInFrame` and `Predicted` — two of the four variants the guard exists
for — were never collected. The closure improvement I measured for
`std.spatial_frame` and `std.realization_schedule` therefore came from those
modules leaving the closure for an unrelated upstream reason, not from this
guard. Right conclusion, wrong evidence, and the review caught it.
The fix is one predicate: a bare `type X` head opens a pending coproduct too.
The same-line extraction is re-gated on the LINE's own `=` rather than on the
flag, so widening the flag cannot silently disable it — which it did in the
first attempt at this repair.
WHAT THAT FIRST ATTEMPT COST, recorded because the number is the whole argument:
rewriting the scanner to gather each declaration as a BLOCK (with an alias guard,
so `type List<element> = FreeMonoid<element>` would stop contributing
`FreeMonoid`) is the obviously nicer design, and it measured WORSE — the branch
arm went 94 sources -> 126 and 2 blocking -> 3, while main stayed at 99/0. Two
distinct causes were found and fixed inside it (a blank line inside a coproduct
ended the block, silently reopening the `Volume` -> `gunbc.roadmap_model` edge
guard 2 closes) and it was STILL worse, so the alias distinction interacts with
something not yet understood. The line-wise scanner is kept and the alias
over-collection is left in place, marked, as a known under-pull needing its own
arms rather than a rider on this one.
REGRESSION TESTS, as the review asked — five, over the scanner paths this PR
adds: every coproduct form including the multiline one and blank lines inside a
block; record field labels NOT collected; a parenthesis-free arrow lambda
parameter bound while a match arm head is NOT (the direction that matters, since
binding an arm head is a silent under-pull); and the named-argument and
comma-positioned lambda forms.
SECOND CHANGE: the trace now carries the census's own verdict beside the arm.
`resolve_in` returned `Option<String>` and discarded whether the lookup was
`GlobalBareUniqueBinding` or `GlobalBareAmbiguousBinding` — the one authority on
whether a bare name has competing declarations. Reconstructing that from source
does not work: a line-leading `=`/`|` declaration scanner undercounts (it misses
`type Connective = Conj | Disj | NoConnective | Arrow` entirely) and a permissive
one overcounts (it reads alias targets and `data` initializer heads as variants),
and the two answers differed by 30x on the same trace. The census already knows.
First measurement it makes possible, cut branch under regen's roots:
35017 scoped unique
534 scoped service
9 scoped AMBIGUOUS
737 pool-fallback, every one unique
So the nearest-ancestor picker is consulted nine times in this closure and never
on the whole-pool arm. Those nine are `Json`, `owner`, `row` and
`rm_force_command` — real competing declarations, decided by module-path
proximity.
Arms unchanged by this commit: main 99 sources / 0 blocking / exit 0; cut branch
94 sources / 2 blocking.
review 55386 — verified, correct, fixed in
|
review 55399: `module_self_declared_names` collected the first identifier after a type `=` as a self-declared variant, alias targets included, so `type List<element> = FreeMonoid<element>` suppressed the closure edge to FreeMonoid's declarer. A fail-open under-pull, the dangerous direction. An earlier revision carried it as a marked KNOWN GAP with no owner and no trigger. That was the error under review, not just the scanner: a marked gap records how debt ENDS, it does not authorize creating it. The discriminator is the right-hand side's own shape — a coproduct alternates or carries a record payload, an alias does neither. The arm shape cannot decide (`type X = Foo`, bare capitalized RHS) is censused rather than assumed: 60 occurrences across dag + src/v2 + src/v1, every one an alias. 47 name a type in another module — the population this changes behaviour on. 13 name a same-file type and are inert either way, since a same-file `type` head is already self-declared via the declaration-head arm. Zero are single-alternative coproducts. Not claimed: that the ambiguity is impossible; the residual failure would be an over-pull. The closure is INVARIANT under this fix on this corpus — 94/2 before and after, because the 47 restored edges reach targets already pulled by another route — so the closure measurement cannot be evidence the fix works, and the two new tests carry the correctness claim alone. Executed, not argued: with the predicate forced to `false` the alias test FAILS and the payload test stays green. 7 pass in the lib target. Main unchanged at 99 sources / 0 blocking / exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 55399 is right, and the marking was the errorThe finding is correct as stated. I had carried it as a marked KNOWN GAP with no owner and no trigger, on the grounds that the obvious repair measured worse. That justification does not survive contact with the rule: a marked gap records how debt ends, it does not authorize creating it. Fixed, not defended. The discriminator is the right-hand side's own shape — a coproduct alternates ( The arm the shape rule cannot decide, censused rather than assumed
Zero of the 60 is a single-alternative coproduct. What is not claimed: that the ambiguity is impossible. The closure arm is insensitive to this fix, so the tests carry the correctness claim aloneMeasured on the cut branch, both arms under the same binary:
Byte-identical. The mechanism is not "the fix did nothing": the 47 restored edges point at targets already reachable by another route — The consequence matters more than the number, and I would rather state it than let a reviewer derive it unaided: since the closure is invariant under this change on this corpus, the closure measurement cannot be evidence that the fix works. A broken discriminator and a correct one both produce 94/2 here. The two new unit tests are the whole evidence base. Which is why this is worth recording: my first run of them reported Discriminating RED, executed rather than argued. With the predicate replaced by One post-hoc reading, labelled as suchThe null result also refutes a hypothesis I was chasing about this branch's two remaining blockers — that they came from a short closure caused by this very under-pull. This fix can only enlarge a closure, so a change was available and did not happen. I am labelling that post-hoc rather than dressing it up as a prediction: the measurement had already returned before I framed it that way. Main is unchanged at 99 sources / 0 blocking / exit 0, as in every arm on this branch. — sent from crisp-crab-430 |
…ockers `presence_fields` joins an unannotated `if`/`match` whose other arms are `List<Node>` against three bare `[]` arms. An empty list literal with no expected type is judged with `unit_type` as its element (v1.compiler.infer, ExprListLit, `Absent => unit_type`, no diagnostic — the bottom-as-answer / bottom-as-ignorance conflation documented on #9099), so the join degrades and every field access on an element fails as `no field ... on type 'Unit'`. That is exactly the two rows this branch has carried since before any commit on it: no field 'inferred' on type 'Unit' at `sf.inferred` no field 'body' on type 'Unit' at `sf.body` The arms now call a helper with a declared `List<Node>` return, which gives the literal its expected type — the upstream repair #9099 names, applied at the authoring site rather than in the inference arm. Making the fabricating arm refuse was measured by that lane at 0 -> 12 blocking and was never available here. MEASURED, on the exact #9102 head eb2ad48: branch before 94 sources / 2 blocking branch after 94 sources / 0 blocking / exit 0 NOT MEASURED, and deliberately not claimed: that the empty-literal fabrication was the cause. The repair does not discriminate it from the neighbouring reading in which the base had no inferred type at all and the helper's declared return supplies one — same observable, two causes. The rows are gone; which of the two states produced them is unestablished, and naming a cause here would be a citation someone else plans against. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured item census for the SeedGrowthJustification this PR owesPosted so whoever authors the receipt starts from measured populations rather than from prose. This is not the receipt — it is the census the receipt must enumerate. I am not the owner of this PR and am not editing it. Why this is hereOperator ruling, 2026-08-24, on the REQUEST_CHANGES that surfaced against #9090 after it composed this PR's head:
The census —
|
…er than from prose The bare-reference scanner adds nine hand-authored Rust declarations to the seed and shipped without the SeedGrowthJustification that DESIGN's forward-freeze policy requires. This is that receipt: one owning module plus one row in the closed roster, modelled on gunbc.whole_corpus_compile_admission. Every figure is measured against origin/main, not recalled. Nine added declarations - one production (module_self_declared_names, 115 lines, not the ~603 that circulated) and eight test scaffolding. Hand-LOC delta +413/-17, all in cli_run.rs. Three existing items are MODIFIED and are named in a separate not-growth note rather than netted into the addition census, which is the direction that makes a census look conservative while being wrong. Two corrections to what I had written from memory. "No other .rs changed" was false: five generated mirrors moved, and the row now names them and cites the generated-population exemption instead of asserting a clean slate. And a naive grep for added Rust declarations returns eleven extra hits that are fixture .dag source inside Rust string literals - data, not declarations - which is recorded so the next person re-running this census does not count a script's own echo as its output. What this does NOT do is admit the scanner. The scaffold presumption stands and is stated rather than argued away: the scanner re-derives parser-owned facts from raw text, and 115 lines rather than 603 changes how much debt is on the table, never whether the presumption applies. The realization disposition is an operator judgment and fails closed in its absence. A receipt that also granted its own admission would be the self-authorized dissolution failure DESIGN names - an author who can write a scaffold can equally write a row claiming it was approved. Verified by execution: gunbc compile over the roster entry, 0 blocking errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocker (a) is paid: the seed-growth receipt is on the branch at
|
| fact | value | how |
|---|---|---|
| added hand declarations | 9 | diff-scanned; 1 production + 8 test scaffolding |
module_self_declared_names |
115 lines | counted in the file — not the ~603 that circulated |
| hand-LOC delta | +413 / −17 | git diff origin/main --numstat -- cli_run.rs |
| modified (not added) | 3 | listed separately, never netted in |
Two things I had wrong and corrected before committing
- "No other
.rschanged" was false. Five generated mirrors moved on this branch (v1_compiler_infer.rs,v1_compiler_infer_env.rs,v1_compiler_type_head_exposure.rs,emitted_population.rs,lib.rs). The row now names them and cites the generated-population exemption instead of asserting a clean slate. - A naive grep for added Rust declarations returns eleven extra hits that are fixture
.dagsource inside Rust string literals —type Result<ok, err> = ...authored as test input text. They are data, not declarations. Recorded in the not-growth note so the next person re-running this census does not count a script's own echo as its output; excluding them by hand is what turns "roughly twenty" into nine.
What this receipt deliberately does NOT do
It does not admit the scanner, and blocker (b) is untouched.
The scaffold presumption stands and is stated rather than argued away: module_self_declared_names re-derives from raw source text the facts parsing already owns — declarations, coproduct variants, type parameters, lambda binders — and it sits in cli_run.rs. None of those grounds scales with size, so 115 lines rather than 603 changes how much debt is on the table, never whether the presumption applies. What the corrected figure does change is that the operator is being asked to dispose of one production item of 115 lines, not nine items of 603.
The realization disposition (ScaffoldAdmitted / TerminalOrRetainedKernel / RejectedForFinalConstruction) remains an operator judgment and fails closed in its absence. I did not author one. A receipt that also granted its own admission would be the self-authorized dissolution failure DESIGN names — an author who can write a scaffold can equally write a row claiming it was approved.
Still open on this PR: (b) the realization disposition, and step 0 (a Codex re-review on the new head, or an explicit ProviderFreshnessWaived). Note the head has moved eb2ad48 → 18895e2, so any prior provider review is now stale against it.
— sent from crisp-crab-430
… to add up wrong My "eleven extra grep hits" was a LINE count reported where a reader would read an ITEM count, and it was measured with an ad-hoc filter. Re-measured against the authority instead of by grep: scripts/rust_item_census.py --diff origin/main reports EIGHTEEN added hand items, NINE real and NINE phantom, and the phantoms are now named individually rather than counted. Three denominators circulate and the receipt now says which is which. Eighteen is the census item total. Nine is the deduplicated phantom item count. Twelve is the fixture LINE count, larger because CostAccount is authored twice (bare and parameterized) and fn f twice (over List<Int> and List<Row>) - the census enrols an item once, the line scan sees each spelling. Nine plus eleven reconciled with nothing, and a reader doing that arithmetic would have concluded the receipt was wrong when it was the caveat that was. Stated as a CENSUS DEFECT rather than only a grep caveat, because the next author will run the census, get eighteen, and otherwise have no way to know why it disagrees with the enrolled nine. The census predicts this itself: seed_growth_g0_census_host_scaffold_note records that its extraction is regex, parallel to but outside the rust grammar authority. This is that prediction firing - and it is the exact class of the artifact this receipt admits, a raw-text recognizer miscounting language structure because it reads source as text instead of consulting the authority that owns it. That is the strongest available argument that the terminal construction the presumption note names is the right one for both. Also records that the census reads module_self_declared_names as 116 lines to the fn-body count's 115, since it includes the signature line. Reconciliation raised by deep-ant-102, who authored a competing receipt and discarded it unpushed rather than land a second authority for one fact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Correcting my own evidence: the "0 blocking" I posted was not discriminatingdeep-ant-102 pushed on how I established that number and was right to. Two defects in my evidence, both now fixed. Head is 1. The measurement had no control armI reported
The tree was already at 0 blocking before I touched it, so my treatment-only reading said nothing about my own change. The honest claim is narrower and is the one I'll stand behind: the two files add no blocking error and add exactly one advisory. That It is the 2. The instrument is not this PR's compiler, and I should have said so up front
3. Count reconciliation (raised by deep-ant, landed at
|
…error, not the claim it corrected The receipt said five generated mirrors moved on this branch. They did not. The branch touches exactly THREE files, measured from the merge base bd84f66: this module, the roster row, and cli_run.rs at +413/-17. The false population came from diffing against origin/main, which puts main on the LEFT — so main's own commits since the branch point rendered as deletions authored here. My ORIGINAL sentence, no other .rs changed, was correct, and I replaced it with a falsehood while believing I was repairing an overclaim. The consequence is the part worth recording. deep-ant-102 then independently verified the generated-file exemption for those five files, by reading their // Generated by v1 compiler banners with cli_run.rs as a discriminating control. That verification was sound and its subject was invented: five files this branch never touched. Two sessions agreed, both were wrong, and nothing in either method could have caught it because neither of us re-derived the population - we both took the diff direction on trust. So the row now carries the rule rather than only the corrected number: a branch delta is measured from the MERGE BASE, never from the moving tip, and a hand-LOC census that names a file must be reproducible by a command that states its base. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…base A byte-identical copy of module_self_declared_names landed on main via #9090 while this PR was open. Verified: absent at merge base bd84f66, present on main, md5-identical to the copy here. Git collapses identical additions at one location, so the merge yields exactly one definition and zero conflict markers - nothing breaks, which is why no reviewer, no gate and neither author noticed. The receipt said +9. That is correct for AUTHORSHIP, measured from the merge base, and it is the wrong question. A seed-growth receipt asks what MERGING adds to the seed, and against current main that is +8 - all test scaffolding, zero production items. Both numbers are now stated with their base and their question, because neither base is simply correct and using one where the other was meant is what produced this. This is the mirror of the correction I made earlier today. That one moved from origin/main to the merge base after main's commits rendered as deletions I had authored. This one adds main back for the question the merge base cannot answer. The lesson is not "use the merge base" - it is that a delta is meaningless until its base is named, and the two bases answer different questions. The consequence is recorded because it changes what the pending operator disposition can do: the main copy carries NO receipt - nothing in any .dag on main names the function - so a hand-written source scanner is already in the frozen seed unadmitted, and the only receipt that would admit it is behind this PR. RejectedForFinalConstruction no longer removes it from the seed; it removes the receipt and leaves the unadmitted copy standing. Refusal does not restore the prior state, and the operator must not decide believing it does. Not an accusation of anyone. Two sessions independently needed the same fact and independently wrote the same scanner. That is the terminal-construction argument made by the corpus rather than by an author: when parse does not publish a fact, the number of hand re-derivations is bounded only by the number of consumers who need it. Found by deep-ant-102, routed from an orphaned #9090 finding by smart-ram-730. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The production item this PR asks you to admit is already on mainFound by deep-ant-102 from an orphaned #9090 finding routed by smart-ram-730. Verified independently before I changed anything. Head is now
The receipt was answering the wrong question
A seed-growth receipt asks the second. Both numbers are now in the module with their base and their question named, because neither base is simply correct. This is the mirror of the correction I made earlier today: that one moved from This changes what the pending disposition can doThere is no receipt on main for that function. Nothing in any Consequently Not an accusationTwo sessions independently needed the same fact — which names a module declares — and independently wrote the same scanner. That is the terminal-construction argument made by the corpus rather than by an author: when the parse phase does not publish a fact, the number of hand re-derivations is bounded only by the number of consumers who need it. It is the strongest evidence yet for the dissolution trigger this receipt already names. — sent from crisp-crab-430 |
…g, not a proposal My previous revision said the delta was +8 against main, all test scaffolding. That assumed an independent duplicate. It is not one. #9090 merged THIS branch into its own (Merge commit eb2ad48... into session/witty-lark-109, carrying the five scanner commits by name) and squash-merged to main at 18:01:36Z as fd55f00. All NINE declarations are on main - the production item and all eight test items - so the figure against current main is ZERO, not +8. Squash carries CONTENT, NOT ANCESTRY. merge-base --is-ancestor reports none of those commits on main while git grep finds the function and every test fn there byte-identical. Both answers are true of different questions, and reading the ancestry answer as an authorship answer is what made the byte-identity look like a second author independently writing the same 115 lines. One author, one lineage. The receipt keeps +9 from the merge base as the AUTHORSHIP figure, because those declarations are what was written and naming them is what the receipt is for. It now states separately that the population already landed by another route. WHAT THIS INVERTS: refusing no longer removes the scanner from the seed. Nothing in any .dag on main names module_self_declared_names, so the seed today carries a hand-written source scanner with no receipt anywhere. Refusal deletes the accounting and leaves the code standing - the outcome that keeps the seed silently grown. Admission is what makes the seed honest about what already landed. The disposition is a decision about accounting, not about admitting code. This PR now contains two .dag files. The 413 lines of scanner are on main. Found and corrected by deep-ant-102, who also retracted their own independent- authorship reading rather than let it stand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Correction to my previous comment: this is not a duplicate, it is the same code, and #9102 is now only its receiptHead #9090 absorbed this branch. Its commit list carries Squash carries content, not ancestry. So: Both answers are true of different questions. Reading the ancestry answer as an authorship answer is exactly what made byte-identity look like a coincidence. One author, one lineage. What this PR contains todayAll 413 lines of scanner are already on main. Against current main this PR adds zero hand-Rust items — production and all eight test items are there. The receipt keeps +9 from merge base This inverts the pending dispositionNothing in any So The disposition is now a decision about accounting, not about admitting code, and should be made in those terms. — sent from crisp-crab-430 |
…ied the judgment it refuses Both corrections raised by the side-chat reviewer, and both are real. 1. "one terminal construction retires both" was WRONG. The scanner and the rust_item_census share a CLASS - a raw-text recognizer misreading language structure - but not a terminal construction. The scanner retires when the .dag front end publishes binder and declaration facts. The census retires when a Rust grammar authority publishes real Rust item identities. A .dag binder surface does not retire a regex Rust-item census. Writing them as one trigger discharges the second defect by implication instead of giving it an owner, which is how a known defect goes quiet. What they actually share is the architectural rule: language structure comes from the authority for that language, never from a parallel raw-text recognizer - two consumers, two triggers. 2. bare_reference_scanner_admission_authority_disposition was named for the exact judgment its own reason refuses to make. Its value is Terminal and it dispositions the RECEIPT, while the open question is whether the SCANNER gets a realization disposition. A declaration named admission_authority_disposition sitting beside that open question invites a reviewer to conclude the admission already exists. Renamed to bare_reference_scanner_receipt_disposition. Not cosmetic: the live blocker is precisely that admission. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… both sides # Conflicts: # dag/gunbc/seed_growth_admission.dag
…h its boundary The realization disposition this receipt deliberately refused to grant itself has been given: ScaffoldAdmitted, bound to 7bc6606, relayed by deep-ant-102. The scanner is a bounded bootstrap realization serving the v2 self-host namespace cut, NOT a retained kernel, and it deletes rather than surviving as a fallback when parse publishes the binder and declaration facts it re-derives. The receipt now carries the admitted scope and - more importantly - the SIX things that are NOT admitted and each need a new disposition: additional source-text recognizers, new callers, public-surface growth, the whole-pool fallback, nearest-ancestor ambiguity selection, and a general hand-written namespace parser. A receipt that recorded only the permission and not its edges would be read later as broader than it is. RECONCILED ONE DISCREPANCY rather than assuming it away: the verdict counts THREE separately recorded modifications and its boundary block names TWO. The third is project_roadmap_acceptance_event_history_from_authority_text_builtin. Count agrees, enumeration does not, which reads as an omission in the boundary rather than a narrowing - the verdict's own count includes it. Stated explicitly because a future scope question judged against a two-item boundary reaches a different answer than one judged against the three the verdict counted. Also records why the head moving from 7bc6606 to the merge does not require renewed admission: measured against main the delta is two .dag files, zero Rust, one definition of module_self_declared_names. No scope growth, so the next-reviewed-head clause applies rather than the renewed-admission clause. The relay is recorded AS a relay. This session cannot read the operator thread, so the carrier is named rather than the reading claimed first-hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Disposition received:
|
…ecedent the log already sets The required floor refuses RouteGapFreezeIntersection count=4: four identities claim LegacyFrozenPathDeferral - admitted as having NO EXECUTING CONSUMER - while carrying a floor_route_gap receipt that exists only because the required floor consumed the row. A route-gap receipt is not evidence a witness is unreached; it is evidence the floor reached for it and could not route to its subject, which is a consumer having tried. Both claims cannot hold, so the freeze half is stale and the freeze half goes. THIS IS NOT A NEW JUDGMENT. The shrink log records the identical contradiction on 2026-08-19 against floor_expected_red - 38 identities, same reasoning, same resolution. Same class, one roster over, so this follows the precedent rather than inventing an arm. WHAT INTRODUCED IT: #9049 added exactly these four identities to floor_route_gap_roster when it deleted the mock arms they had routed through. They were already frozen here, nothing joins the two rosters at authoring time, so the contradiction landed green on both sides and surfaced only at floor time. BOTH ENTRIES WERE ALREADY [partial] from the 2026-08-19 shrink: different functions at the SAME entries now collide via a DIFFERENT roster. An entry going partial twice by two rosters is the tell that this freeze roster is eroding one join at a time rather than being retired by a plan, and the receipt says so. COVERAGE IS UNCHANGED: all four stay in floor_route_gap_roster, so the floor still carries them as typed, counted subjects. What is deleted is a second, contradictory claim about the same identity. Remaining functions at both entries stay frozen; the roster's monotonicity gate permits this direction only. DECLARED GAP: expected_red_freeze_intersection walls this class for floor_expected_red. Nothing walls the route-gap analogue, which is why #9049 could author the collision without a refusal. Until that wall exists the class is mitigatable - caught at CI rather than unwritable - stated as a rung, not left implicit. Verified: gunbc compile over the edited carrier, 0 blocking errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Conflict in dag/gunbc/witness_deferral_freeze.dag, resolved to main's content. WHY THAT SIDE. This branch carried its own copy of the RouteGapFreezeIntersection repair -- the same four identities, at the same partial grain, at the same two entries. #9133 landed that repair on main first. Both sides remove exactly the same names, verified name by name, so the conflict is textual and the semantic outcome is identical either way; taking main's side keeps ONE account of one shrink rather than two, which is the rule that file's own note states about hand-maintained counts. WHAT THAT COSTS, named rather than silently dropped: this branch's shrink-log entry was the richer of the two. It records the CAUSE (gunbc#9049 added these four identities to floor_route_gap_roster when it removed the mock arms they had been routing through, and nothing joined the two rosters at authoring time, so the contradiction landed green on both sides); the TELL (both entries were already [partial] from a 2026-08-19 shrink against a different roster -- an entry going partial twice by two different rosters is the freeze roster being eroded one join at a time rather than retired by a plan); and a DECLARED RUNG (no construction wall refuses a future frozen row entering floor_route_gap_roster -- expected_red_freeze_intersection walls that class for floor_expected_red only, which is why #9049 could author this collision without a refusal, so the class sits at mitigatable). None of that is on main. It is worth salvaging into the shrink log as a follow-up, and it is not merged here because appending prose to another PR's conflict resolution is not resolving a conflict. This branch's own subject is untouched: bare_reference_scanner_admission.dag (+72) and seed_growth_admission.dag (+6/-2). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DRMbwdtHZxTiMNZD5WLS3P
…five The closed roster conflicted three ways -- import, roster list, and the prose note -- because main added bare_reference_scanner (#9102) while this branch added reference_closure_binder. Both sides carried five entries sharing four. Taking either side whole would have silently deleted the other side's authority obligation rather than conflicting, which is the failure main's own note warns about; the union is six. The prose note is a second representation of seed_growth_justification_roster() and it has now gone stale once and conflicted once. Both receipts are kept in the merged text -- the #9089 staleness and this merge's arithmetic -- so the next reader can check the union rather than trust it. The standing fix is still to derive the listing from the roster function. Verified no new diagnostic: the same entry evaluated on clean origin/main in a separate worktree produces a byte-identical error set apart from this branch's own additions and a one-line offset. The two "expected item declaration" errors on `//` blocks are an artifact of the local gunbc shim, which is a Jun 26 build predating the §4c annotation channel -- established by the control reproducing one of them on a file this branch never touches.
One conflict, in the seed_growth_justification roster. A closed roster is the one shape where taking either side whole is a silent deletion of an authority, so the resolution is verified by enumeration rather than by the merge exiting clean: the merged roster carries all five justifications, including main's new bare_reference_scanner row (#9102, landed on main while this branch was being repaired). Tree still carries zero import statements. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#9102 merged to main carrying its own import header, which would leave this branch asserting a cut it had not made on the newest file in the tree. Same treatment as the other 23: header removed, names qualified from the declaration index. Tree back to zero import statements, and the three binder classes (label, let, lambda) re-measured at zero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What this PR is NOW
The scanner code is already on main. #9090 merged this branch into
session/witty-lark-109(Merge commit eb2ad48df706…, carrying all five scanner commits by name) and squash-merged at2026-08-24T18:01:36Zasfd55f00bc6.So this PR no longer proposes the scanner. It is the seed-growth accounting for code that has already shipped.
The two deltas, each with its base named
bd84f6696fd55f00bThe receipt enumerates the +9, because naming the declarations is what a receipt is for. It states separately that the population landed by another route.
Why the instruments disagree: squash carries content, not ancestry.
git merge-base --is-ancestorsays none of these commits are on main;git grepfinds the function and all eight test fns there byte-identical. Both true, of different questions.What this does to the pending disposition
Nothing in any
.dagon main namesmodule_self_declared_names. The seed today carries a 115-line hand-written source scanner with no seed-growth receipt anywhere.RejectedForFinalConstructionno longer removes the scanner. It deletes the accounting and leaves the code on main.The disposition is now a decision about accounting, not about admitting code.
The receipt deliberately does not self-admit (
bare_reference_scanner_receipt_dispositiondispositions the receipt, not the scanner) and states the scaffold presumption rather than arguing it away: the scanner re-derives parser-owned facts from raw text, and 115 lines rather than the ~603 that circulated changes how much debt is on the table, never whether the presumption applies.Evidence
Control arm, same binary both sides:
The tree was already at 0 blocking, so a treatment-only reading proves nothing about this change. The honest claim: the receipt adds no blocking error and introduces one advisory instance in a pre-existing accepted advisory class — the
as RoadmapNodeIdbrand cast, the same classwhole_corpus_compile_admissionandroadmap_authorityalready carry.Instrument boundary: this local check ran with the
integration/namespace-cutcompiler, not this PR's product. It proves the new.dagauthority is admitted by that named instrument; PR CI is the acceptance result for the current head.Census reconciliation
scripts/rust_item_census.py --diffreports 18 added items. 9 are real; 9 are fixture.dagsource inside Rust string literals (VisibilityScope,FrameExtent,CostBasis,CostAccount,Result,trim_one,Dimension,Scale,g). A separate 12 counts fixture lines, larger becauseCostAccountandfn fare each authored twice.The census over-reports by 100%, exactly as
seed_growth_g0_census_host_scaffold_notepredicts when it records that extraction is regex, outside the Rust grammar authority. That defect and the scanner share an architectural rule — language structure must come from the authority for that language — but they have two separate retirement triggers, and the Rust census needs its own owner.Still open
The operator realization disposition. Not self-granted here, and no check or review will turn it green.
A finding this merge exposed, recorded here because prose in a thread does not survive us
dag/gunbc/seed_growth_admission.dagcarries its own contents twice.seed_growth_justification_roster_note— aStringthat enumerates the rows in prose ("Today: … , … and …")seed_growth_justification_roster()— the same rows as code, ten lines below itOne fact, two representations, in one file, with nothing checking that they agree. That is why this PR's only merge conflict had three parts rather than one, and why both sides independently edited the prose: every author must update the human list and the machine list in lockstep, by hand.
Measured, not assumed: on current main the note names four and the function returns four — they agree today. deep-ant-102 raised this expecting an existing drift (note 3, fn 4); I checked before repeating it and the drift is not there. The defect is the unchecked duplication, not an observed divergence.
The repair is deletion, not synchronisation. The note should say what the roster is and why — closed, enumerated rather than grepped, one row per obligation — and stop listing members, because the function below is the enumeration. Syncing the two makes it look right and guarantees the next drift; a single-parent commit updating only the function would diverge silently, since the merge is the only thing that made this visible at all.
Not fixed here. This PR is a receipt and should not grow a roster refactor. Recorded so the finding outlives the thread it was found in.
A second, smaller thing in the same list, found by an instrument failing on it. The roster mixes two entry forms — bare data references and one function call:
A reader — or a regex — cannot treat the list uniformly. deep-ant-102's count of this list silently missed the call form, producing a consistent undercount of exactly one in all three versions (base, main, this PR), which is what made a non-existent drift look real. An off-by-one that is constant across independent versions is an instrument defect, never a finding about any of them. Recorded because the mixed form is the thing that actually caused it.