Skip to content

Stop the deterministic passes from silently truncating the sections they preempt - #318

Merged
sroussey merged 3 commits into
code-extractorsfrom
claude/zen-albattani-alr17v-deterministic-recall
Aug 21, 2026
Merged

Stop the deterministic passes from silently truncating the sections they preempt#318
sroussey merged 3 commits into
code-extractorsfrom
claude/zen-albattani-alr17v-deterministic-recall

Conversation

@sroussey

@sroussey sroussey commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Two HIGH-severity correctness bugs on this branch, one commit each. Both are the same shape: a model-free parse replaces the model on a section whose destination the caller has already cleared, so a row the parse never produced is a row the filing loses — with no dead letter, no diagnostic, and the same result on every replay.

1. SKIP_PURPOSE matched its total/source phrases anywhere in the label

parseSpacUseOfProceeds dropped any row whose label contained gross proceeds or offering expenses, which is how most filers qualify the two rows that matter most:

  • Underwriting commissions (2.0% of gross proceeds from units offered to public, excluding deferred portion) — the largest expense in the table
  • Not held in trust account after offering expenses — the residual trust row

Measured against src/eval/goldenS1Labels.ts: 13 filings, 16 line items lost. The same code path runs under extractor id 424 for the priced 424B1/424B4, i.e. the final deal figures. Example — CIK 1848507, accession 0001193125-21-066104, loses Underwriting commissions (2% of gross proceeds …) = $7,000,000 and Not held in trust account after offering expenses = $525,000.

Every alternative was reviewed, not only the two named:

alternative verdict
gross proceeds, proceeds after, % public offering, offering expenses anchored to the start of the label — a total/source/ratio row names itself there
^proceeds from, ^from, ^total, the financial-statement captions already anchored, unchanged
reimbursed expenses removed — the golden labels record it as a real line item
per share / per unit kept as an "anywhere" rule but tested against the label with parentheticals stripped, so Held in trust account ($10.20 per unit) survives while a dilution-table metric row does not

Two follow-on findings the anchoring surfaced:

  • One filing (s1_2134856) previously extracted nothing at all — the underwriting-discount row was the only SPAC_USE anchor and it was being dropped, so the whole table failed its own presence check. It now yields 18 rows.
  • A filer who factors the sources into a block under a bare Gross proceeds heading writes the children as plain labels (Offering, Private Units). Such a heading now opens a source block that the matching Offering expenses heading closes.

One golden label was wrong rather than the parser: s1_2114227 was missing the Reimbursed expenses row its identically-tabled sibling s1_2114229 carries. Added from the filing (| Reimbursed expenses(4) | | 3,000,000 | | | 3,675,000 |).

2. preempts() authorized a parse on column coverage alone

covers and clears both name destinations. For a multi-row destination a bare table name asserts only "the parse fills every column persist writes" — never "the parse found every ROW". DeterministicPass.complete already expressed the missing claim but was read only for s1:management roster closure, and no wired pass declared it.

Two further live instances beyond finding 1:

  • spac-sponsorscovers equals clears exactly. Both prose patterns require our|the immediately before sponsor, so our co-sponsor, Beta Holdings LLC, is … matches neither and an already-cleared spac_sponsor_link was rebuilt with one of two sponsors.
  • beneficial_ownership under a full ownershipCoverage — rows failing looksLikeOwner are dropped and stubs carrying a street number are truncated by peelName, before persist and before the section resolves.

complete is now the row half of the contract and a precondition of preempting, checked through a new assertsCompletePopulation (a missing or throwing claim declines, matching how a throwing covers is already treated).

What each pass can honestly claim, and what that costs

pass claim preempts?
spac-classification one row per filing — producing it IS the population yes, unchanged (14 of 42 fixtures)
sponsor-promote one row per (extractor, accession); promoteCoverage still answers the column question yes, unchanged (0 of 42 today)
use-of-proceeds the walk's own decline log: a labelled row between the first and last line item that carries no readable figure and matches no declared rule yes, on 16 of the 20 fixtures it parses
spac-sponsors none — two prose regexes cannot report that the section named no one else no (was 7 of 42)
beneficial-ownership none — a walk that filters its own rows cannot tell a dropped row from an absent one no (was 0 of 42)

Cost/behaviour trade-off. On the committed 42-filing corpus this adds model calls for 7 sponsor sections and 4 use-of-proceeds sections, and removes none elsewhere; beneficial-ownership and sponsor-promote never reached full coverage anyway, so nothing changes for them today. The sponsor and ownership sections are the ones where the honest answer is "this parser cannot assert completeness and must stop preempting" — losing the saving beats losing filed rows.

use-of-proceeds keeps most of its saving because its decline log is a genuine claim derived from the same walk, not a second reading. It errs toward incomplete: three fixtures are declined over sub-table headings that read identically to a blank-figure line item, which costs a model call. The one it declines correctly is s1_2147219, whose Held in trust account row states its amount only as 100.0% of public offering size — golden records it with amount: null, and the parse cannot represent it.

Tests

