Bead: pdf-toolkit-mcp-0av.1 (child of epic pdf-toolkit-mcp-0av)
Branch: claude/math-not-reconstructed-gap (from origin/master @ dd2d922, v0.11.0)
Lane host: silvercloud VM. node_modules is a symlink — do NOT run npm/pnpm install.
A full olmOCR-bench run (1,403 PDFs / 7,019 tests) found that 71.8% of the 3,385 math tests fail with no typed gap covering them.
Do NOT describe this as "we drop equations without declaring it" — that is false. The converter already declares the limit in prose: a ## Conversion limitations section ships in 95.2% of outputs (1,336/1,403) and states that "General equations, subscripts, other fraction bars, unregistered raster variants, and other damaged mathematical glyphs remain source reading-order text rather than being guessed."
The real, narrower gap:
- the declaration is global prose, not a per-page typed gap;
- a consumer cannot tell which pages actually lost mathematical content;
- nothing machine-readable exists to route on (e.g. "send these pages to vision").
E1 = make an existing prose limitation machine-readable and page-specific. It does not change reconstruction behavior at all.
Add a MATH_NOT_RECONSTRUCTED gap code and emit it per page, on evidence, when mathematical source content is present but is not represented as reconstructed math in the Markdown.
- Evidence-backed, never speculative. Emit only on positive source evidence of mathematical content that was not reconstructed. No heuristic "this looks mathy" guess. If evidence is absent or ambiguous, emit nothing — a missing gap is better than a fabricated one.
- Zero behavior change to the document body. This lane DECLARES; it does not reconstruct, remove, or reorder source content. Everything before the trailing declaration sections must be byte-identical to the pre-change frozen corpus. The trailing gaps section, structured
gaps[], page/documentconversion_status, Markdown byte count and digest, and renderer identity are expected to change together when the new gap fires. - Fail-closed discipline preserved. Consistent with the existing gap vocabulary; typed code, page-scoped, no numeric confidence.
- Pinned pdfjs 5.4.624 stays pinned. Do not touch
vendor/qpdf-wasm/runtime/,.beads/, or unrelated tools. - Share-mirror parity — mirror
server/*changes intopdf-toolkit-mcp-share/and passnpm run test:contract:share.
server/markdown-conversion.js—GAP_CODESset (~line 64; currently 17 codes, alphabetically ordered — insert to preserve ordering). Gap emission happens per page alongside the existingTABLE_TOPOLOGY_UNKNOWN/VECTOR_CONTENT_NOT_INTERPRETEDemissions; follow that exact pattern. TheLIMITATIONSarray (~line 84) already contains the equations prose — update it only if wording must change to reference the new typed gap (prefer minimal edits).server/output-schemas.js— the gap-code enum used by theconvert_pdf_to_markdownoutput schema must include the new code, or structured results will fail validation.- The semantic validator asserts
GAP_CODES.has(gap.code)(~line 2954) — new code must be registered before emission.
Do not invent a new parser. Candidates already in the IR / renderer:
- Type-3 / legacy Computer-Modern glyph groups and
glyph_recoverieson raw items — the strongest signal for legacy math typesetting, already tracked for the registry recovery path. - Existing math-run detection used by the bounded
log-spacing repair and the stacked-fraction path (server/markdown-conversion.js) — it already identifies "short compact left-to-right math run" with same-baseline variable evidence. - Superscript/raised-run detection — already implemented; note the existing limitation prose is explicit that a raised run does NOT distinguish an exponent from a footnote marker, so raised runs alone are weak evidence and probably should not trigger the gap by themselves.
- Replacement/damaged-glyph counts on math-class items.
Prefer a conjunction (e.g. damaged/legacy math glyph evidence, or a detected math run that was emitted as flat reading-order text) over any single weak signal. Document the chosen rule in a code comment with the reasoning.
Add test/math-not-reconstructed-gap.test.js:
- Positive: a fixture with clear unreconstructed mathematical content emits
MATH_NOT_RECONSTRUCTEDscoped to the right page. - Negative (critical): a plain-prose fixture with no mathematical content emits no such gap. A fixture with only a raised footnote marker also emits none (raised ≠ math).
- Body invariance: the lane's explicit positive, negative, and adversarial corpus is pinned to Markdown body bytes captured from
dd2d922, in default and compact modes. The existing conversion suites remain the broader regression gate. Do not claim those suites are pre-change byte pins when they are not. - Schema: structured results validate; the new code is in the output-schema enum; the semantic validator accepts it.
- Determinism: two runs byte-identical.
Gates (must be green, paste real output):
npm test -- --run test/math-not-reconstructed-gap.test.jsnpm test -- --run test/convert-pdf-to-markdown.test.js test/markdown-conversion.test.jsnpm run test:contract:share
No release/tag/push. Do not edit .beads/. No npm install. No reconstruction of equations (that is explicitly out of scope and a separate, much harder problem). Do not weaken any existing gap or limitation.
New typed gap registered, schema-wired, emitted only on documented evidence, Markdown body provably unchanged for the frozen lane corpus, derived partial status and renderer 1.18.0 provenance bound consistently, adversarial negatives pass, share parity green. Hand back for review; a codex diff review runs before integration.
Bead: pdf-toolkit-mcp-0av.2 (child of epic pdf-toolkit-mcp-0av)
Base: exact reviewed E1 commit f5d60170ece21d9ab62d93f7e3458325ad533e92
Improve absent-header/footer benchmark behavior without guessing at document structure or silently deleting source content. The renderer removes only source-evidenced extreme-margin furniture by default and offers an exact opt-out that preserves every source line.
A candidate must be no longer than 120 Unicode characters, wholly inside the top or bottom 12 percent of the page, smaller than or comparable to established body text, separated from at least two inner-body lines, and not be a detected heading or any row in a source-evidenced table region. It is removed only when it is either:
- an explicit page-number or provenance pattern; or
- normalized text repeated in the same margin band on at least two selected pages, with digits normalized only to admit changing page/year numbers.
A bare one-character Roman glyph is deliberately not treated as a page number. When evidence is ambiguous, retain the source line. No numeric confidence is created and no model or provider is called.
- Default:
remove_page_furniture: true; exact opt-out:false. - Every affected page emits
PAGE_FURNITURE_REMOVED, so its derived conversion status ispartialand the Markdown declaration, bytes, and digest change. normalizationsreports page-number, running-header, running-footer, removed character, and affected-page counts.- Renderer identity is
pdf-tools.layout-markdown-renderer1.20.0. - Source and share-bundle implementations remain byte-identical.
Positive fixtures cover explicit and cross-page repeated furniture. Negative fixtures cover a genuine heading, reconstructed table content, ambiguous bare Roman text, and the exact opt-out. The real header/footer corpus must be measured against the frozen pre-E2 baseline, with present/order controls checked for regression. Focused conversion, schema, contract, and share-parity gates must pass before local integration. No release, tag, push, or public benchmark claim is authorized by this lane.