Skip to content

corpora: validate doc, rtf, xlsx drawings, and compute against real-producer output - #1184

Merged
Mearman merged 6 commits into
mainfrom
fix/956-corpora
Sep 10, 2026
Merged

corpora: validate doc, rtf, xlsx drawings, and compute against real-producer output#1184
Mearman merged 6 commits into
mainfrom
fix/956-corpora

Conversation

@Mearman

@Mearman Mearman commented Sep 10, 2026

Copy link
Copy Markdown
Member

Closes #956: the four-package corpus workstream, each layer built from LibreOffice-produced output (soffice headless), each with a committed rerunnable scripts/generate-corpus.mjs, a vitest corpus project under pnpm test:corpus, and test/corpus/ gitignored per the family convention.

The corpus runs found and fixed three genuine defects:

  • doc-codec's inline-picture locator assumed Word's OfficeArt InlineSp nesting -- LibreOffice wraps differently and its FOPT recLen spans past the blip, so every LO-produced picture was silently dropped; the locator now scans for a validated blip (known type + rgbUid instance + length + payload file signature), spelling-independent. 8-document corpus (styled runs, headings, lists, tables with merges, inline image, sections) plus 347 unit tests green
  • ooxml.js: a twoCellAnchor editAs=oneCell picture's frame came from the to-marker difference, disagreeing with the producer's own exact-EMU a:ext by the character-unit column-width approximation; the producer transform now rules for oneCell, the to-marker for twoCell (ECMA default). Calc never emits absoluteAnchor (verified -- it normalises all drawings to twoCellAnchor), so that path keeps hand-built fixtures, stated in the module gate comment
  • documents.js: a negative stated answer (T = -0.36) degraded its entire equality -- the unary-minus reading only handled a leading minus; normalisation now signs the following operand after any relation/operator (37 of 300 compute corpus documents affected)

rtf-codec validated cleanly on first run against its first-ever corpus (8 documents). document-compute.js's at-scale differential measurement: 300/300 worked-example documents matched at 6 significant figures -- 100.0%, 0 gaps, 0 unresolved (report committed at test/corpus/report.txt).

Provenance, stated in each README: every corpus is LibreOffice-produced -- no genuine Word-produced files exist on this machine outside private user data (excluded on privacy grounds). Each README says what a LibreOffice corpus does and does not prove; real-user corpora remain a local drop-in on top.

Closes #956.

@Mearman
Mearman marked this pull request as ready for review September 10, 2026 06:04
@Mearman

Mearman commented Sep 10, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@Mearman
Mearman enabled auto-merge (rebase) September 10, 2026 06:04
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review Completed 2026-09-10T06:21:26.919130Z fd82789 Manual request

Security findings