The recall assertions are the fix's safety net — every pre-existing corpus assertion is precision-only (if (parsed.length === 0) continue plus "does not invent names/purposes outside the golden set"), so a dropped row could never fail CI.

  • parseSpacUseOfProceeds.corpus.test.ts — must find every golden line item on a filing it claims to have enumerated (reverting the anchoring makes this red with all 16 lost rows named), plus a floor on how often the completeness claim holds so the predicate cannot degrade into "always false".
  • parseSpacSponsors.corpus.test.ts — must find every golden sponsor on a filing it hits; the co-sponsor blind spot is pinned directly, so a stray complete: () => true cannot land as a tidy-up.
  • parseSpacClassification.corpus.test.ts — must agree with the golden verdict on every filing it answers. It is the one wired parse whose result is never checked against a model.
  • parseOfferingTables.corpus.test.ts — the promote pass must be right about every column promoteCoverage claims, with no null forgiveness (80 field checks across 17 filings).
  • parseBeneficialOwnership.corpus.test.ts — the walk's four known drops (AIGH; Acuitas Group Holdings, LLC, Acuitas Capital LLC, Dorado Goose, LLC) are pinned as an exact list. A new gap fails; closing one also fails, which is the prompt to reconsider the claim. Reported rather than fixed here: the pass no longer preempts, so nothing is lost by them today.
  • deterministicPass.test.ts / sectionRunner.deterministic.test.ts — full column coverage alone no longer preempts; a false or throwing completeness claim declines.

Two storage tests re-pointed at the new contract (third commit)

Form_S_1.storage.sponsors.test.ts and Form_S_1.storage.ownership.test.ts pinned the superseded preemption contract — the sponsors test asserted the section resolved "without calling the sponsor model", which is exactly what finding 2 removes. Both were verified to pass on base and fail on this branch, i.e. they are this PR's intended behaviour change and not an unrelated break.

They now assert the mirror image: the section reaches the model, the entity is still persisted through that path, and provenance carries the model id and explicitly not DETERMINISTIC_MODEL_ID. Neither was weakened — each now opens by running the deterministic parse over the same section text and requiring it to read the filing outright, so the filing is demonstrably the preemptable-looking case and the point is that it reaches the model anyway. Applied to the base branch, both new files fail there and pass here. No assertion was deleted.

Verification

bun run test           Test Files 428 passed | 3 skipped (431)
                       Tests 3885 passed | 20 skipped (3905), 0 failed
                       (run twice, ~384s each, fully deterministic)
bun run format-check   All matched files use Prettier code style!
bun run build          clean (bun build + tsc)

CI build green on bbb7837.

Correction to an earlier revision of this description. It reported 940 passed, 9 failed and attributed all nine to loads committed S-1 fixtures timing out at 30s under parallel load, calling them pre-existing contention. That was wrong. A full bun run test shows no fixture-loading timeouts at all, and two of those nine were genuine regressions introduced by this branch — the two storage tests described above — masked in the noise of that claim. They are now fixed and the suite is deterministic.


🤖 Generated with Claude Code

https://claude.ai/code/session_01Ske1Jwk7fDFxHykfZGEzce

sroussey and others added 3 commits August 21, 2026 08:42
SKIP_PURPOSE matched its total/source phrases ANYWHERE in a row label, but
those phrases are also how filers qualify a real line item. The
underwriting-commission row is routinely written "Underwriting commissions
(2.0% of gross proceeds from units offered to public)" and the residual trust
row "Not held in trust account after offering expenses", so both were dropped.

That is silent deletion, not a missed hit: `use-of-proceeds` declares
`clears: {use_of_proceeds}` and the deterministic pass declares the identical
bare `covers`, so it preempts the model on every SPAC filing, and the table has
already been emptied by the time the parse runs. The section then resolves
clean, with no dead letter and the same result on every replay. Measured
against the committed golden labels: 13 filings, 16 line items — the largest
expense row in each — and the same code path runs under extractor id `424` for
the priced prospectus, i.e. the final deal figures.

Every alternative is now anchored, or scoped to where it means what it says:

- the total/source/ratio family is anchored at the start of the label, which
  is where a filer names a row that is the table's own arithmetic;
- `reimbursed expenses` is dropped entirely — the golden labels record it as a
  real line item, and the parse now emits it (the label set for one Churchill
  fixture was missing the row its identically-tabled sibling carries, added
  here from the filing);
- the per-share/per-unit metric rules are tested against the label with its
  parentheticals removed, so a metric row is still skipped while a line item
  qualified "($10.20 per unit)" survives.

Anchoring exposed the other half of the shape: a filer who factors the sources
into a block under a bare "Gross proceeds" heading writes its children as plain
labels ("Offering", "Private Units"), which only the heading identifies as
sources. Such a heading now opens a block that the matching expenses heading
closes.

