Skip to content

XL-0-CITE follow-up: key prose-citation retirement by qualified identity (review 58234 repairs to merged #9940) - #9958

Merged
gunbai-bot[bot] merged 15 commits into
mainfrom
session/gentle-bear-446
Sep 1, 2026
Merged

gunbai-bot[bot] merged 15 commits into
mainfrom
session/gentle-bear-446

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Why this exists

#9940 merged at 15:35:36Z as 4c6e5dfcd41 while the repairs for codex review 58228 / review 58234 were still local. Main therefore carries the pre-fix content and this branch carries it plus the repairs. Squash carries content, not ancestry, so both sides presented as add/add on merge; resolved to ours after checking that main's side is byte-identical to c88e4541, the commit that was squashed — so ours is a strict superset and the resolution discards nothing.

Both findings in review 58234 stood as REQUEST_CHANGES against the merged version.

Finding 1 — retirement was keyed on a bare name

The review called this a false-negative risk. It is worse than that, and the census's own argument is why.

A row retired from its module while the same spelling survives in another module read as "still live" and left the census entirely. But a prose citation of that name still resolves — to a declaration the prose did not mean. That is exactly the state the module's no-sweep section calls worse than dangling, because a resolving citation is never re-examined. A census whose thesis is "a citation that resolves and lies is worse than one that dangles loudly" cannot be built so that precisely those sites are the ones it cannot see.

Retirement is now module_path + decl_name, with baseline module lines read at the same revision through the same GrepMatchesAtRevision operation. Both sides of the join are the authored unstripped module name — checked against v2.std.decl_index, which documents module_path as authored-and-unstripped, rather than assumed; a silent v2. mismatch would have made every src/v2 row read as retired.

A baseline path with no module line establishes no identity and is not judged either way. Defaulting it to retired would enrol every unparsed file's rows into the population.

The site now carries ProseCitationResolution = CitedNameAbsentEverywhere | CitedNameSurvivesElsewhere, both counted — the two states the old key collapsed by dropping the second.

Finding 2 — hand-rolled coproduct predicate

prose_citation_site_is_annotation was an is_* -> Bool matching ProseCitationCarrier. Deleted; matched inline at its two consumers. Same class as review 58038 on #9874 (rung_drop_is_standing) — a lesson I had already been given and then regressed on.

The same error one level up, which finding 2 exposed

cross_file: Bool had no spelling for "the name was retired from several paths, so which file this citation is local to is not decidable from the site". A Bool must answer true or false: ignorance rendered as an answer.

  • ProseCitationLocality = InRetiringFile | InAnotherFile | RetiringFileAmbiguous
  • ProseRowOrigin = RetiredFromOnePath { path } | RetiredFromSeveralPaths, replacing a name-keyed map whose map_insert was silent last-write-wins over distinct origins.

Measured before building

Neither fix is priced on a hypothetical:

at the pre-sweep baseline
retired rows / distinct names 4002 / 4002
sites in the survives-elsewhere class 0
colliding retirement origins 0
prose-row names declared in >1 file 38, across 119 rows

The spelling already collides in this corpus; only the retired subset happens not to. A realized n of zero is a fact about this tree, not about the construction.

Evidence

26 controls green by execution under --claim-run, five new, each a pair the old bare-name or Bool-valued version could not satisfy:

  • the same bare name, retired in one module and live in its own → one retired, one not
  • a cited name declared nowhere vs. surviving elsewhere → different arms
  • one origin vs. several → resolves vs. refuses to pick
  • the same path seen twice → still one origin (without this, any duplicate degrades every site to ambiguous)
  • a baseline row whose module is unknown → not judged

Live run, every figure predicted before it was observed:

retired-rows=4002 sites=397 names=298 annotation=314 prose-row=83
another-file=249 ambiguous-origin=0 absent-everywhere=397 survives-elsewhere=0

survives-elsewhere=0 and ambiguous-origin=0 match the populations measured above, so this is construction for a real-but-unoccupied class, not repair of a live miscount. Sites 394 → 397 is this branch's own newly-tracked prose citing retired names — the census seeing itself.

Carried forward from #9940

The operator ScaffoldAdmitted record (scope, boundary, renewal triggers, dissolution) and the required_gate_prefixes enrolment are already on main via the squash and are unchanged here. The enrolment appears exactly once after the merge.

Predecessor: #9940 (merged).

🤖 Generated with Claude Code

Brian Searls and others added 14 commits September 1, 2026 01:39
…m the rows' own standing

DESIGN.md's "The ones standing today" list was projected as an UNFILTERED map over
gunbc.rung_drop rung_drop_roster. Retirement was written only as prose inside each row's
`authored` paragraph, which a projection cannot read. So the list asserted
"Cited-symbol resolution as a required check" as currently standing for six days after
its restoration trigger fired on 2026-08-25 -- in a document loaded in full on every turn
of every session. Two sessions independently built work on that false premise.

The repair is derivation, not a hand-edit: DESIGN.md declares itself a projection that is
never hand-edited, so editing the list would have left the second authority in place to
drift again on the next retirement.

- gunbc.rung_drop gains RungDropStanding (Standing | Retired { trigger_fired }) and a
  `standing` field, so retirement is a fact the row owns rather than prose beside it.
- rung_drop_is_standing / standing_rung_drops derive the list; gunbc.design_document
  projects standing_rung_drops() instead of the whole roster.
- gunbc.design_ledgers still folds the FULL roster: a ledger records retired drops too.
  Two consumers, each asking its own question of one authority.

CENSUS OF THE OTHER SEVEN ROWS, since a list that drifted once may have drifted more: it
had not. Exactly one row is retired. emit_stage_blocking says "NARROWED RATHER THAN
RETIRED" and fabric_evidence_gating says "THIS IS NOT RETIRED BY THE MEMO PR MERGING";
the rest carry no retirement language. DESIGN.md goes 8 rows to 7.

EVIDENCE, executed, not described. test.claim.rung_drop_standing_partition_witness_test
returns true on this tree and false when the retired row is re-marked Standing. Its second
conjunct asserts the roster still HOLDS a retired row: without it, a filter over an
all-Standing roster excludes nothing and the check would be permanently green by
construction -- a decoration, not a wall.

RESIDUE, named rather than left to be discovered: `standing` is authored and nothing
derives it from the paragraph beside it. This change removes the second authority for the
standing LIST; it does not close the gap between a row's prose and its field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Fk4zsZLzxJ3zJ3o969vBQ
… check named to catch them is blind to the whole population

Nine citation sites, six symbols, repointed at the declaration each §4c
annotation is attached to, per the disposition #9866 settled against
gunbc.deleted_cadence_reference_census. Comment and diagnostic text only.

The measurement that motivates them is in the PR body: 0 typed DeclarationRef
citations dangle from the sweep, 468 prose sites over 336 retired symbols do,
and seven of the ten post-sweep sites were authored after it merged — an open
future set, not a bounded population.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPgQxFX4Cc37W2kEDC8kXS
… filter

review 58038 (codex/gpt-5.6-sol) on #9874: rung_drop_is_standing was a standalone
coproduct-to-Bool predicate beside the variant the row already carries. Deleted;
the standing arm is now matched inline at the single site that consumes it,
which is the shape std.trait_derive_shape already uses.

The witness stopped re-deriving the predicate too, which would only have tested
its own copy of the arm. It now joins on IDENTITY: the retired row is absent from
the derived list and present exactly once in the roster it was retired in place
within. Executed both arms with gunbc run against the whole dag+src/v2 closure:
true as authored, false when cited_symbol_census is flipped back to Standing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPgQxFX4Cc37W2kEDC8kXS
# Conflicts:
#	dag/gunbc/rung_drop.dag
#	dag/test/claim/rung_drop_standing_partition_witness_test.dag
…mber

The 468/336/314 figures from #9874 lived only in a merged PR body, produced by a
scratchpad census that did not survive the session. DESIGN section 6 cites a
measurement by naming the producer that re-derives it, so the deliverable here is
the producer and the numbers are its first output.

tools.prose_citation_census re-derives, on demand and at identity grain, the prose
citation sites naming a prose row that a declared BASELINE REF held and HEAD does
not. Executed against the pre-sweep baseline 2afe322 (the three slices #9751 /
#9753 / #9752 are one commit chain, so one baseline covers the sweep):

  retired-rows=4002 sites=394 names=297 annotation=313 prose-row=81 cross-file=246

The baseline is a PARAMETER, so the slice split is runs of this function rather
than three literals written into it.

NO SWEEP, EVER, and it is in the module as the reason rather than the rule.
Annotation attachment is POSITIONAL, so repointing every site at the declaration
its annotation now sits above is not semantics-preserving: the measured specimen
is host_budget_unreadable_cgroup_receipt_note, whose prose was module-level
rationale about an absorbing-fallback deletion and whose following declaration is
now an unrelated seed-mirror scaffold disposition. A mechanical sweep would
manufacture, once per cross-file site, a citation that RESOLVES AND LIES -- worse
than one that dangles loudly, because a resolving citation is never re-examined.

MEMBERSHIP IS gunbc.prose_row_frontier's AND NOWHERE ELSE. The grep pattern is
deliberately WIDER than the rule -- every `data NAME: String` head -- and
prose_row_decl_name decides. Narrowing the argv to the `_note` suffix would put
the membership rule beside the module that owns it, where the two drift and the
argv wins silently. The stated consequence: a retired `data` row whose name lacks
that suffix is outside this subject, about five percent of what the slices
retired. Named rather than closed by widening a standing wall's recogniser from
inside a measurement lane.

THREE DEFECTS THIS FOUND IN ITSELF, each by execution rather than by review.

1. A three-argument lookup(map, key, default) compiled clean while never returning
   the value. The cross-file control passed FOR THE WRONG REASON -- with the path
   empty, every site reads as cross-file -- and only the same-file arm went red. The
   repair is construction, not a corrected default: the membership test and the read
   are one fold over the Optional, so the unreachable arm has no value to fabricate.

2. The diff producer refused, correctly, at 18 MB against an 8 MB shell output
   limit (13 MB restricted to *.dag). Its size is driven by distance to HEAD, not by
   the retirement measured, so it would refuse for every baseline eventually. Reading
   the baseline's own declarations through the new git.Inspect.GrepMatchesAtRevision
   is 1.06 MB and is the better question besides: the baseline declaring a row is a
   direct observation, where a diff infers it from everything that happened since.

3. The file roster was the union of data_decl_type_facts and decl_facts rel_paths.
   That is not a roster: a source with no `data` row is invisible to the first and
   the second skips test .dag files, so nine citation sites in test modules with zero
   data declarations were lost -- an empty-observation narrow arriving as a smaller
   number. Rebuilt from the tree listing, which is complete by construction. A
   declaration index answers which files declare something; this scan needs which
   files exist.

EVIDENCE. Sixteen controls green by execution under --claim-run, including the
twelve sampled corpus specimens carried verbatim so the precision result is
re-executed rather than remembered, both boundary arms, the semantic-position
negative, the non-prose-row negative, an identity join in both directions, and a
counter pair shown non-constant in its retired index. A deliberately false probe
was run to prove the harness reports red at all, then removed rather than left as
a permanent CI failure. Against an independently written oracle sharing no code:
ZERO sites missed, one extra explained by a main merge mid-session.

WHAT THIS DOES NOT CLAIM. The text acquisition is a declared Scaffold bound to
std.source_annotation SourceAnnotationDebt -- it dissolves when annotation text is
published corpus-wide beside the declaration index. This module does NOT admit it:
a new source-text recogniser is an operator judgment that gunbc.bare_reference_
scanner_admission explicitly does not cover, and a row granting its own admission
is the self-authorized dissolution this corpus names. The census gates nothing,
which is why it can stand unadmitted at all. The section 4c/4b ruling on whether
prose-citation resolution is a compiler capability is recorded as deferred, not
dropped; this module is what makes it decidable against a re-derivable population.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXd2xwe4GiuVt3rmVt2g4E
… evidence outlives the PR that added it

The floor executed all 18 controls on 4a14b72 and reported them
standing=planned-and-passed disposition=planned_as_changed_witness. That
disposition is the whole finding: they ran because THIS PR CHANGED THEM, not
because the gate selects them. required_gate_prefixes carries 27 prefixes and
none matches test.claim.prose_citation_census_witness, so on the next PR that
does not touch the file every one of those identities is declined SILENTLY and
the floor stays green.

That is evidence with an expiry date. DESIGN section 4b(4) keeps a class's
discriminating controls ENROLLED as the executing evidence that its rung stays
real, and a control that stops executing while the claim it backs still stands
is the inert-lens failure arriving by attrition rather than by deletion. Reporting
"18 identities passed" without this row would have been true of one run and read
as a standing property.

One prefix row, 27 -> 28, verified to match the module. Nothing else changes:
selection is additive, so no existing identity's disposition moves.

COST IS NOT A CONCERN HERE AND WAS CHECKED RATHER THAN ASSUMED. The controls are
pure string recognition over literal specimens -- no corpus walk, no filesystem
read, no git -- and all 18 already executed inside the floor run on 4a14b72
within its budget. v2.workflow.floor_cost_debt exists because the 500ms per-claim
line cannot carry witnesses that read the live tree; these do not.

NO MIRROR REGENERATION IS OWED: the seed decodes this roster from the .dag at run
time via floor_decode_module_prefix_roster, so there is no hand-authored Rust copy
of the list to drift. The row is not annotated because section 4c models
module-item grain only and this is a list element, and because no sibling row
carries one -- the rationale belongs here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXd2xwe4GiuVt3rmVt2g4E
…, boundary and dissolution verbatim

OPERATOR DISPOSITION: ScaffoldAdmitted, 2026-09-01, bound to gunbc#9940 at head
4a14b72. Relayed by bright-ram-778; this session cannot read the operator
thread, so the relay is named as the carrier rather than presented as a first-hand
reading.

The verdict is RECORDED in prose_citation_text_acquisition_disposition's annotation
and was not decided there. That distinction is the reason the row previously stood
unadmitted: an author who can write a scaffold can equally write a row claiming it
was approved, so approval has to be external to the diff or it is not approval.

WHAT IS WRITTEN DOWN, all of it verbatim from the ruling rather than paraphrased,
because a paraphrased boundary is a boundary that has already moved once:

  SCOPE -- GrepMatchesAtRevision; the raw-source text acquisition; the pure
  recognizer, join, carrier and report; the current executing witness population;
  manual/on-demand invocation with caller-supplied baseline and report.

  BOUNDARY, each item requiring a NEW disposition -- no required or advisory
  workflow invocation; no merge gate; no automatic source rewrite or citation
  repointing; no widening of gunbc.prose_row_frontier membership; no reuse as a
  general source-language recognizer; no baked baseline or persisted count.

  RENEWAL -- a new workflow consumer, automatic judgment, an additional source-text
  recognizer, or a materially broader scan. A review correction preserving the
  scope is not a renewal.

THE DISSOLUTION IS IN THE RULING'S OWN TERMS BECAUSE THE WORDING IS THE GUARANTEE:
when annotation text AND ITS ATTACHMENT IDENTITY are published corpus-wide beside
the declaration index, the raw-line acquisition DELETES. It does not survive as a
fallback. An acquisition deleted in name that still answers whenever the published
surface is unavailable would leave two authorities for one fact and retire nothing,
which is the failure that sentence exists to forbid.

THE ROW STAYS Scaffold. Being admitted is not being terminal -- the ruling grants
that it may stand, not that it should survive -- and relabelling it Terminal on the
strength of an admission would convert a bounded exception into a permanent
construction, the one move the dissolution sentence forbids. The recogniser half
remains Terminal on its own executed controls, which is a different claim about a
different half.

AND IT IS A NEW DISPOSITION, NOT AN EXTENSION OF AN OLDER ONE. gunbc.bare_reference_
scanner_admission lists "additional source-text recognizers" among what it
explicitly does NOT cover, RESERVING them for a fresh judgment. Without that
sentence a later reader finds two source-text recognizers standing under one
admission and concludes the older one was widened. It was not.

Annotation text only; no declaration, type or behaviour changes. Compile over the
census closure: 0 blocking errors, 301 advisories.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXd2xwe4GiuVt3rmVt2g4E
… the two states a bare-name key was collapsing

Two REQUEST_CHANGES findings from codex/gpt-5.6-sol. Both accepted. They turned
out to be one defect seen at two levels, and the second finding's repair is what
made the first one's fix obvious.

IDENTITY. Retirement was keyed on bare decl_name against a bare-name liveness
index. The review called it a false-negative risk; it is worse than that, and the
reason is this module's own argument. A row retired from its module while the same
spelling survives in ANOTHER module read as "still live" and left the census
entirely -- but a prose citation of that name still RESOLVES, to a declaration that
is not the one the prose meant. That is precisely the state the no-sweep paragraph
calls worse than dangling, because a resolving citation is never re-examined. A
census arguing that cannot be built so those are the sites it cannot see.

Retirement is now decided on module_path + decl_name, with baseline module lines
read at the same revision through the same operation. Both sides of the join are
the authored UNSTRIPPED module name -- v2.std.decl_index documents module_path as
authored-and-unstripped, and this was checked rather than assumed, because a silent
v2. prefix mismatch would have made every src/v2 row read as retired. A baseline
path with no module line establishes no identity and is not judged either way;
defaulting it to retired would enrol every unparsed file.

The site now carries ProseCitationResolution = CitedNameAbsentEverywhere |
CitedNameSurvivesElsewhere, both counted, because those are the two states the old
key collapsed by dropping the second.

PREDICATE. prose_citation_site_is_annotation was an is_* -> Bool matching the
ProseCitationCarrier coproduct. Deleted and matched inline at its two consumers.
This is the same class review 58038 raised on gunbc#9874 against
rung_drop_is_standing, which I had already been shown and then reintroduced.

THE SAME ERROR ONE LEVEL UP, which the predicate finding is what exposed:
cross_file was a Bool. A Bool has no spelling for "the name was retired from
several paths, so which file this citation is local TO is not decidable from the
site" -- it must answer true or false, which is ignorance rendered as an answer.
It is now ProseCitationLocality = InRetiringFile | InAnotherFile |
RetiringFileAmbiguous, and the retired index carries ProseRowOrigin =
RetiredFromOnePath | RetiredFromSeveralPaths instead of a name-keyed map whose
map_insert was silent last-write-wins over distinct origins.

MEASURED BEFORE BUILDING, so neither fix is priced on a hypothetical: at the
pre-sweep baseline 4002 retired rows carry 4002 distinct names, 0 sites fall in the
survives-elsewhere class and 0 origins collide TODAY -- while 38 prose-row names in
that same baseline are declared across more than one file over 119 rows. The
spelling already collides in this corpus; only the retired subset happens not to.
A realized n of zero is a fact about this tree, not about the construction.

EVIDENCE. 26 controls green by execution under --claim-run, five of them new and
each a pair that a bare-name or Bool-valued version could not satisfy: the same
bare name retired in one module and live in its own; a cited name declared nowhere
versus surviving elsewhere; one origin versus several; the same path twice staying
one origin; a baseline row whose module is unknown going unjudged.

Live run, unchanged in every figure the prediction named:
  retired-rows=4002 sites=397 names=298 annotation=314 prose-row=83
  another-file=249 ambiguous-origin=0 absent-everywhere=397 survives-elsewhere=0
Sites 394 -> 397 is this branch's own newly-tracked prose citing retired names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXd2xwe4GiuVt3rmVt2g4E
…bear-446

#9940 merged at 15:35:36Z as 4c6e5df while the review-58234 repairs were still
local, so main now holds the pre-fix content and this branch holds it plus the
repairs. Squash carries CONTENT, NOT ANCESTRY, so the branch commits are not
ancestors of main and both sides present as add/add.

RESOLVED TO OURS ON BOTH CONFLICTS, AFTER CHECKING RATHER THAN ASSUMING WHAT THE
OTHER SIDE WAS: main's side of prose_citation_census.dag and its witness is
BYTE-IDENTICAL to c88e454, the commit that was squashed. Ours is exactly that plus
the qualified-identity join, the ProseRowOrigin / ProseCitationLocality /
ProseCitationResolution carriers, and the deleted is_* -> Bool predicate. So ours
is a superset and taking it discards nothing -- which is a measurement of the two
sides, not an assumption about who was later.

required_floor.dag auto-merged and carries the enrolment prefix exactly once; main
already had it from the squash.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXd2xwe4GiuVt3rmVt2g4E
…rains

The ScaffoldAdmitted disposition of 2026-09-01 admits PERSISTENT REQUIRED-FLOOR
ENROLMENT of test.claim.prose_citation_census_witness OVER AUTHORED IN-MEMORY
FIXTURES, and explicitly does NOT admit live-instrument invocation or git,
filesystem or corpus acquisition during those controls. That boundary had no trace
in the file it constrains.

IT HOLDS TODAY BY CONSTRUCTION AND NOT BY DISCIPLINE, which is the property worth
preserving and the reason this is worth writing down at all: the module's import
list names only pure functions and types, so these controls CANNOT acquire
anything -- there is no acquirer in scope to call. Verified rather than asserted:
no filesystem_read, no git.Inspect operation, no data_decl_type_facts, and neither
live entry point appears anywhere in the module.

SO THE RULE IS ABOUT THE IMPORT LINE, NOT THE TEST BODY, and that is the sentence a
later author needs. Adding any acquirer leaves the admitted scope AND puts a
live-tree read inside a required-floor witness, which is the cost shape
v2.workflow.floor_cost_debt exists for. Two consequences from one edit that would
otherwise look like adding a test.

This is the same failure as the enrolment gap this branch already fixed: a real
constraint with nothing in the artifact carrying it, so it survives only as long as
whoever knows it is still reading. Annotation text only; 0 blocking, 302 advisories
over the witness closure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXd2xwe4GiuVt3rmVt2g4E
…ired

A one-line nit the reviewer marked as not worth a round trip. It is worth one,
because of WHAT it is rather than its size: "SAME-FILE IS THE OTHER ARM OF
cross_file" cites a field this very diff deleted, inside the witness of the
instrument that measures citations to deleted symbols. Shipping it means the census
can report its own witness, and a reader who noticed would be right to distrust the
result.

THE POPULATION IS EIGHT MENTIONS AND THREE CLASSES, and treating them uniformly
would have deleted rationale that is doing real work:

  STALE CITATION, repaired -- the line above, which names cross_file as though it
  still exists. Two control NAMES with it too, renamed for what they actually
  assert: an_annotation_citation_in_another_file_is_one_site_at_its_line and
  a_citation_in_the_retiring_file_is_local_to_it.

  RATIONALE ABOUT A DELIBERATE ABSENCE, kept -- "A Bool cross_file would have had
  to answer this case true or false." That names something removed on purpose and
  says why the coproduct replaced it. Prose citing a live symbol is misplaced data;
  prose naming a deliberate absence is the reason the design has its shape, and
  sweeping it would leave the third locality arm looking arbitrary.

  ORDINARY ENGLISH, kept -- "cross-file site" describes a relationship between two
  files and refers to no field at all.

A rename changes an enrolled floor identity, so the two renamed controls were
executed under their new names rather than assumed inert: both PASS. Compile over
the witness closure 0 blocking, 302 advisories.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXd2xwe4GiuVt3rmVt2g4E
…t as zero retirements

Review 58281 on #9958 is correct and the finding is my own named failure mode.
`prose_citation_census_live` checked `exit_code` on the first git query and
argued about it at length in an annotation, then consumed the SECOND query --
the `^module` read added during the qualified-identity rework -- inline as
`.matches` with no outcome check. A failed module read yields an empty index,
every baseline row then misses in `lookup`, the `Absent => false` filter arm
drops it silently, and the function returns `ProseCitationCensusObserved` with
zero retirements. An acquisition failure rendered as a reassuring census: the
empty-observation narrow this module's own header argues against.

Two arms, both fail-closed:

- the module read's `exit_code` is checked and only 0 is accepted. Unlike the
  declaration read, where exit 1 is an honestly empty match set, a baseline
  with no module lines at all is not a corpus this census can speak about.
- `prose_citation_retired_rows` becomes `prose_citation_retirement_scan`,
  returning `ProseCitationRetirementScan { retired, unidentified }`. Rows with
  no module identity are still not JUDGED -- but they are no longer DROPPED,
  and the caller refuses with the count when any exist. "Not judged" and "not
  counted" were the two states the single-list return conflated.

NAME THE POPULATION FIRST: measured at baseline 2afe322, 4807 prose rows
against 4412 module lines and ZERO rows without a module identity. So the new
refusal cannot fire on a healthy read; it fires exactly when the acquisition is
broken.

Evidence, by execution:
- 28/28 controls PASS on the real recogniser (up from 25; three new).
- DISCRIMINATING RED: restoring the dropping arm turns
  `a_baseline_row_whose_module_is_unknown_is_carried_not_dropped` and
  `a_partial_module_index_splits_the_rows_across_both_lists` RED while the
  counter pair `a_populated_module_index_leaves_nothing_unidentified` and the
  retirement controls stay PASS -- the controls catch this defect, not any
  change.
- Live re-run at 2afe322 is byte-identical to the pre-fix census:
  retired-rows=4002 sites=397 names=298 annotation=314 prose-row=83
  another-file=249 ambiguous-origin=0 absent-everywhere=397
  survives-elsewhere=0. Neither new arm fires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXd2xwe4GiuVt3rmVt2g4E
@gunbai-bot

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Review 58281 addressed at d1ae0c0 — the finding is correct, and it is my own named failure mode reintroduced while fixing the previous one.

What was wrong. prose_citation_census_live checks exit_code on the first git query and argues about it at length in an annotation, then consumes the second query — the ^module read added during the qualified-identity rework for review 58234 — inline as .matches with no outcome check. A failed module read yields an empty index, every baseline row misses in lookup, the Absent => false arm drops it silently, and the function returns ProseCitationCensusObserved with zero retirements. An acquisition failure rendered as a reassuring census.

Two arms, both fail-closed.

  • The module read's exit_code is checked and only 0 is accepted. Unlike the declaration read, where exit 1 is an honestly empty match set, a baseline with no module lines at all is not a corpus this census can say anything about.
  • prose_citation_retired_rows becomes prose_citation_retirement_scan, returning ProseCitationRetirementScan { retired, unidentified }. Rows with no module identity are still not judged — but they are no longer dropped, and the caller refuses with the count when any exist. "Not judged" and "not counted" were the two states the single-list return conflated, which is what made the drop invisible.

Named the population before building the arm. Measured at baseline 2afe322f: 4807 prose rows against 4412 module lines, and zero rows without a module identity — every .dag source declaring a row also declares its module. So the new refusal cannot fire on a healthy read; it fires exactly when the acquisition is broken, which is what makes it a wall rather than a nuisance.

Evidence, by execution.

  • 28/28 controls PASS on the real recogniser (was 25; three new).
  • Discriminating RED: restoring the dropping arm turns a_baseline_row_whose_module_is_unknown_is_carried_not_dropped and a_partial_module_index_splits_the_rows_across_both_lists RED, while the counter pair a_populated_module_index_leaves_nothing_unidentified and both retirement controls stay PASS. The controls catch this defect, not any change.
  • Live re-run at 2afe322f is identical to the pre-fix census — retired-rows=4002 sites=397 names=298 annotation=314 prose-row=83 another-file=249 ambiguous-origin=0 absent-everywhere=397 survives-elsewhere=0 — so neither new arm fires on the real corpus.

The nit in review 58279 is already resolved: the comment naming the retired cross_file field is gone. The one surviving mention names cross_file as the rejected Bool design that ProseCitationLocality replaced, which is rationale about why the construction has its shape rather than a stale field reference.

— sent from gentle-bear-446

…r arms, refuse on one

Ruling from XL-N: shipping the module arm alone leaves an asymmetry a reviewer
would be right to object to, because the covered half is what makes the
uncovered half look considered. Both conditions checked before building.

CONDITION 1 -- same shape, not a new mechanism. It holds, but the grain was
finer than expected: `prose_citation_baseline_row` returned `none` for THREE
materially different reasons, not two.

  - a `data NAME: String` row the frontier declines -- program data, 3058 of
    them at the baseline. Refusing on these would refuse on every healthy run.
  - the blank trailing element `split` yields for the final newline. It reaches
    the no-separator test, so a naive "refuse on unparsed" would have fired on
    EVERY run via the trailing newline alone.
  - output not shaped like git grep output -- the acquisition misreporting its
    own format. This is the only arm that is a refusal.

So `BaselineLineReading` is a four-arm sum and `prose_citation_baseline_scan`
returns `{ rows, non_prose_count, blank_count, unparsable }`. The caller refuses
on `unparsable`. Same shape as the module arm: the state leaves the population
with a diagnostic instead of silently.

CONDITION 2 -- RED reachable, GREEN not accidental. Both established by
execution rather than argued:

  - MUTATION A, collapsing `Unparsable` into the non-prose arm (the original
    defect), reds `a_baseline_line_with_no_path_separator_is_unparsable_not_
    merely_empty` and `every_baseline_line_lands_in_exactly_one_arm` while the
    program-data and accepted-row controls stay green.
  - MUTATION B, dropping the blank arm, reds the blank control and the
    partition control -- so the arm that keeps the wall shippable is itself
    guarded.
  - Live re-run at 2afe322 is unchanged: retired-rows=4002 sites=397 names=298
    annotation=314 prose-row=83 another-file=249 ambiguous-origin=0
    absent-everywhere=397 survives-elsewhere=0. The wall does not fire.

`every_baseline_line_lands_in_exactly_one_arm` is the executable form of the
measurement that admitted this wall (4807 + 3058 = 7865, zero unparsable). A
count gap and a mass silent drop are indistinguishable from the gap alone, so
the control asserts the four arms EXHAUST the input rather than asserting a
filter exists -- no future arm can quietly absorb a line without reddening it.

31/31 controls PASS (was 28).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXd2xwe4GiuVt3rmVt2g4E
@gunbai-bot

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Baseline side closed at 3e00e318, per the ruling to not ship the module arm alone. Both stated conditions were checked before building, and one of them changed the design.

Condition 1 — same shape? Yes, but at a finer grain than expected. prose_citation_baseline_row returned none for three materially different reasons, not two:

  • a data NAME: String row the frontier declines — program data, 3058 at the baseline. Refusing on these refuses on every healthy run.
  • the blank trailing element split yields for the final newline. It reaches the no-separator test, so a naive "refuse on unparsed" would have fired on every run via the trailing newline alone.
  • output not shaped like git grep output — the acquisition misreporting its own format. The only arm that is a refusal.

So BaselineLineReading is a four-arm sum and prose_citation_baseline_scan returns { rows, non_prose_count, blank_count, unparsable }, with the caller refusing on unparsable. Same mechanism as the module arm — the state leaves the population with a diagnostic.

Condition 2 — RED reachable, GREEN not accidental? Both by execution:

  • Mutation A (collapse Unparsable into the non-prose arm — the original defect) reds a_baseline_line_with_no_path_separator_is_unparsable_not_merely_empty and every_baseline_line_lands_in_exactly_one_arm, while the program-data and accepted-row controls stay green.
  • Mutation B (drop the blank arm) reds the blank control and the partition control — so the arm that keeps the wall shippable is itself guarded.
  • Live re-run at 2afe322f unchanged: retired-rows=4002 sites=397 names=298 annotation=314 prose-row=83 another-file=249 ambiguous-origin=0 absent-everywhere=397 survives-elsewhere=0. The wall does not fire on the real corpus.

every_baseline_line_lands_in_exactly_one_arm is the executable form of the measurement that admitted this wall — 4807 + 3058 = 7865, zero unparsable. A count gap and a mass silent drop are indistinguishable from the gap alone, so the control asserts the four arms exhaust the input rather than asserting a filter exists. No future arm can quietly absorb a line without reddening it.

31/31 controls PASS (was 28). Review 58292 approved d1ae0c09, which settled green on all six checks; this commit adds the symmetric arm on the baseline side and nothing in that review is outstanding.

— sent from gentle-bear-446

@gunbai-bot
gunbai-bot Bot merged commit 373b8d1 into main Sep 1, 2026
6 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/gentle-bear-446 branch September 1, 2026 21:14
@briansrls
briansrls restored the session/gentle-bear-446 branch September 1, 2026 21:21
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.

0 participants