Advisory findings (1)

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread packages/ooxml.js/scripts/generate-xlsx-drawing-corpus.mjs Fixed
scripts/generate-corpus.mjs authors construct-spanning flat-ODT and
packaged-ODT sources (paragraphs, styled runs, headings, lists, tables
with merges, an as-char inline picture, sections) and converts each
headlessly through Writer's own MS Word 97 filter, so every corpus file
is genuine application output; the regenerated corpus.test.ts asserts
each fixture's expectations against the reader, and test/corpus/ stays
gitignored per the family's convention. The corpus immediately exposed
a real reader defect: the inline-picture locator assumed Word's
OfficeArtInlineSpContainer nesting, which LibreOffice does not follow
(its property-table record's recLen spans past the blip), so blips were
silently dropped from every LibreOffice-produced document. The locator
now scans for a validated blip record -- known type, known rgbUid
instance, length inside the stream, payload starting with its format's
own file signature -- which is spelling-independent and rejects wrapper
bytes outright.
scripts/generate-corpus.mjs authors construct-spanning flat-ODT sources
(styled runs, colour, headings, lists, tables, multiple paragraphs,
alignment) and converts each headlessly through Writer's own RTF export
filter, so every corpus file is genuine application output rather than
hand-authored control words; the regenerated corpus.test.ts asserts each
fixture's expectations against the reader through readRtfContent, and
test/corpus/ stays gitignored per the family's convention. The corpus
validated the reader cleanly across all eight fixtures -- no defects
found -- turning the README's 'never been exercised against real
application output' status line into a statement of what now is.
…orpus

scripts/generate-xlsx-drawing-corpus.mjs authors flat-ODS sources with
declared column widths and row heights and a cell-embedded picture at
varied anchors, converting each headlessly through Calc's own Office
Open XML export so both the drawing markup and the worksheet grid it
resolves against are genuine application output; the regenerated
corpus.test.ts asserts the anchor cell and frame against the authored
geometry. The corpus exposed a real sizing defect: a twoCellAnchor
editAs="oneCell" picture's frame was derived from the to-marker
difference, which disagrees with the producer's own transform extent by
the character-unit column-width approximation underneath -- the
producer's exact-EMU transform now rules for the oneCell spelling, the
to-marker difference for twoCell (ECMA's default and the
resize-with-grid semantics). Calc never emits xdr:absoluteAnchor (it
normalises every drawing to twoCellAnchor spellings), so that path
keeps its hand-built fixtures -- stated in the module's own gate
comment.
…following operand

The unary-minus lowering only recognised a minus at the HEAD of a
sequence, so a stated answer like T = -0.36 degraded its entire
equality to one unparsed node (and a + -b likewise): the empty segment
between the relation and the number tripped the operator-placement
diagnostic instead of reading as the number's sign. Normalisation now
rewrites every empty-segment-with-a-subtract-after-it into a negation
flag on the segment the minus precedes -- parity-counted for --x --
leaving genuine placement errors (a = = b) to the existing diagnostic,
and the leading-minus case folds through the same pass rather than its
own special case. Found by the generated at-scale worked-example corpus
(37 of its first 300 documents carried a negative stated answer).
…corpus

scripts/generate-corpus.mjs regenerates 300 deterministic worked-example
markdown documents over the mechanically-lowered arithmetic grammar
(explicit \times/\frac/\sqrt compositions, never juxtaposition;
single-letter symbols, since digit-bearing and multi-letter tokens lower
to unparsed nodes and juxtaposition respectively), each stated answer
computed by the generator's own parallel JS evaluation and rounded to 6
significant figures the way a textbook author rounds -- so a mismatch
names a genuine lowering/evaluation defect, never a fixture typo. The
regenerated corpus.test.ts runs the at-scale measurement through the
identical readMarkdownContent -> lowerMarkdownMath -> runCorpus pipeline
the starter corpus uses, asserting full coverage and writing the
measured report to test/corpus/report.txt (300/300 matched, 0 gaps, 0
unresolved at introduction). The package's vitest config gains the
explicit unit/corpus project split the family convention carries, which
the configless default include would otherwise let leak into pnpm test.
@Mearman
Mearman merged commit 420aca3 into main Sep 10, 2026
22 checks passed
@Mearman
Mearman deleted the fix/956-corpora branch September 10, 2026 06:13
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 8.10.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: fd82789b06

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.


/** Scans forward from `from` for a validated blip record (see readInlinePicture's own locating note) -- every candidate header of a blip type must also carry a known rgbUid instance count, a length inside the stream, and payload bytes starting with its format's own file signature. */
function findBlipRecord(data: Uint8Array, from: number): FoundBlip | undefined {
for (let at = from; at + RECORD_HEADER_SIZE <= data.length; at++) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review

P2 Badge Security: Bound repeated blip searches to the referenced picture

When a service accepts attacker-supplied .doc files, this byte-by-byte search runs to the end of the entire Data stream for every U+0001 anchor. Anchors can reuse one sprmCPicLocation, so work is O(anchors × Data bytes): a 269 KB document with a 256 KiB Data stream and 64 anchors held readDocContent for 6.44 s; the parent took 0.53 ms for 16 equivalent lookups versus 1.65 s here. Bound the search to the referenced picture, cache by location, and add a work/abort budget. Parser resource amplification is in scope.

SECURITY.md reference: SECURITY.md:L15-L18

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No package has been validated against a real-world corpus for doc, rtf, xlsx drawings, or compute

1 participant