`useOfProceedsIsComplete` reports whether the walk enumerated the table, from
its own decline log rather than a second reading: a labelled row between the
first and last line item, carrying no readable figure and matching no declared
rule, is a row the parse could not represent. It errs toward incomplete, which
costs a model call rather than a filed line item.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ske1Jwk7fDFxHykfZGEzce
`preempts()` authorized a model-free parse to replace the model whenever
`covers` was a superset of `clears`, and both sets name DESTINATIONS. For a
destination holding many rows that says only "the parse fills every column
persist writes" — never "the parse found every ROW". The caller has already
cleared the destination, so a walk that reads N of M rows persists N and the
section is marked resolved: no dead letter, no diagnostic, the same answer on
every replay. The unanchored use-of-proceeds skip rules fixed in the previous
commit were one instance of exactly this hole, and two more were live:

- `spac-sponsors` covers `clears` exactly, so `parseSpacSponsors` preempted on
  its two prose patterns. Both require `our|the` immediately before `sponsor`,
  so a vehicle whose second sponsor is introduced as "our co-sponsor, Beta
  Holdings LLC, is …" rebuilt an already-cleared `spac_sponsor_link` with one
  of two sponsors.
- `beneficial_ownership` under a full `ownershipCoverage`: rows failing
  `looksLikeOwner` are dropped and ones whose stub carries a street number are
  truncated by `peelName`, before persist and before the section resolves.

`DeterministicPass.complete` already expressed the missing claim but was read
only for roster closure, and no wired pass declared it. It is now the row half
of the contract and a precondition of preempting at all, checked through
`assertsCompletePopulation` (a missing or throwing claim declines, matching how
a throwing `covers` is treated). A pass that cannot say its rows are the whole
population costs a model call instead of losing filed rows.

Declared per pass, from what each destination and walk can honestly support:

- `spac-classification` and `sponsor-promote` write one row per filing, so
  producing that row IS enumerating the population — they keep preempting, and
  `promoteCoverage` keeps answering the column question;
- `use-of-proceeds` claims completeness from the walk's own decline log
  (`useOfProceedsIsComplete`), which holds on 16 of the 20 committed SPAC
  fixtures it parses and correctly declines the one whose trust row carries no
  figure;
- `spac-sponsors` and `beneficial-ownership` declare nothing and stop
  preempting: neither prose regexes nor a table walk that filters its own rows
  can report that the section named no one else.

The corpus tests gain the recall side, which every existing assertion missed
because a dropped row invents nothing: sponsors and classification must agree
with the golden labels on every filing they answer for, the promote pass must
be right about every column its coverage claims (80 field checks over 17
filings), and the ownership walk's four known drops are pinned as a list so a
new one fails and closing one prompts revisiting the claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ske1Jwk7fDFxHykfZGEzce
Both pinned the preemption rule the previous commit replaced, and both were
mirror images of what the rule now says. `preempts()` used to authorize a
model-free parse on column coverage alone, so `spac-sponsors` and
`beneficial-ownership` stood in for the model; requiring a row-completeness
claim retires both, because two prose patterns cannot report that the section
named no other sponsor and a walk that filters its own rows cannot tell a row
it dropped from a row the table never had. The destination is cleared before
persist, so a subset there is filed data lost with no dead letter — the model
call is the cheaper side of that trade.

So the assertions flip rather than relax. Each test now proves the section
reaches the model and that the entity is still persisted through it: the
section's prompt appears in the recorded calls, and the observation's
provenance carries the model's id instead of `deterministic`. Each also opens
by running the deterministic parse over the same section text and requiring it
to read the filing outright, which is what makes the filing the case that
demonstrates the rule — coverage is not the question, and a parse that handles
the section perfectly still does not get to answer for it.

The ownership resale test keeps every assertion and gains an accurate name: it
no longer discriminates preemption from non-preemption, since no ownership
section preempts now, and what it verifies is that the class, offered and
after-offering figures the table states survive the model path — the three the
walk would have written null.

Verified on this branch: `bun run test` 428 files / 3885 tests passed, 3
files / 20 tests skipped, 0 failed; `bun run format-check` and `bun run build`
clean. The two tests were confirmed to pass on `code-extractors` and fail at
this branch's head before the change, and the replacements fail on
`code-extractors` and pass here.

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

Copy link
Copy Markdown
Contributor Author

The two red tests encoded the preemption contract this PR supersedes: both asserted that spac-sponsors / beneficial-ownership stand in for the model on column coverage alone. Confirmed by bisect — both pass on code-extractors and fail at fa62f14.

They now assert the mirror image: the section's prompt reaches the model, and the persisted observation's provenance carries the model id rather than deterministic. Each opens by running the deterministic parse over the same section text and requiring it to read the filing outright, so the filing is demonstrably the preemptable-looking case and the point is that it goes to the model anyway. No assertion was dropped; the replacements fail on code-extractors and pass here. The resale-table test keeps every assertion and gains a name that no longer claims a preempt/no-preempt distinction, since no ownership section preempts now.

Verified on bbb7837: bun run test — 428 files / 3885 tests passed, 3 files / 20 tests skipped, 0 failed (no fixture-loading timeouts; the earlier "9 failed" report was inaccurate and had masked these two). bun run format-check and bun run build clean.


Generated by Claude Code

@sroussey
sroussey merged commit 2b4be37 into code-extractors Aug 21, 2026
1 of 2 checks passed
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.

2 participants