.
Skip to main content

Open points from the milestone specifications, September 2026

Writing each milestone's specification after the roadmap revision of 2026-09-26 raised questions the specification could not settle alone: a roadmap line that disagrees with the corpus, a decision that needs the maintainer, a detail of a standard to check before a rule is written, a debt the work found. They are listed here as the writers raised them, grouped by the specifications that raised them, so that none is lost before it is answered in the roadmap, an ADR, a debt row or the milestone itself.

How the points were settled​

All 173 points — the 147 raised with the first specifications and the 26 raised while M23, M29, M30 and M31 were being deepened — were reviewed on 2026-09-27 against the files they name. Each point is followed by its outcome and what the check found.

OutcomePoints
Already resolved by the milestone files as written49
Fixed in the milestone files during the review44
Roadmap amended37
ADR amended8
Debt table amended8
CLAUDE.md amended3
Corpus documentation, manifest or schema amended7
Decided by the maintainer16
Not an issue1

The maintainer's decisions, which several points share:

  • D1. The input keeps its version. With no version chosen, a document read from a file keeps its declared version, raised only to what its features need; a new document is written as 1.7 (ADR 40 amended).
  • D2. All three proposals accepted: objects read from a file are read-only behind an explicit change set; a full rewrite of a signed document is refused unless AllowInvalidatingSignatures is set; CancellationToken comes last with IProgress<PdfProgress> before it, and the asynchronous paths never do synchronous I/O. Each is recorded as an ADR when M03 implements it.
  • D3. One manifest. Its file pattern admits the formats a milestone needs, a format field (pdf by default) selects that format's expectations, and every corpus test filters on it. M07, the first milestone that needs it, does it once for M07, M10, M14, M16, M18 and M31.
  • D4. American English everywhere: public identifiers, package identifiers, CLI verbs, diagnostic codes, manifest keys and prose, rewriting existing preview API where needed (ADR 30 allows it before the first stable release). Applied repository-wide on 2026-09-27.
  • D5. M06 refuses a certified part by default; M07's portfolio unpacking carries a certified member unsigned, with the original attached as /Source and reported.
  • D6. A data-only AdCodicem.Pdf.Fonts satellite with Liberation Sans, Serif and Mono as WOFF2, and Liberation Sans regular in the core. M08's slice-10 ADR confirms the sizes and the license terms.
  • D7. T05 moves to the start of M03, slice 0; M12.1 only extends the baseline.
  • D8. M15 delivers it, as a slice in AdCodicem.Pdf.FacturX running M14's matcher over M15's search; M15 depends on M14.
  • D9. The core carries managed MD5, RC4 and AES (CBC and ECB), tested against RFC 1321, RFC 6229 and NIST SP 800-38A and fuzzed; AES-GCM comes from the platform only and is refused, typed, where the platform has none (ADR 41 amended).
  • D10. The command is adpdf, recorded among the ADR index's decisions too small for a record of their own.
  • D11. Decided at the start of M29, in its slice 1: both the PDF/X and PDF/VT referee and whether PDF/X-4 is capped at PDF 1.6.
  • D12. ADR amendments: ADR 15's and ADR 17's garbled Context and Decision repaired; ADR 43 gains the Unicode property tables (one generator, each table in the assembly that reads it); ADR 34 gains the font guards (MaxFontLength, MaxCharstringOperations).
  • D13. CLAUDE.md: invariant 6 names the entropy encryption needs among the caller-supplied variations; the trap on reals separates computed values from values read from a file; the corpus generators pdftk-java, iText 9 (as a producer only) and our own engine are named.

Nothing here is still open but what D11 leaves to M29's first slice. A point whose outcome is a later milestone's slice is carried by that milestone's file, not by this document.

Raised while specifying M03 and M04​

  • Roadmap M03 acceptance names 'the signed contract document', but documents/contract/chromium-contract-fr.pdf is unsigned. M03 writes the row against the seven vendored signed documents (all useCase contract) and lists a pyHanko-signed twin as a gap. M26's acceptance ('does not invalidate an existing signature on the contract document') depends on the same missing file.
    • Outcome: roadmap amended. Verified: documents/contract/chromium-contract-fr.pdf carries no signature feature; the seven signed committed files are vendor files. M03.md already runs the row on them and lists the pyHanko twin as a gap; M26.md (row 437) already uses 'the signed twin of the contract — M04's fixture, filled by M04'. Only the roadmap's wording is wrong.
  • Roadmap M03 acceptance says 'every corpus document survives open → save → reopen'. That cannot hold for the 18 encrypted committed documents before M16: their strings and streams cannot be written back without the file key. M03 asserts a typed refusal (PdfEncryptedException) instead. M16's acceptance should gain their round trip, full rewrite and update, with the recorded password; it has no such condition today.
    • Outcome: roadmap amended. Verified: 18 committed entries are encrypted; M03 asserts PdfEncryptedException. M16.md already has the round-trip row (CorpusRoundTripTests.Encrypted_documents_round_trip_under_their_own_key, lifting every earlier exclusion), so only the roadmap's two acceptance lists lag.
  • T25 timing disagrees. Roadmap M04 says T25 'is fixed first' in M04, while docs/status.md's debt table and M02.md's exit criteria require it before M02 closes (before M02 slice 2). M04.md treats it as a precondition: fixed by M02, or else as M04's first slice.
    • Outcome: roadmap amended. Verified: status.md T25 says 'Before M02 closes'; M02.md's exit criteria fix it before the cross-reference slice; M04.md treats it as a precondition or its first slice. The files agree; the roadmap's sentence is the odd one.
  • Roadmap M04 acceptance says 'lists the revisions qpdf and pikepdf see', but neither tool enumerates revisions: qpdf merges the chain, and pikepdf is built on qpdf, so it is not an independent second opinion. M04 takes the revision count from pyHanko and has qpdf and pikepdf judge each revision's extracted copy. The roadmap wording could be aligned.
    • Outcome: roadmap amended. qpdf reads the /Prev chain into one merged cross-reference table and exposes no revision list; pikepdf is built on qpdf, so it is the same opinion. M04.md already counts with pyHanko and has qpdf and pikepdf judge each revision's copy.
  • The roadmap table lists M05's dependencies as 'M02, M03', but the roadmap prose ('after the revision history') and M05.md's header say M02, M03, M04.
    • Outcome: already resolved. docs/roadmap.md's table now reads '| M05 | Repair | M | M02, M03, M04 | to do |', equal to M05.md's header.
  • PdfStreamData.Length is an int. An encoded (raw) stream longer than 2 GB is valid under ISO 32000, yet the reader cannot represent it and M03's writer cannot copy it. This is a sibling of T28, which covers only decoded length. It deserves a debt row under invariant 12 and ADR 34.
    • Outcome: debt table amended. Verified: src/AdCodicem.Pdf/Objects/PdfStreamData.cs declares 'public abstract int Length', and PdfObjectParser.ReadStream takes a /Length above int.MaxValue as -1 and searches for endstream in its window. ISO 32000 bounds no stream, and a file past 2 GB is valid, so under invariant 12 the bound needs classifying.
  • CLAUDE.md's known trap says to format reals with '0.####'. Applied in the writer, that would round values read from a file (a /Matrix, a /BBox) and break round-trip fidelity. M03 writes the shortest decimal that reads back to the same double, with no exponent and always a decimal point, and keeps '0.####' rounding where values are computed. The trap's wording may need refining.
    • Outcome: CLAUDE.md amended (D13). Verified: CLAUDE.md line 150 prescribes '0.####' without distinction; M03.md's Serialization section and Traps explain why a rewrite must not round a /Matrix or turn 4.0 into an integer.
  • ADR 40 does not say what happens to an input declared below 1.7. M03 writes max(caller's choice, minimum needed), so a PDF 1.4 input comes out as 1.7 by default; qpdf keeps the input version by default. This needs the maintainer's confirmation.
    • Outcome: decided by the maintainer (D1). The input keeps its version. With no version chosen, a document read from a file keeps its declared version, raised only to what its features need; a new document is written as 1.7 (ADR 40 amended).
  • PDF/A-1, 2 and 3 require a '%PDF-1.n' header, so rewriting such a document as PDF 2.0 loses its claim. M03 obeys the caller and reports write.conformance-lost, and runs the veraPDF acceptance on 1.7 output. ADR 40 could say so explicitly.
    • Outcome: ADR 40 amended. PDF/A-1 is based on PDF 1.4 and parts 2 and 3 on 1.7 (ISO 19005-2 and -3 require a %PDF-1.n header); M03.md already obeys the caller and reports write.conformance-lost, and runs veraPDF on 1.7 output. The ADR's consequences should carry it.
  • M03 makes three design proposals, each recorded as an ADR when implemented, that the maintainer should confirm. (a) Objects read from a file become read-only, with an explicit change set (SetObject, AddObject, RemoveObject); the FIFO cache would otherwise lose in-place edits, and the change alters preview API. (b) A full rewrite of a signed document, usage rights included, is refused with PdfSignatureInvalidationException unless AllowInvalidatingSignatures is set, in which case each broken signature is reported. (c) The cancellation and progress convention: CancellationToken last, then IProgress<PdfProgress> with a readonly record struct, never in options; SaveAsync that never calls Stream.Write, and OpenAsync for non-seekable streams, because ASP.NET Core forbids synchronous I/O.
    • Outcome: decided by the maintainer (D2). All three proposals accepted: objects read from a file are read-only behind an explicit change set; a full rewrite of a signed document is refused unless AllowInvalidatingSignatures is set; CancellationToken comes last with IProgress<PdfProgress> before it, and the asynchronous paths never do synchronous I/O. Each is recorded as an ADR when M03 implements it.
  • T13 in docs/status.md plans pdftotext for M15 and veraPDF for M20. M03 needs veraPDF, poppler (pdftotext, pdfinfo, pdfsig), pikepdf and pyHanko in the integration suite already, for invariant 7 and the signature checks, so T13 should be brought in line when M03 starts.
    • Outcome: debt table amended. Verified: status.md T13 reads 'veraPDF, pdftotext and a rasterizer join it as their milestones arrive | M15, M20, M25'; M03 to M11 call on many more referees (point 25 raises the same row for M06 and M07).
  • M04 has three items to verify against the ISO text before its rules are written. First, whether DocMDP P=1 permits a DSS and document time stamps (the text of ISO 32000-2 12.8.2.2.2, compared with PAdES and pyHanko's default diff policy). Second, how /Info and XMP metadata changes after a certification are classified. Third, whether usage rights added after a certification (the Canadian IMM 1344 file) count as a permitted change. M04.md flags these as traps.
    • Outcome: already resolved. M04.md carries each as a check before the rule is written: the trap 'DSS and document time stamps under P=1' (ISO 32000-2 12.8.2.2.2 and pyHanko's policy compared), the class table's Metadata row ('the level pyHanko gives it … and the ISO text'), and the acceptance row on the Canadian IMM 1344 form ('whatever pyHanko and the ISO text make of usage rights added after a certification, recorded with its reason').
  • Usage-rights (/Perms /UR3) signatures have no independent referee: neither pyHanko nor pdfsig reads /Perms. M04 takes their coverage expectation from the file's %%EOF byte positions, as file.eof-missing's expectation is derived. This should be acknowledged in docs/corpus.md when the expect.signatures field is added.
    • Outcome: fixed in the milestone files. M04.md's Documentation now says docs/corpus.md records this one exception with expect.revisions and expect.signatures: usage-rights coverage derived from the file's %%EOF positions, as file.eof-missing's expectation is derived, and the entries say so. M04's acceptance row already states why.
  • Checked while writing: vendor/pyhanko/acrobat-reader-signed-twice.pdf is not two plain updates, as its manifest title suggests. Acrobat Reader linearized the document when it applied the first signature: /L 83059 equals that signature's ByteRange end, so its three %%EOF markers make two revisions. The manifest title and features ('incremental-update' only) could say so.
    • Outcome: manifest amended. Verified with the bytes: /Linearized 1/L 83059; three %%EOF markers at 451 (after 'startxref 0'), 83053 and 107975; first /ByteRange [0 59580 78318 4741] ends at 83059, second [0 86638 105376 2605] at 107981, the file's length. The title says 'each in its own incremental update' and the features list only incremental-update. M04.md's trap already describes it.

Raised while specifying M06 and M07​

  • Roadmap dependency column: M06 'Depends on: M03, M04', yet its CLI verbs validate and repair need M02 and M05. The execution order places both before M06, so nothing blocks, but the column understates it. M06.md says that if M05 has not closed, the repair verb lands with M05.
    • Outcome: roadmap amended. True, but changing the column would let M05 gate the first business priority; M06.md already lands the repair verb with M05 should M05 close later. Saying so in the deliverables is enough, and keeps M06.md's header equal to the table.
  • The roadmap puts the page-selection grammar in M07, but M06's merge and pages verbs need page ranges. M06.md gives M06 plain ranges (1-3,7,z) and M07 the full grammar as a superset, so nothing written for M06 changes meaning.
    • Outcome: already resolved. M06.md's Scope gives M06 plain ranges ('1-3,7,z') and M07.md's grammar is declared their superset ('nothing written for M06 changes meaning'); the roadmap's M07 deliverable is unaffected.
  • The roadmap's M06 deliverables do not name the logical structure tree. ADR 17 already decided that structure trees are recombined on merge, and invariant 7 forbids silent loss, so M06.md includes a structure merge (slice 9). The roadmap row could say so.
    • Outcome: roadmap amended. Verified: ADR 17 reads 'When merging, structure trees, OutputIntents, metadata and fonts are genuinely recombined'; M06.md delivers it in slice 9.
  • The roadmap's M06 acceptance refers to 'the contract appendices', but documents/contract/chromium-contract-fr.pdf has no appendix, link or bookmark. M06.md substitutes a set (word2019-ccs-contract-schedule.pdf, indesign13-pdfua1-german-book-chapter.pdf, chromium-report-fr.pdf) and lists the gap. Separately, M03's roadmap acceptance mentions 'the signed contract document', but the committed contract is unsigned; the signed contracts are vendor files.
    • Outcome: roadmap amended. Verified with pikepdf: chromium-contract-fr.pdf is two pages with no outline or link. M06.md substitutes a set of real documents and lists the gap at priority 2.
  • manifest.schema.json describes expect.attachments as M15's and expect.formFields as M16's, but M06 is the first milestone to assert both. M06 also adds expect.pageLabels. The schema descriptions and docs/corpus.md should move with M06.
    • Outcome: manifest schema amended. Verified: the schema's descriptions read 'Embedded files the document carries (M15).' and 'Interactive form fields the document declares (M16).'; docs/corpus.md already credits merge invariants to M06. pageLabels is added by M06's slice 5, with its schema, so only its definition is proposed here.
  • Debt item T06 (PDFDocEncoding, UTF-8 and UTF-16LE text strings) is scheduled in status.md 'before the first public release'. M06 needs it before slice 5, for field-name collisions, label prefixes and outline titles; M06.md makes it an exit criterion. status.md should reschedule it.
    • Outcome: debt table amended. Verified: status.md T06's decision column says 'Before the first public release'; M03.md fixes it before its slice 8, M06.md now requires it before slice 5 (aligned with M03 in this review), and M07 and M11 read titles and annotation text through it.
  • The roadmap's M07 acceptance says 'every image file in the corpus becomes a page', but the corpus holds no image file, and manifest.schema.json only admits .pdf paths. The maintainer needs to decide how image files enter the corpus (schema and docs/corpus.md). M07.md meanwhile uses the JPEG, JPEG 2000 and CCITT streams taken out of committed PDFs, and lists the rest as priority-1 gaps.
    • Outcome: decided by the maintainer (D3). One manifest. Its file pattern admits the formats a milestone needs, a format field (pdf by default) selects that format's expectations, and every corpus test filters on it. M07, the first milestone that needs it, does it once for M07, M10, M14, M16, M18 and M31.
  • ADR 42 says CCITT decoding in the core is 'needed by M07's image pages', but the roadmap places CCITT decoding in M22, after M07. M07.md needs no decoder: strips pass through, several strips are stacked, and FillOrder 2 is a bit reversal. Either amend ADR 42's sentence or confirm that M07 needs no CCITT decoder.
    • Outcome: ADR 42 amended. Verified: M07.md passes CCITT strips through, stacks several strips, bit-reverses FillOrder 2, and uses poppler to check pixels; joining strips is left to M22. The roadmap's M22 carries 'in the core, CCITT G3 and G4 decoding (ADR 42)'.
  • M07.md goes beyond the roadmap's image list ('single-strip TIFF in CCITT'): it adds multi-strip stacking, LZW, Deflate and PackBits TIFF pass-through, and lossless re-deflating for PNG interlacing and alpha using the core's Flate and predictors. The maintainer may want to confirm this scope.
    • Outcome: roadmap amended. The additions stay within ADR 42 (nothing lossy, every rewrite reported), but they contradict the roadmap's 'without re-encoding' and its byte-identical acceptance for interlaced or alpha PNGs, so the roadmap should state the scope M07 specifies.
  • Neither the roadmap's M06 nor its M07 lists these verbs, but under the tool-track rule M06.md adds attachments and M07.md adds split, collate, images, outline and unpack. The command name adpdf is only a working name (ADR 24 covers package identifiers only) and needs the maintainer's decision.
    • Outcome: decided by the maintainer (D10). The command is adpdf, recorded among the ADR index's decisions too small for a record of their own.
  • Signed-input policy needs the maintainer's confirmation. The roadmap only says that voiding a certification is refused, or reported when the caller insists. M06.md adds: approval-signed parts are carried unsigned by default and reported, with an AttachOriginal option that attaches the original as AFRelationship /Source; certified parts are refused by default. M07's portfolio unpacking deliberately defaults to 'carry unsigned + attach original' for certified members too, so the roadmap's portfolio acceptance can pass without Insist.
    • Outcome: decided by the maintainer (D5). M06 refuses a certified part by default; M07's portfolio unpacking carries a certified member unsigned, with the original attached as /Source and reported.
  • New referee containers: M06 and M07 rely on pikepdf, pdf.js (Node + pdfjs-dist), poppler (pdftotext, pdfdetach, pdfimages, pdftoppm), veraPDF, pyHanko and Pillow. ADR 27 names only qpdf, veraPDF, pdftotext and mutool, and debt item T13 lists qpdf as the only referee. The integration harness and T13 should record the additions.
    • Outcome: debt table amended. Same row as point 10: T13 rewritten to list every referee M03 to M10 call on. ADR 27's decision ('qpdf — and later veraPDF, pdftotext, mutool') names later referees by way of example; its rule, containers via Testcontainers, is unchanged and needs no amendment.
  • Alignment with the parallel M03.md and M04.md: M06.md now uses write.version-raised (M03) for version raises, not a code of its own; follows M03's IProgress``/CancellationToken convention and AllowInvalidatingSignatures; and uses M04's write guard (PdfSignatureInvalidationException) and PdfSignatureKind. It keeps PdfWriter internal, which M03 left for M06 or M08 to decide. If M03 or M04 rename these, M06.md must follow.
    • Outcome: already resolved. Checked: M03.md defines write.version-raised, PdfProgress, AllowInvalidatingSignatures and PdfSignatureInvalidationException; M04.md defines PdfSignatureKind with Approval, Certification, DocumentTimestamp and UsageRights, the four M06 names; M08.md keeps the writer internal behind PdfDocumentBuilder. The one gap, M06's progress stages, now names M03's Merging.
  • Corpus finding worth keeping: one sRGB ICC profile (the same decoded bytes) appears under four different encodings and two OutputConditionIdentifiers ('sRGB IEC61966-2.1' and 'Custom') from PDFlib, Antenna House, 3-Heights, Acrobat and Distiller. M06.md therefore deduplicates color profiles on decoded bytes. M20's output-intent rules will meet the same fact.
    • Outcome: already resolved. Verified with pikepdf: ten committed documents share one decoded sRGB profile under four raw encodings, identified as 'sRGB IEC61966-2.1' and 'Custom'. M06.md keys profiles on decoded bytes; M20.md's merge row (pdflib-oj-exchange-rates-greek with 3heights-eu-consolidated-regulation-2026, 'one sRGB profile differently encoded') expects one output intent profile object.

Raised while specifying M08 and M09​

  • Roadmap M08 acceptance asks for 'typographic ligatures', while architecture §3.3 and ADR 43 keep shaping (GSUB/GPOS, kerning) out of the core. M08.md delivers ligatures only through the glyph-run path (pre-shaped runs); the unit test project, never the core, references HarfBuzzSharp to shape them. The core's simple path (stamps, headers, captions) does no kerning and no ligature substitution. If the maintainer wants kerning or standard ligatures in stamps, that needs a separate decision: a GPOS pair-adjustment and GSUB liga reader in the core. A native test-only dependency on HarfBuzzSharp in tests/AdCodicem.Pdf.Tests also wants confirming.
    • Outcome: fixed in the milestone files. M08.md's slice 7 no longer gives a test project a native dependency: the shaped runs are computed once with uharfbuzz in the Python referee container and committed as test data with each font's checksum, which honors ADR 43 (HarfBuzzSharp in .Html) and keeps the glyph-run contract M12 will use. Kerning or ligatures in the core's simple path stay out, as ADR 43 and architecture §3.3 decide; nothing in the roadmap asks for them.
  • T04, OFL set packaging: M08.md proposes, for an ADR in slice 10, a data-only satellite AdCodicem.Pdf.Fonts holding Liberation Sans/Serif/Mono as WOFF2 (the metric twins of Helvetica/Arial, Times/Times New Roman, Courier/Courier New), plus one face (the sans regular) carried by the core so that the core alone can stamp PDF/A inputs. The package is in neither the architecture.md §2 package table nor ADR 24. The Reserved Font Name 'Liberation', and whether format conversion to WOFF2 is allowed, are to confirm against the OFL FAQ. M09's default stamp face depends on this outcome, and M09.md says what happens if the core carries no face.
    • Outcome: decided by the maintainer (D6). A data-only AdCodicem.Pdf.Fonts satellite with Liberation Sans, Serif and Mono as WOFF2, and Liberation Sans regular in the core. M08's slice-10 ADR confirms the sizes and the license terms.
  • M08.md puts two new guards in PdfReaderLimits (MaxFontLength 64 MB and MaxCharstringOperations 65,536, codes limit.font-length and limit.charstring-operations), and has the font registry take a PdfReaderLimits. This stretches ADR 34's record beyond reading PDFs. The maintainer should confirm that one record for all hostile bytes is preferred over a separate PdfFontLimits.
    • Outcome: ADR 34 amended (D12). A font inside a PDF is decoded under its document's limits already; a second record would make one file answer to two. ADR 34's rejected alternatives already prefer one record over scattered bounds. Recording the extension in the ADR is the clean way.
  • M03 left the public shape of the forward-only writer to 'M06 or M08', and M06 kept it internal. M08.md therefore introduces PdfDocumentBuilder/PdfPageBuilder as its public face (pages flushed as finished, fonts finalized last). M12 should build on it, and this is an API decision to ratify.
    • Outcome: already resolved. The chain is consistent: M03.md delegates the shape to 'M06 or M08', M06.md keeps the writer internal, M08.md decides PdfDocumentBuilder, and M12.md (line 154), M13, M14, M16 and M17 build on it. As a preview API it may still change (ADR 30); M08's Documentation records it in architecture §3 as built.
  • The tool gets verbs the roadmap does not list: fonts in M08 (lists a document's fonts as pdffonts does, plus what our parser made of each program), and impose in M09 beside stamp/bates/normalize (the track rule gives every operation a verb).
    • Outcome: already resolved. The roadmap's track rule covers them: 'a milestone that adds an operation adds its verb once the tool exists'.
  • M09's markers for its own stamps use second-class keys under a developer prefix (ISO 32000-2 Annex E; working prefix ADCP). The prefix must be registered with the PDF Association before a stable release writes it into documents. No roadmap or ADR entry mentions this.
    • Outcome: debt table amended. M09.md's exit criteria already make the registration or its recording a blocker of the first stable release; like T15 and T19, it belongs in the debt table where the release path is checked.
  • Roadmap M09 wording: 'Stamping every corpus document leaves every untouched object byte-identical' holds literally only for incremental updates, since M03's full rewrite renumbers and re-serializes. M09.md states it as revision equality for updates, and as graph equality with identical encoded stream bytes for rewrites. Likewise, 'removing Bates restores content streams byte for byte' becomes: the original streams are never touched, and /Contents and /Resources are restored.
    • Outcome: roadmap amended. M03's full rewrite renumbers and re-serializes; M09.md states revision equality for updates and graph equality with identical encoded streams for rewrites.
  • Roadmap M09 says every stamp is an /Artifact /Pagination '(header, footer, page number, Bates)', but the PageNum and Bates subtypes exist only in PDF 2.0. M09.md writes Header/Footer by position in 1.7 output and PageNum/Bates only in 2.0 output. M10.md (written in parallel) says a barcode stamp uses '/Pagination with /Bates' unconditionally, and should follow the same version rule.
    • Outcome: roadmap amended. ISO 32000-2 adds PageNum and Bates to the artifact subtypes Header, Footer and Watermark of PDF 1.7. M10.md now follows M09's version rule ('Header or Footer by position in PDF 1.7, Bates in PDF 2.0'), so only the roadmap lags.
  • M09 uses M07's page-selection grammar and M07's passed-through image XObjects (logos in stamps), yet the roadmap lists M09's dependencies as M06 and M08 only. M07 precedes M09 in execution order, so this is harmless, but the dependency column could add M07.
    • Outcome: roadmap amended. True and harmless in execution order. M09.md's header stays equal to the table; its Scope now carries a Dependencies note, as M10.md does, until the table changes.
  • M09 reuses M06's report shape (entries of code/document/page/object/message, stable JSON) under stamp.* and geometry.* codes, and M11.md says it returns 'the report M09's content operations return'. If M06's type is assembly-specific (PdfAssemblyReport), a general manipulation report type should be settled once, in M06 or M09.
    • Outcome: fixed in the milestone files. M06.md now defines PdfOperationReport (entries of code, document or part, page or object, message; counts by code; stable JSON) as the one report every operation on existing documents returns, replacing PdfAssemblyReport; M07.md, M09.md and M11.md name it. M16.md already says 'the report M06, M09 and M11 return'.
  • A stamp placed as a /Stamp annotation is permitted under a DocMDP P=3 certification where page content is forbidden. M09.md defers it to M11; M11's scope could name it explicitly.
    • Outcome: already resolved. M11.md's Scope names it ('any of M09's PdfStamp marks placed as a stamp annotation — the form a DocMDP P=3 certification permits where page content is forbidden, which M09 left here'), and its acceptance has the P=3 row.
  • M02's structural 'Fonts' family promises '/Widths consistent with the descriptor', but a check against the embedded program needs M08's parser. It is undecided whether M08 adds that rule to the structural profile or it stays with M20's PDF/A clause 6.2.11.5.
    • Outcome: fixed in the milestone files. M02.md's Fonts row now says what the structural profile checks (the array's length from /FirstChar to /LastChar and its coherence with the descriptor) and that widths against the program are PDF/A's rule (ISO 19005-2 6.2.11.5), M20's pdfa-font on M08's parser — which M20.md already lists ('widths against the program').
  • Test fonts are not covered by docs/corpus.md. M08.md places them in tests/fonts/ under their OFL licenses, each under 2 MB, including a CJK face subsetted by fontTools for the roadmap's CJK sample. The license terms of the candidate CJK face (e.g. Noto Sans CJK, derived from Source Han Sans) need checking; if it cannot be committed, a fetch-on-demand path like ADR 32 would be needed for fonts.
    • Outcome: docs/corpus.md amended. docs/corpus.md says nothing about test fonts; M08.md's Corpus section describes them. A short section gives them the corpus's discipline and the fetch-on-demand fallback.
  • M08 relies on the BCL's BrotliDecoder for WOFF2, and M23 runs the core in browser WebAssembly. Brotli's availability there is unverified; M08.md requires a diagnostic rather than a PlatformNotSupportedException if it is missing.
    • Outcome: fixed in the milestone files. Checked on Microsoft Learn: the .NET 10 reference shows no UnsupportedOSPlatform("browser") on BrotliDecoder or BrotliStream, while ZstandardStream and BrotliCompressedContent carry one. M08.md's trap now says so, leaves the proof to M23's browser run, and names the diagnostic (font-program.unsupported); a unit test through a seam that reports Brotli missing was added. M23.md should include a WOFF2 decode in that run (cross-file issue).
  • Corpus facts measured for these files with pikepdf and fontTools on 2026-09-26, recorded in the milestone text. 118 committed documents embed 368 font programs (287 TrueType, 61 bare CFF, 12 CID-keyed CFF, 1 OpenType, 7 Type 1). fontTools refuses 7 of them: 4 Distiller 5 TrueType subsets with cmap subtables six bytes short, and 3 CFF programs. No committed pages share a content stream. Content ending inside BT occurs on damaged pages of distiller4-congress-hr1904-enrolled-bill (page 11) and pdfmaker707-word-law-library-iraq-legal-history (three pages). Chromium pages open with an unrestored top-level flip cm. There are 30 committed PDF/A claims: 22 upheld, 6 rejected, 2 without a verdict. These counts should be re-checked if the corpus changes before the milestones start.
    • Outcome: fixed in the milestone files. Re-measured with pikepdf: 118 documents and 368 programs hold when counted by font descriptor (366 distinct, two TrueType programs shared), 30 PDF/A claims with 22 upheld, 6 rejected, 2 without verdict. But 17 of the 118 documents are encrypted, holding 36 programs the reader cannot decode before M16; M08.md's acceptance row and slices 1 and 2 now count the 101 readable documents (259 TrueType, 58 bare CFF, 8 CID-keyed CFF, one OpenType) and add the rest at M16.

Raised while specifying M10 and M11​

  • Roadmap table: M10 'Depends on: M08' only, yet its own deliverable is 'a hook for M09's stamps' (and M09.md already reserves that form-XObject hook for M10). Execution order puts M09 first so nothing blocks, but the table should read 'M08, M09'. M10.md keeps the header as the table says and explains it under Scope.
    • Outcome: roadmap amended. True; M10.md keeps its header equal to the table and explains under Scope.
  • Roadmap M10 acceptance 'A Swiss QR-bill passes the reference validator of the Swiss payment standards': SIX's validation portal is an online upload service, not something a container runs in CI (ADR 27). M10.md makes it SwissQRBill's validator (MIT, in a container) in CI, plus a manual submission of the variant set to SIX's portal once per guideline version, recorded in status.md. 'Read by a banking-app-grade decoder' is likewise made concrete as zxing-cpp and zbar at 150 and 300 dpi with payload parity against segno. The roadmap wording may want to say so.
    • Outcome: roadmap amended. M10.md makes both conditions executable (SwissQRBill's validator in CI, a manual SIX submission per guideline version recorded in status.md; zxing-cpp and zbar at 150 and 300 dpi, payload equal to segno's). The roadmap should say what is tested.
  • M07.md says separators recognized by their barcode wait for 'M10 with M22', but the roadmap's M10 delivers encoders only and no milestone or open question plans barcode recognition. Suggest an open-questions row: 'Barcode recognition on scanned pages — trigger: scan batches split on barcode or patch-code separator sheets'.
    • Outcome: roadmap amended. Verified: M10.md and M22.md both put barcode reading out of scope. M07.md's sentence is corrected in this review; the open question makes the trigger visible.
  • Roadmap M11 acceptance names 'the rasterizer referee' before M25 exists; M11.md names MuPDF (mutool draw), as M09.md already does, beside pdf.js, with poppler as a second opinion.
    • Outcome: already resolved. M11.md's Integration names MuPDF (mutool draw) as 'the rasterizer referee the roadmap asks pdf.js to agree with', as M09.md does; the roadmap's generic wording is compatible.
  • Roadmap M19 says its content-stream editing pipeline is 'shared by every later content change', and M09.md leaves editing operators inside existing streams to M19 — but M11's layer removal and flattening must rewrite content streams (hidden sections keep their graphics-state and text-position effects, so it is not a byte deletion). M11.md builds the first rewriter (a marked-content filter over M08's PdfContentReader/PdfContentBuilder) and says M19 should grow from it; M19's roadmap text could say 'generalizes M11's filter'.
    • Outcome: roadmap amended. M19.md already builds its pipeline from M11's filter ('PdfContentEditor — M11's filter grown into the pipeline'); M09.md now says so too. Only the roadmap's M19 wording lags.
  • Factual error in M06.md's acceptance: it describes vendor/opf-format-corpus/pdfmaker707-word-law-library-iraq-legal-history.pdf as having 'a layer off by default'. Its /D has an empty /OFF and an empty /Order: the HeaderFooter group is ON, only hidden from the layers panel. The committed groups actually off by default are the two in vendor/pdf-association/handwritten-utf16le-strings.pdf and the AR-11 form's PrintOnly group (/BaseState /OFF). Also, three of the five PDFMaker 7/8 layered files (pdfmaker7-powerpoint-va-cancer-database-course, pdfmaker707-word-va-esig-developer-guide, pdfmaker707-word-va-lms-step-by-step) have /OCProperties with no /D at all, which ISO 32000 requires — M02's Arlington-generated shape rules should report it and the manifest's findings will need it.
    • Outcome: fixed in the milestone files. Verified with pikepdf: its /D has empty /OFF and /Order (HeaderFooter on, hidden from the panel); handwritten-utf16le-strings has two groups in /OFF; pdfmaker7-powerpoint-va-cancer-database-course, pdfmaker707-word-va-esig-developer-guide and pdfmaker707-word-va-lms-step-by-step have no /D. M06.md's layer row and Corpus bullet corrected; M11.md already had it right; M02's Arlington-generated rules report the missing /D and the manifest's findings follow slice 3.
  • vendor/us-federal/livecycle-uscis-ar11-xfa-form.pdf, named by M06's layer row for 'print-only and view-only layers with usage', is AES-128 encrypted (empty user password), so the reader cannot open it before M16 — M06's own definition excepts encrypted documents. M11.md asks for a decrypted twin derived with qpdf --decrypt (priority 1 gap); M06 may want the same.
    • Outcome: fixed in the milestone files. Verified: the manifest marks it encrypted (encryption-aes128, empty user password). M06.md's layer row now uses the decrypted twin, the original once M16 opens it, and its What it lacks gains the twin at priority 1 (shared with M11); M11's layer-merge row names the twin too.
  • docs/architecture.md's package table does not list AdCodicem.Pdf.Barcodes among AdCodicem.Pdf.Html's dependencies, while M12.5 uses barcodes (M10). M10.md leaves the choice (direct reference, or resolving the barcode: scheme through ADR 38's resolver) to M12.5 and says the table must record it then.
    • Outcome: already resolved. M12.md decides the seam ('The M10 seam, decided: .Html references .Barcodes directly') and its Documentation updates architecture.md §2 accordingly; M10.md now says the seam was decided rather than pending.
  • The Swiss QR-bill needs Liberation Sans regular AND bold; M08.md proposes the core carry only the sans regular and the rest of the OFL set go to a data-only AdCodicem.Pdf.Fonts satellite (ADR pending in M08). If so, .Barcodes either references .Fonts or requires the caller to register the bold face — to settle when M08's ADR lands.
    • Outcome: fixed in the milestone files. M10.md's slip layout now states the rule whichever way M08's ADR packages the set: .Barcodes references the data-only package holding the bold face when the core lacks it, and a slip without a permitted bold face is refused, naming the face, never drawn in synthetic bold.
  • T16 (per-package API baseline and one pack script) names '.Barcodes in M10 at the latest'; M10.md puts it first in slice 1 and in the exit criteria.
    • Outcome: already resolved. Consistent: status.md T16 ('.Barcodes in M10 at the latest'), M10.md slice 1 ('T16's per-package API baseline in place first') and its exit criteria agree.
  • docs/website/sidebars-project.js lists only milestones/M01, M02 and M05; the new milestone files (M03, M04, M06–M11) publish without sidebar entries until someone adds them (not edited here, per instructions).
    • Outcome: already resolved. sidebars-project.js now lists milestones/M01 to milestones/M31 under 'Milestones'.
  • ADR 24 lists the satellite identifiers of 2026-09-13 only; AdCodicem.Pdf.Barcodes (and .Imaging, .CaseFile, .Compare, .Docx, .Tool) are covered by the reserved prefix but not recorded in the ADR — architecture.md's table is the only record.
    • Outcome: ADR 24 amended. Verified: ADR 24 names the core plus .Validation (now .Conformance by ADR 36), .Html, .AspNetCore, .FacturX, .Rendering and .Signing; architecture.md §2 lists the rest. One consequence line makes the register explicit rather than amending the ADR for each package.
  • docs/features/features.json files barcodes under the 'HTML to PDF' group, while M10 serves stamps on existing documents as much as HTML; the group may be worth revisiting when M10 closes.
    • Outcome: fixed in the milestone files. Verified: the 'barcodes' feature sits in the group titled 'HTML to PDF'. M10.md's Documentation now asks for the entry's group to be revisited when it closes, since features.json is brought to its state then (definition of done, point 5).
  • M11.md adds five validation rules to M02's structural profile (annotation.reply-cycle, annotation.popup-parent-mismatch, layer.group-undeclared, layer.configuration-unknown-group, layer.group-unused) — rule identifiers are public contract, so they need the maintainer's eye; layer.group-unused (Information) will add a declared finding to vendor/uk-ogl/indesign-acrobat-hmcts-n208-form.pdf's manifest entry.
    • Outcome: already resolved. The identifiers follow M02's convention (family.name in lowercase kebab case, ADR 36), which ValidationRuleIdTests enforces, and 'annotation' is M02's existing Annotations family; they become contract only with the first stable release (ADR 30), and are reviewed in their pull request like any API. Declaring the HMCTS finding is what M02's manifest rule requires.
  • pdf.js applies an OCG's View/Print usage directly under its display/print intents, whereas ISO 32000 applies usage only through /AS; M11.md's evaluator follows the specification and its acceptance records disagreements rather than bending to the referee. On PDF/A-4 the layer form of a print-only stamp is left undecided until veraPDF's PDF/A-4 profile is seen to accept it.
    • Outcome: already resolved. M11.md's evaluator section says the evaluator follows the specification and the acceptance records the difference, and its print-only table allows the layer form on PDF/A-4 only once veraPDF's profile has been seen to accept it.

Raised while specifying M12​

  • Roadmap dependency column for M12 lists M06, M08, M10 only, but M12.5 uses M07's image container parsers and EXIF orientation, and M12.6 uses M07's outline model and open parameters, M09's stamp pipeline (HTML-fragment stamps) and M11's link-annotation model. The execution order satisfies all of them; the table could list M07, M09 and M11, or M12.md's per-sub-milestone table could be taken as authoritative.
    • Outcome: roadmap amended. Verified: M12.md's sub-milestone table names M07, M09 and M11, and the roadmap's own prose says M06, M07, M09, M10 and M11 come before M12. With the change, M12.md's header must take the same list; it now equals the current roadmap row.
  • ADR 38's status says 'Implemented by M12.5', but M12.1 needs the resolver seam for <link rel=stylesheet> and @import. M12.md lands the interface and the non-network policy (data:, declared directories, embedded resources, file: confinement) in M12.1 and the network half in M12.5. ADR 38's status could say 'begun in M12.1, completed in M12.5'.
    • Outcome: ADR 38 amended. Verified: M12.1 slice 6 delivers IResourceResolver, data:, AllowDirectory, AllowEmbedded and file: confinement; M12.5 slice 1 adds the network. The proposed text replaces the Status paragraph of docs/adr/0038-the-html-engine-loads-resources-deny-by-default.md.
  • 'The two-column report' in the roadmap's M12 acceptance is ambiguous. The corpus has one report with a two-column annex (documents/report/chromium-report-fr.pdf, feature two-column-text), which M13 and M15 treat as the two-column report. M12.md does the same: pages 1-3 are the multi-page report and page 4 is the two-column report. It adds a three-column terms-of-sale source as a gap. If a separate, fully two-column report was meant, a new source is needed.
    • Outcome: roadmap amended. Verified: the only committed report with two columns is report-fr.html's annex, page 4 of chromium-report-fr.pdf. LibreOffice's rendering also sets it in two columns on page 3, checked with pypdf positions at x=51 and 302. M12, M13 and M15 all use that annex. Defining the term once, in M12's bullet, leaves M13's and M15's bullets as they are. A separate fully two-column report would be a new source; M12 already lists three-column terms of sale as a gap.
  • status.md T09 puts the throughput budget in M23, but M12's roadmap acceptance enforces a batch-generation throughput budget in CI (1,000 invoices). T09 should be narrowed: reader/writer throughput stays with M23, HTML batch throughput starts in M12.
    • Outcome: debt table amended. Verified: the M12 roadmap acceptance and M12.6 slice 8 (CorpusBatchTests.A_thousand_invoices_meet_the_throughput_budget) enforce it in CI. M01.md and M23.md keep the reader and writer budgets in M23.
  • T05 (public API baseline) is scheduled 'at the start of M12', and M12.md closes it in M12.1 slice 1 as status.md says. But public API and satellites ship earlier (M06's tool, M10's .Barcodes), and T16 wants per-package baselines before .Barcodes ships, so T05 should probably move to M06 or M10.
    • Outcome: decided by the maintainer (D7). T05 moves to the start of M03, slice 0; M12.1 only extends the baseline.
  • Manifest inconsistency (reported, not edited): documents/report/chromium-report-fr.pdf has the feature repeated-table-headers, but in that file the measures table fits on page 3 and no header repeats (checked with pypdf: the header text appears once). The repetition is in documents/report/libreoffice-report-fr.pdf, which ignores the forced break and runs the table onto page 3 with its header row repeated, and whose features do not say so. M12.3's acceptance points at the LibreOffice file for this reason.
    • Outcome: corpus builder and manifest amended. Verified with pypdf: Chromium's page 3 shows the header row once. LibreOffice's page 2 ends inside the table and its page 3 begins with the header row, as does libreoffice-report-pdfa2b.pdf. Both LibreOffice files also set the annex in two columns on page 3. The features are written by build_corpus.py (line 540 and the lo_report and archival record calls). M12's trap and M15's rows now describe the discrepancy and read as history once this is applied.
  • Overlap with the sibling M13: M13.md says 'M13 adds DisplayDocTitle', while M12.6 sets it whenever <title> exists, as Chromium's corpus output does. It is harmless, but the two files should agree on which milestone writes it.
    • Outcome: fixed in the milestone files. M13.md line 331 already credits M12.6 for the HTML path. The remaining overlap was the core structure builder writing /ViewerPreferences << /DisplayDocTitle true >> unconditionally. M13.md now writes DisplayDocTitle through M06's typed viewer preferences only when there is a title or the PDF/UA-1 target is set, and says M12.6 already sets it from
  • Architecture package table: M12.5 decides M10's open seam by having .Html reference AdCodicem.Pdf.Barcodes directly (barcode: URLs resolved in-process, allowed by default like data:). §2 must add .Barcodes to .Html's dependencies. The hyphenation satellite needs an identifier: AdCodicem.Pdf.Hyphenation is proposed, with an ADR on pattern licenses in M12.2 slice 5. ADR 24's identifier list does not include it.
    • Outcome: already resolved. M12.md decides the M10 seam in M12.5 (Barcodes section). Its Documentation section updates architecture.md §2 (.Html depending on .Barcodes, the hyphenation package named, .AspNetCore's dependencies) when it is built. The hyphenation identifier is settled by the ADR of M12.2 slice 5, as architecture.md §2 already anticipates ('named by their milestones without a package identifier yet'). ADR 24 records the identifiers claimed in 2026-09-13 and the reserved prefix, which covers any AdCodicem.* package; it needs no change.
  • The roadmap's 'memory constant as page count grows' cannot hold for a single-string HTML source, because AngleSharp has no streaming DOM. M12.md adds a streamed source (HtmlSource.FromChunks), on which memory is flat. For the single-string path it measures retained memory (DOM subtrees released behind the writer) apart from peak memory. The roadmap wording may need this nuance.
    • Outcome: roadmap amended. Verified in M12.md: a streamed source (HtmlSource.FromChunks) holds memory flat; a single string is parsed whole, and only retained memory falls behind the writer. The acceptance row measures both. ADR 39 needs no change: its bound (a page plus the anchor map) holds on both paths.
  • Visual threshold: M11 proposes 'at most 1% of a region at 150 dpi'. M12.md proposes a tighter page-level rule against our own approved images: at most 0.2% of pixels differ, and no 8-connected region larger than 144 px. M25 reuses 'the agreed threshold'. One recorded definition, or two named ones, should be agreed by the maintainer.
    • Outcome: fixed in the milestone files. M25.md already names three thresholds: cross-engine, regression (M12's) and scans. M12.md now names its threshold the regression threshold, recorded under that name beside M11's cross-engine one. What remains is outside my range: M11's and M25's definitions of the cross-engine threshold differ, and M09 uses a threshold before any is fixed (listed in crossFileIssues).
  • ADR 5 credited Skia with 'SVG fallback rendering'. M12.5 draws SVG itself as vectors and rasterizes nothing by default: filters are skipped and reported, and box-shadow is drawn as shadings. Skia only decodes GIF, WebP and BMP. This is consistent with ADR 43, but worth confirming.
    • Outcome: already resolved. ADR 5 allows Skia for image decoding and SVG fallback rendering; it does not require either. ADR 43 amends ADR 5 on other points and says nothing about SVG. M12.md's where-it-lives and SVG sections record the narrower use. Nothing contradicts.
  • Additions beyond the roadmap's M12 deliverables that the maintainer may want to confirm: /PageLabels from the CSS page counter's style (M12.6, because M18 relies on labels), and a vendor prefix -adc- fixed in M12.1. M13.md already relies on that prefix, and it covers carried-forward subtotals (-adc-sum), shrink-to-fit, data-adc-relationship and stamp fields. An ADR recording the -adc- convention may be wanted.
    • Outcome: fixed in the milestone files. M12.md now makes the -adc- ADR a deliverable of M12.1 slice 7, since M12.3, M12.6, M13 and M17 build on the prefix. The vendor-prefix paragraph lists M13's and M17's families. Page labels from the page counter fall within the roadmap's 'counters and counter styles' over M06's page labels, which M18 relies on; they need no decision.
  • Generation-time image downsampling, JPEG quality and a DPI cap stay in M23 per the roadmap, although the survey placed a DPI cap in the HTML engine. M12 only reports images placed far above a useful resolution (image.resolution-excessive).
    • Outcome: already resolved. This matches the roadmap (M23: image downsampling with every lossy step opt-in) and ADR 42 (lossy steps opt-in and reported). M12.md's scope and its image.resolution-excessive diagnostic say so. No inconsistency.
  • ADR 8 names the thread-safe singleton IPdfRenderer, while AdCodicem.Pdf.Rendering (M25) is PDF-to-image rasterization, so the names may confuse users. M12.md keeps ADR 8's name.
    • Outcome: already resolved. M12 keeps ADR 8's name, and M25 calls its engine PdfRasterizer, so IPdfRenderer meets no clash. The one real collision is M25's PdfRenderOptions and PdfRenderReport against M12's PdfRenderOptions (AdCodicem.Pdf.Html); it is reported in crossFileIssues, with PdfRasterOptions and PdfRasterReport suggested.
  • The corpus and manifest are PDF-centric, while M12's inputs are HTML sources, CSS framework fixtures (Bootstrap, normalize.css, Tailwind, MIT) and a web-platform-tests subset (BSD-3-Clause). corpus.md has no rule for vendoring third-party HTML templates or stylesheets (license, provenance, where they live). An ADR 23-like rule, or a tests/corpus/sources/third-party/ convention, is needed before the public-source template gap can be filled.
    • Outcome: docs/corpus.md amended. Verified: corpus.md's layout gives sources/ to our inputs and vendor/ to third-party PDFs only. ADR 23 already allows attribution-only licenses: MIT, BSD-3-Clause (WPT) and CC0 (css-parsing-tests, verified) all qualify. The proposal adds a place and a provenance rule. Whether non-PDF inputs are also indexed is point 51's decision.
  • M12.6 commits our own renderings as documents/{invoice,report,contract}/adcodicem-*-fr.pdf through build_corpus.py. That script is Python and would have to build and call the .NET html2pdf tool, a tooling decision for M12.6 slice 11.
    • Outcome: fixed in the milestone files. M12.6 slice 11 now states the mechanism. build_corpus.py calls the html2pdf verb of the tool built from the same commit through dotnet, with the SDK the session hook installs, as it calls Chromium and LibreOffice, and the manifest records the engine's version as the producer. The Documentation section adds docs/corpus.md for our engine as a generator. CLAUDE.md's generator list is point 55.
  • Facts M12.md relies on that its slices must re-establish rather than trust: Puppeteer's PDFOptions defaults and semantics (the mapping is set by probes against a pinned version); WeasyPrint 70 being on PyPI (per the research file); the license of each hyph-utf8 pattern file (the ADR review); the license of css-parsing-tests; SkiaSharp's lack of AVIF, HEIC and JPEG XL decoding in the pinned build. Chromium's A4 of 594.96 x 841.92 pt, its /Dests with 8 links, the Liberation faces and the page breaks were verified on the committed corpus files.
    • Outcome: fixed in the milestone files. Checked today: PyPI lists WeasyPrint 70.0 as its latest release, and css-parsing-tests' LICENSE is a CC0 dedication, now recorded in M12 slice 2 and NOTICE. Puppeteer's mapping is set by probes against a pinned version (M12.6). The hyph-utf8 licenses go to the slice-5 ADR, with nothing shipped before it. AVIF, HEIC and JPEG XL are refused by M12's design whatever Skia supports. M12's unverified ISO 32000-2 §14.8.2.3 citation is now flagged 'to verify'.

Raised while specifying M13 and M14​

  • M12 (written in parallel) says M13 will tag footnotes Note "and FENote under PDF 2.0" with a Ref; ADR 40 keeps the PDF 2.0 types and attributes for M28, so M13 writes Note plus a Reference in both outputs. M12's footnote paragraph should be aligned.
    • Outcome: fixed in the milestone files. M12.7's footnote paragraph now reads: M13 tags the note Note with its /ID and the call Reference, in 1.7 and 2.0 output alike; FENote and /Ref are PDF 2.0's, left to M28 by ADR 40. This matches M13's notes section and M28's footnote row.
  • M11's print-only layers vs PDF/UA-1 §7.10 (Matterhorn 20-003, no /AS in optional-content configurations): M11 chooses the layer form for tagged or PDF/UA documents without a PDF/A claim; if that form relies on /AS for the print state it breaks PDF/UA-1. M13 records the rule; M11 should use its annotation form under a PDF/UA claim too, as it does under PDF/A.
    • Outcome: fixed in the milestone files. Verified: M11.md's print-only table sends 'Tagged or PDF/UA, no PDF/A claim' to the layer form, which lists /AS entries for View and Print. M13's §7.10 row now makes a configuration carrying /AS a conflict under the PDF/UA-1 target. The M11 side is outside my range (see crossFileIssues). The point's remedy, 'as it does under PDF/A', is inaccurate: under PDF/A, M11 refuses both forms.
  • M03's minimum-version table leaves the associated-files row to M14, but M06 already writes /AF and AFRelationship. M14 states the row only under a PDF/A-3 target (raises nothing, ISO 19005-3 brought /AF into 1.7); what /AF does to the version outside PDF/A should be stated by M06/M03.
    • Outcome: already resolved. M06.md (attachments section) decides that /AF and AFRelationship are written in 1.7 output without raising the version. M14.md states only the PDF/A-3 row and says 'outside PDF/A they are written as M06 decided'. The stale piece is M03.md's 'associated files in M14' (crossFileIssues).
  • Roadmap M15's acceptance repeats 'the Factur-X invoice yields its embedded XML byte-identical', which M14 now accepts on every hybrid; M15 could restate it through its extraction API or drop it. M14 also leaves to M15 the comparison of a received PDF's visible amounts with its XML (the weclapp one-cent gap), which is not among M15's roadmap deliverables — the maintainer should decide whether M15 takes it.
    • Outcome: decided by the maintainer (D8). M15 delivers it, as a slice in AdCodicem.Pdf.FacturX running M14's matcher over M15's search; M15 depends on M14.
  • The architecture table gives AdCodicem.Pdf.FacturX a dependency on AdCodicem.Pdf.Html, so a caller who only extracts or validates ships Skia natives. M14 slice 7 measures it and asks the maintainer to choose one package or two (.FacturX / .FacturX.Html); the architecture table also needs the third-party model dependency (ZUGFeRD-csharp) once its ADR is accepted.
    • Outcome: already resolved. M14.md keeps Container/ and Validation/ free of Html/, measures the cost in slice 7 and records the maintainer's choice between one package and two (where it lives, slice 7, exit criteria). Its Documentation section updates architecture.md with the satellite's dependencies and the packaging decision once the model ADR (slice 10) is accepted.
  • Two new ADRs are required by M14 (the composed EN 16931 model, and the licenses of the ported rule sets — the CEN artifacts are EUPL, XRechnung's Apache-2.0). No number was claimed, to avoid colliding with ADRs other parallel writers may add after 44.
    • Outcome: already resolved. M14.md writes them in slice 8 (licenses) and slice 10 (model), 'at the next free number', and lists both in its Documentation section. Claiming numbers now would collide with parallel writers; the index gets them when they are written.
  • The corpus manifest and schema lack: fields for Factur-X facts (flavor, profile, version, relationship, XML SHA-256), a PDF/UA verdict field beside conformanceValid, and a place for non-PDF fixtures (XML invoices) — the same question M07 (image files) and M10 (payload set) raise; docs/corpus.md must decide.
    • Outcome: docs/corpus.md amended. Verified: claimsConformance is a single string (schema pattern ^PDF/(A-…|UA-[12])$), so a dual PDF/A and PDF/UA claim cannot be recorded. No committed document uses a UA value, and the three PDF/UA claims live only in features whose names are inconsistent (pdfua1-claim on Kraxi, pdfua-1-claim on the InDesign files; worth normalizing at the same time). The non-PDF place is point 51's decision.
  • The Factur-X/ZUGFeRD profile and version table in M14 (Factur-X 1.0.x to 1.09.x, ZUGFeRD 2.1 to 2.5.x, EXTENDED-CTC-FR, the XMP namespaces, recommended AFRelationship per profile, whether MINIMUM/BASIC WL are legal invoices in France) comes from the survey and general knowledge; M14 requires each row verified against FNFE-MPE and FeRD publications before encoding, but the maintainer should be aware it is unverified today.
    • Outcome: already resolved. M14.md says each row is verified against FeRD's and the FNFE-MPE's current publication before it is encoded, with version and date recorded. Slice 7 delivers 'the data table (each row verified and sourced)', and an exit criterion holds it. The maintainer's awareness is served by that text.
  • M13 proposes tagging ON by default in the HTML engine (EAA rationale). That changes M12's committed renderings (they must be regenerated tagged) and possibly M12's performance budgets; the maintainer may want to confirm the default.
    • Outcome: fixed in the milestone files. M13.md already regenerates the committed renderings tagged in the commit that turns tagging on (its first gap row) and lets Tagging = None turn it off. Slice 10 now also re-measures M12's memory and throughput budgets with tagging on and records them beside the untagged ones. The default's rationale was corrected (EAA scope) and backed by the corpus: Chromium 141, invoked by build_corpus.py without a tagging flag, writes tagged PDFs.
  • M13 uses M12's -adc- prefix, PdfRenderOptions.Conformance and the structure sink M12 leaves as a no-op; it also adds PdfRenderOptions.Tagging, which M12's options list does not yet name.
    • Outcome: fixed in the milestone files. M12.md's public API now says PdfRenderOptions grows with later milestones and lists Tagging (M13), the targets through Conformance (M14), M17's four options and M18's ExternalDestinations. The vendor-prefix paragraph names M13's -adc-pdf-* family.
  • CLAUDE.md's settled-decision row D04 still reads 'SkiaSharp and HarfBuzzSharp allowed — in AdCodicem.Pdf.Html only', which ADR 43 amended (Skia also in .Rendering). Not a milestone file, but inconsistent.
    • Outcome: already resolved. CLAUDE.md's D04 row now reads 'SkiaSharp and HarfBuzzSharp allowed — in AdCodicem.Pdf.Html (and Skia in .Rendering, ADR 43), never in the core'.
  • Licenses of schemas and code lists M14 would embed (UN/CEFACT CII XSDs, OASIS UBL XSDs, FNFE-MPE profile XSDs, CEN code lists) are not yet checked; M14 makes a license check a precondition of embedding and falls back to a pinned build-time fetch.
    • Outcome: already resolved. M14.md's validation step 2 checks each schema's license before embedding it, and falls back to a pinned build-time fetch. The Documentation section puts every embedded schema and code list in NOTICE with its terms.
  • PDF /CheckSum on embedded files is MD5, which browser WebAssembly does not provide (M23's WASM target); M14 writes it only on request. M16's password handler (RC4/MD5 key derivation) meets the same limit and should say how it runs in WASM.
    • Outcome: already resolved. M14.md writes /CheckSum only on request, for this reason. M16.md's standard-handler section has slice 1 record the platform matrix, and an ADR chooses between managed implementations and a typed PrimitiveUnavailable refusal, never a crash. What that choice means for M23's WebAssembly acceptance is point 48's decision.

Raised while specifying M15 and M24​

  • ADR 43 places UAX #9 and its generated property tables in AdCodicem.Pdf.Html, but M15 must put right-to-left text back into logical order in the core, so it needs the Bidi_Class table there too (M15 generates it beside M08's Script tables, with case folding and decompositions, rather than using ICU-dependent string.Normalize). Either ADR 43 is amended to say the core holds the generated Unicode tables and .Html consumes them, or the table is generated twice.
    • Outcome: ADR 43 amended (D12). Verified: M15's right-to-left step is reordering by position, 'not UAX #9 itself', so ADR 43 is not contradicted. But its sentence 'from property tables generated … in AdCodicem.Pdf.Html' reads as placing the tables there. M08's generator already emits Script into the core, and M12 and M15 both reuse it. A clarifying bullet avoids generating the table twice from two generators.
  • The predefined CJK CMaps and Adobe CID-to-Unicode maps weigh several MB. M08 deferred them to M15, and the architecture's package table has no place for them. M15 has slice 3 decide the packaging in an ADR (core resource or data satellite), with a stable diagnostic and no silent degradation if they live outside the core.
    • Outcome: fixed in the milestone files. M15.md already has slice 3 decide the packaging in an ADR, with extraction.cmap-unavailable and no silent degradation. Its Documentation section now also adds the data satellite's row to architecture.md §2 if the ADR chooses one.
  • The roadmap lists the visual diff under both M24 ('once M25 exists') and M25 ('the visual diff of M24'), and M24 comes before M25. As written, M24 delivers the pixel-comparison algorithm and the IPageRasterizer seam, proven on MuPDF rasters run in the container, and M25 plugs its rasterizer in and adds the in-process acceptance row. This matches the architecture table, where Compare depends on the core only. The roadmap wording could say so.
    • Outcome: roadmap amended. M24.md delivers the comparison and the seam, proven on MuPDF's rasters; M25.md implements the seam and closes the in-process row. M24.md now places the seam in the core, so .Rendering does not depend on Compare. The proposal replaces the M24 deliverables sentence and the end of M25's. Applied with one change: M24 and M25 both name 'the core's page-raster seam', which M22 puts in the core.
  • M24's roadmap dependency column says M15 only, but its design and acceptance use M04 (revisions), M06, M09 (N-up, stamps), M11 (redline annotations), M14 (XMP), M16 (field values) and M19 (its acceptance edits every corpus document through M19's content pipeline). All of them come earlier in the order of work, so nothing needs reordering, but the table could name M16 and M19.
    • Outcome: roadmap amended. Verified in M24.md: M14's XMP model (MetadataChanged), M16's field values (FieldChanged), and M19's content pipeline, which its acceptance uses through the test support (M19.md says so too). M04, M06, M09 and M11 are reached through M15. All of them precede M24, so nothing moves. With the change, M24.md's header takes the same list.
  • M14 defers 'comparing a received PDF's visible amounts with its XML' to text extraction (M15). M15's roadmap deliverables do not name it, and M15 does not depend on M14. It belongs to the FacturX satellite, on M15's search or M24's templates, and no milestone owns it yet.
    • Outcome: decided by the maintainer (D8). M15 delivers it, as a slice in AdCodicem.Pdf.FacturX running M14's matcher over M15's search; M15 depends on M14.
  • M16 (decryption) comes after M15. The 18 encrypted committed documents cannot be extracted in M15, so the roadmap's 'each corpus document' acceptance cannot hold literally. M15 excepts them, as M11 does, and uses decrypted twins where a row needs one: the vertical Japanese NTA form, and the seventh Type 1 program inside distiller2-irs-9465-1996-rc4-40.pdf.
    • Outcome: roadmap amended. Verified: 18 committed and 5 remote documents carry expect.encrypted. M15.md excepts them, reads decrypted twins where a row needs one, and M16.md's acceptance lifts every exclusion. The roadmap's definition of done says 'no document skipped', so the exception belongs in its wording.
  • The roadmap's M15 acceptance says 'the two-column report, where reading order must be correct'. The only committed two-column report (chromium-report-fr.pdf) is tagged, so it tests the structure tree, not the layout analysis. M15 adds an untagged twin as a priority-1 gap.
    • Outcome: already resolved. Verified: chromium-report-fr.pdf and libreoffice-report-fr.pdf both have /StructTreeRoot and set the annex in two columns. M15.md's reading-order row covers both from the tags and an untagged twin from the layout, and its gap table lists the twin at priority 1.
  • M07 already uses the tool verb 'images' (image files to pages). M15 therefore puts pdfimages-style image extraction under 'inventory --images DIR' and keeps the four verbs the roadmap names.
    • Outcome: already resolved. Verified: M07.md defines images as image files to a PDF. M15.md's tool section uses inventory --images and keeps the four verbs the roadmap names.
  • The Context and Decision text of ADR 15 (0015-full-text-extraction-tagged-structure-preferred.md) is garbled ('We will positioned glyphs, grouping into lines…'). M15 and M24 lean on this ADR more than any other, so its wording (not its decision) is worth repairing.
    • Outcome: ADR 15 amended (D12). Verified: docs/adr/0015-… repeats one noun phrase as Context and as 'We will '. ADR 8 ('We will a thread-safe IPdfRenderer singleton…') and ADR 24 ('We will AdCodicem.Pdf for the core…') carry the same conversion artifact and deserve the same repair. The proposal replaces ADR 15's Context and Decision sections; its Consequences stay.
  • T13 in docs/status.md schedules pdftotext for M15, while M03.md says M03 brings pdftotext, pikepdf, pyHanko and pdfsig in and updates T13. Whichever lands first should close or adjust the row. M15 adds MuPDF, PyMuPDF, pdfplumber, cmark-gfm, xmllint with the ALTO 4 schema, hocr-tools and jsonschema.
    • Outcome: debt table amended. Verified: M03.md's referees section brings veraPDF and pdftotext in with pikepdf, pyHanko and pdfsig and brings T13 in line, and M07.md already uses poppler's pdftoppm, a rasterizer. M15.md claimed to close T13; its Documentation now says 'closed if M03 and M07 have not closed it'.
  • M15 adds three reader guards (MaxContentOperations, MaxPageGlyphs, MaxGraphicsStateDepth), classified under ADR 34. Their defaults must be measured on the remote USGS topo map, which already carries a raised readerLimits for T28: either the defaults let its page through, or its manifest entry gains new readerLimits keys with a recorded reason.
    • Outcome: already resolved. M15.md's bounds table sets MaxContentOperations' default 'from the heaviest corpus page — the remote USGS map — with a margin', and MaxPageGlyphs' 'well above the densest corpus page'. Its stress acceptance row reads the map 'under its readerLimits and the new guards' defaults'. Its entry carries only maxDecodedStreamLength today, which stays.
  • expect.hasExtractableText is per document, but mixed documents exist, such as pdfmaker-acrobat-cerfa-12156-form.pdf with one scanned page. M15 proposes expect.textlessPages, a schema change the manifest owners must accept.
    • Outcome: docs/corpus.md amended. The example is inaccurate: Cerfa 12156's manifest entry has scanned-page-with-live-ocr-text, and M15's OCR row treats that page as an OcrLayer with text, so it is not textless. A real mixed case exists: the remote Konica bizhub scan (features ocr-layer and blank-page, hasExtractableText true). The field is still needed.
  • M24's template acceptance rests on the committed GnuAccounting family: 505 and 506 share one layout, while 502 (2014 labels, US Letter, a spaced IBAN) and 507 (English labels, ISO dates) differ. The roadmap's 'that supplier's other invoices' therefore holds only with label alternatives in the template, which M24 designs and tests both ways. Also, the IBAN and the buyer's SIREN in our own invoice source fail their check digits (fictitious values), and M24's acceptance uses that on purpose.
    • Outcome: already resolved. Verified with pypdf: 505 and 506 share one German layout on A4, 502 is US Letter with 2014 labels, 507 is in English with ISO dates. Also verified: the buyer's SIREN 552 041 319 fails Luhn, the seller's passes, and the IBAN fails modulo 97 (remainder 63). M24.md's template rows handle both layouts with and without alternatives, and use the failing check digits on purpose.

Raised while specifying M16 and M17​

  • Roadmap inconsistency: M17's row says 'Depends on M12, M16', but its acceptance ('passes PDF/UA-1') needs M13's structure writer and PDF/UA-1 target, and M13 explicitly defers Form elements, widgets in the structure and /TU to M17; M13 (and M14 for the PDF/A-3a dual-claim row M17 adds) should be listed as dependencies. M17.md keeps the roadmap's list in its header and says M13 precedes it.
    • Outcome: roadmap amended. Verified: M13 defers Form elements, widgets in the structure and /TU to M17, and M17.md adds a PDF/A-3a and PDF/UA-1 row on M14's target. Both precede M17. With the change, M17.md's header takes the same list; it now equals the current row and notes that M13 precedes it.
  • Roadmap inconsistency: M16 depends on 'M03, M11' only, yet it retrofits the permission guard into M15's extraction (and M06, M07, M09, M11 operations) and lifts the encrypted-document exclusions of M15's acceptance; M15 precedes M16 in execution order but is not a declared dependency. Consider adding M15 to M16's row, or stating the retrofit in the roadmap.
    • Outcome: roadmap amended. Verified: M16's slice 2, permission acceptance rows and exit criteria name M15's operations. M13 is also used when fields are created on a generated tagged document, but no acceptance row needs it, so it is left out. With the change, M16.md's header takes the same list.
  • Roadmap acceptance 'every encrypted corpus document ... matches the unencrypted twin it was derived from' only literally holds for documents/secured/qpdf-invoice-aes256.pdf. Checked with pikepdf while writing: the three PDFMaker 9 AES files and the four OpenOffice RC4-128 files have byte-identical decoded page content to committed unencrypted siblings; the three iBooks files have near-identical separately exported siblings (compared by text); six need derived twins (qpdf --decrypt, priority-1 gap); pdfmaker9-word-distiller-aes128-unknown-open-password.pdf has no recorded password and can only be asserted refused. M16 writes the acceptance that way.
    • Outcome: roadmap amended. M16.md's twin row is already written that way: siblings for the PDFMaker 9, OpenOffice and iBooks files, decrypted twins for six, and a refusal row for the file with an unknown password. The roadmap bullet should say the same. Counts verified against the manifest: 17 committed documents with a recorded password, 11 of them empty, 7 owner-authenticated.
  • ADR 41 says the core reads the ISO/TS 32004 integrity MAC 'with HMAC', but ISO/TS 32004 wraps the MAC in a CMS structure and ADR 41 rejects CMS parsing in the core. M16 slice 3 must decide whether a fixed-profile read with System.Formats.Asn1 is acceptable; otherwise MAC verification moves to AdCodicem.Pdf.Signing (M27) and ADR 41 needs an amendment.
    • Outcome: already resolved. M16.md's MAC section has slice 3 confirm whether the one fixed profile can be read with System.Formats.Asn1, bounded. If not, verification moves to AdCodicem.Pdf.Signing with M27, and ADR 41 is amended; the Documentation ADR list carries that conditional amendment.
  • Invariant 6 versus encryption: R6 needs a random file key and salts and AES-CBC needs unpredictable IVs. M16 derives IVs by HMAC keyed by the file key and requires the caller to name the entropy source (PdfEntropy.System or FromSeed) with no default. This deserves its own ADR (planned for M16 slice 4).
    • Outcome: CLAUDE.md amended (D13). M16.md's writing-encryption section and slice 4 already plan the ADR. Invariant 6 in CLAUDE.md lists the permitted caller-supplied variation as 'creation date, document identifier'. Adding the entropy source keeps a later session from reading the invariant as forbidding R6.
  • Platform primitives: .NET has no RC4; browser WebAssembly (where M23 must run the core) lacks MD5 and possibly AES; AesGcm.IsSupported can be false; SASLprep needs NFKC normalization, which depends on ICU (absent under invariant globalization). An ADR in M16 slice 1 must choose between managed implementations and a typed refusal. This affects M23's acceptance that 'the core runs the same operations in browser WebAssembly'.
    • Outcome: decided by the maintainer (D9). The core carries managed MD5, RC4 and AES (CBC and ECB), tested against RFC 1321, RFC 6229 and NIST SP 800-38A and fuzzed; AES-GCM comes from the platform only and is refused, typed, where the platform has none (ADR 41 amended).
  • M16 makes a cross-cutting policy decision: document permissions are respected by default by every library operation (PdfPermissionPolicy.Respect, with Ignore as a declaration that gets reported). On the corpus this means extraction from the AR-11 and DD 293 is refused by default (no-copy /P −1052). It changes the behavior of M06, M07, M09, M11 and M15 on encrypted inputs and probably deserves an ADR.
    • Outcome: fixed in the milestone files. M16.md's slice 2 and Documentation ADR list now include an ADR: permissions respected by every operation by default, overridden only by a reported declaration (PdfPermissionPolicy.Ignore). Before M16 those operations never saw an encrypted input, since opening one threw, so nothing that works today changes behavior.
  • M16 changes the meaning of the preview option PdfReaderOptions.ThrowOnEncrypted, as ADR 30 allows for a preview: false becomes the 'structure without the key' mode that M04 relies on to compute signature coverage of encrypted documents.
    • Outcome: already resolved. Verified in src/AdCodicem.Pdf: today true throws on any encrypted document and false returns it undecrypted. M16.md keeps false as 'the structure without the key', which is today's behavior, and narrows true to 'throws when no credential opens it'. No stable release has shipped (no tag), so ADR 30 permits the change, as M16 says.
  • Manifest schema work planned by M16 (not done here): expect.security (from qpdf --show-encryption) and expect.form (XFA kind, usage-rights grants, script counts); expect.formFields reshaped from names, which M06 fills, into {name, type, value} records. FDF and XFDF exchange files have no place in the corpus, because the manifest schema accepts only .pdf, so docs/corpus.md needs a decision.
    • Outcome: decided by the maintainer (D3). One manifest. Its file pattern admits the formats a milestone needs, a format field (pdf by default) selects that format's expectations, and every corpus test filters on it. M07, the first milestone that needs it, does it once for M07, M10, M14, M16, M18 and M31.
  • The roadmap lists FDF/XFDF/JSON import and export for fields only, but M11 explicitly defers exchanging annotations as XFDF or FDF to M16; M16.md includes annotations through M11's model. The roadmap wording could say so.
    • Outcome: roadmap amended. Verified: M16.md's exchange section carries annotations through M11's model, and its acceptance has an annotation-exchange row (CorpusFormDataTests.Annotations_travel_in_xfdf_and_fdf).
  • M17 needs M16's generation-side PdfFormBuilder (on M08's PdfDocumentBuilder.Form) to accept caller-supplied appearance XObjects for every field type, per state for buttons, not only for signature placeholders. M16 slice 10 should deliver this; otherwise M17 slice 1 extends it.
    • Outcome: fixed in the milestone files. M16.md's field-creation section and slice 10 now accept a caller's appearances for every type (/N for text and choice fields, one per state for buttons), written as given with /DA, /Q, /MK and /BS still set, and tested. M17.md now relies on M16 slice 10 and says the core gains nothing from M17.
  • No container runs Adobe Reader or Acrobat, so two things are asserted structurally only (coverage, change classes, grants): that usage rights survive an incremental fill within their grants, and that Acrobat re-merges XFA datasets it did not write. The corresponding contributions (W08) are listed as priority-2 gaps.
    • Outcome: already resolved. M16.md states this in its usage-rights and XFA sections and its traps, and lists both documents as priority-2 contributions (W08) in 'What it lacks'.
  • Generating several of M16's gap documents (filled forms, exchange files) uses pdftk-java, and the GCM/MAC fixtures possibly use iText 9 as a producer only. Neither is among the generators CLAUDE.md lists (Chromium, LibreOffice, pypi producers), so the generation container may need them added, or pyHanko if its pinned version writes GCM/MAC.
    • Outcome: CLAUDE.md amended (D13). Verified: CLAUDE.md's development-environment paragraph names Chromium, LibreOffice and Python producers only, while M16 needs pdftk-java and M12 our own engine. Pointing CLAUDE.md at corpus.md keeps the frame short. docs/corpus.md's 'Where the documents come from' table should gain the rows at the same time: pdftk-java for fills and exchange files, iText 9 as a producer only where pyHanko cannot write GCM or MAC files, our engine for its renderings.
  • The ISO/TS 32003 values in M16's handler table (V 6, R 7, /AESV4) and the key derivation of ISO/TS 32004 come from the feature survey and are flagged to be checked against the specifications' text in slice 3. The survey also gives XFDF as ISO 19444-1:2019 in one place and :2016 in another, so M16 cites 'ISO 19444-1 (XFDF 3.0)' without a year.
    • Outcome: already resolved. M16.md's AES-GCM section says the V, R and method values are the survey's and that slice 3 checks every constant against the specifications before writing code. It cites 'ISO 19444-1 (XFDF 3.0)' without a year.
  • M16 adds a save-level usage-rights policy (PdfSaveOptions.UsageRights, default RemoveWhenBroken). M09's stamp.usage-rights-broken and M11's annotate.usage-rights-broken now lead to it, so M09 and M11 saves on Reader-extended documents change behavior once M16 lands. Removing UR3 from a certified document (the remote Canadian IMM 1344) is itself a forbidden change, and is refused.
    • Outcome: already resolved. M09.md already says usage rights are 'reported (stamp.usage-rights-broken), and removed only by M16'. M16.md's usage-rights section names both M09's and M11's codes as leading to the save decision, and refuses removal on the certified IMM 1344.
  • M17 adds three vendor CSS properties (-adc-pdf-field, -adc-pdf-field-scope, -adc-pdf-field-font-size) and render options (FormFields, FormActions, FormFieldFonts, SignatureFields) to M12's PdfRenderOptions and to M12.1's declared CSS level. M12.md's API list and property table do not mention them yet.
    • Outcome: fixed in the milestone files. M12.md's vendor-prefix paragraph now names M17's -adc-pdf-field family, entered in the property table by M17. Its public API lists FormFields, FormActions, FormFieldFonts and SignatureFields among the members later milestones add to PdfRenderOptions.

Raised while specifying M18 and M19​

  • Roadmap dependency columns are incomplete for M18 and M19. M18 lists M07, M09, M12, M15 but also needs M14 (custom XMP schema and associated files under PDF/A; M14.md itself says 'M18 writes custom schemas') and M13 (tagged bordereau), and uses M16 for encrypted pieces. M19 lists M15, M16 but needs M14's XMP model (metadata scrubbing) and M13's structure writer (overlay text as tagged content). The execution order satisfies all of them, but M13 and M14 are not reached transitively through the listed dependencies.
    • Outcome: roadmap amended. Verified by computing the transitive closure of the roadmap table: from M07, M09, M12, M15 neither M13, M14 nor M16 is reached; from M15, M16 neither M13 nor M14. M13 is reached through M14 (M14 depends on M12, M13), so adding M14 suffices for both. M18.md's and M19.md's headers must change with the table (they equal it today). The note below the table was applied reworded ('it need not repeat one reached through another listed there'), since several accepted rows list a milestone they also reach transitively.
  • Roadmap M19 says image pixels are redacted 'once M22 exists', yet M22 comes after M19 and neither M22's roadmap deliverables nor its acceptance mention pixel redaction. M19 defines a PdfImageCodecs seam and refuses marks over DCT/JPX/JBIG2/CCITT images (or removes the image by choice). M22's file should take over the seam, add an acceptance row redacting scans, and decide between a DCT-domain block wipe and a lossless Flate re-encode.
    • Outcome: roadmap amended. M22.md already takes it over (Scope: 'the JPEG block wipe that M19 left'; the 'Pixels under a redaction' table and its ADR in slice 5; acceptance row CorpusImageRedactionTests.Scanned_pixels_under_a_mark_are_removed_and_no_others), and M19 defines the PdfImageCodecs seam M22 completes. Only the roadmap's M22 line lags.
  • M18 needs two things from M12.6 that M12.md may not expose: (1) declared external named destinations (href='#piece-12' resolved by the enclosing M06 assembly rather than an id in the HTML), and (2) a measure-only render (page count without writing), so the bordereau can run ADR 39's two bounded passes at the case-file level, where target-counter() cannot reach PDF parts. M18.md says M18 adds both to .Html if M12.6 has not; the consistency review should decide where they belong.
    • Outcome: fixed in the milestone files. M12.md already reserves ExternalDestinations for M18 (its list of members later milestones add to PdfRenderOptions); it exposes no measure call (the map-only pass is internal to the orchestrator). M18 now owns both: it adds PdfRenderOptions.ExternalDestinations and IPdfRenderer.MeasureAsync (page count and each id's page, nothing written) to AdCodicem.Pdf.Html, in its design, slice 3 and Documentation. M12's list should name MeasureAsync too (crossFileIssues).
  • The roadmap's 'SHA-256 ... for the volume ... in XMP' is self-referential. M18 resolves it with an appended incremental update whose XMP carries the SHA-256 of revision 1, plus a sidecar, with SidecarOnly for non-seekable outputs or portals that dislike incremental updates. This is a design choice for the maintainer to confirm.
    • Outcome: roadmap amended. Not a decision: a file cannot contain its own digest, and M18's appended update (forward-only, no placeholder patched, SidecarOnly for non-seekable outputs) is the only reading that keeps the roadmap's 'in XMP' without breaking invariant 2's forward-only writing. The roadmap's wording should say what is possible.
  • Portal presets (Télérecours, e-Barreau/RPVA, PLEX): M18 deliberately asserts no numeric limit. Encoding is blocked until the current CJA texts (R. 412-2, R. 414-1 and following, R. 414-3), CE 5 Oct 2018 SA Finamur, the CPC (art. 748-1 and following) with the technical orders, and each platform's guide have been read and dated. docs/releasing.md should gain a step to re-verify presets before each stable release. The legal citations in M18.md come from the feature survey and general knowledge and must be checked in their current wording.
    • Outcome: already resolved. M18.md says so where it matters: the goal cites the texts 'as the survey reports them; each is read in its current wording before a preset encodes it'; presets are data whose every value cites a dated source (CasePortalPresetTests fails otherwise), no number is written in the specification, slices 7 and 8 read the texts, and re-verifying before each stable release is part of the release procedure, with docs/releasing.md in M18's Documentation. That step is written when M18 lands, not before.
  • The roadmap says 'EML, MSG optionally'. M18 puts MSG in scope as a slice of its own behind the same EmailMessage, so it can be deferred without touching the rest. The maintainer should confirm whether MSG is required for closing M18.
    • Outcome: fixed in the milestone files. The roadmap already decided ('EML, MSG optionally'); M18's exit criteria contradicted it by requiring MSG. M18 now says it may close without slice 10, the deferral becoming a debt row naming what brings MSG back (a caller whose pieces arrive as Outlook messages); the exit criterion and the note under the acceptance table say so.
  • M18 slice 9 defers to an ADR the choice between in-house MIME/CFB/LZFu/RTF parsers and MimeKit/MsgReader (MIT). architecture.md's package table lists no third-party dependency for AdCodicem.Pdf.CaseFile today, so taking one would amend it.
    • Outcome: already resolved. M18.md 'Where it lives' says taking a library amends the package table, and its Documentation lists 'docs/architecture.md: … any dependency the slice-9 ADR takes'. Both candidates are MIT, and the ADR's bar (AOT and trimming without warnings, deterministic output, limits enforceable from outside) is written.
  • M18 introduces its own CasePieceNumber (hierarchical plus bis/ter suffixes, ordered 12 < 12.1 < 12 bis < 13) because M09's PdfExhibitNumber has no suffix. The consistency review may prefer extending M09's core type instead of keeping two number models.
    • Outcome: fixed in the milestone files. One model chosen: M18 now numbers pieces with M09's PdfExhibitNumber carrying the ordinal suffixes and the order 12 < 12.1 < 12 bis < 13 — the value M09 stamps and M18's reference finder parses, so two orders cannot disagree, and a caller using M09 alone numbers as a case file does. M09's PdfPiece carries a typed number, so M18's former claim that stamps 'take the number as text' did not hold. M09 should declare the suffix (crossFileIssues); if it closes without it, M18's slice 1 adds it to the core type.
  • M19 places the action.* and hidden.* families in a second built-in validation profile, 'disclosure' version 1, rather than in the structural profile, so no existing manifest 'findings' move. This needs a new manifest field, expect.disclosureFindings, written from independent walks (pikepdf, pdfinfo -js, ExifTool, pdfid). The maintainer should confirm this against ADR 36.
    • Outcome: already resolved. Consistent with ADR 36: validation lives in the core, 'callers use the built-in profiles' (plural), a profile has a name and a version, and families other than the structural ones are allowed ('M20's rules take families of their own'). A second built-in profile leaves the structural profile, and so every manifest 'findings' list, unchanged. The field is added with its schema by M19's slice 8, as its Documentation lists.
  • M16's permission table needs two new rows: applying a redaction demands Modify (plus Copy when marks come from search or detectors), and sanitizing demands Modify. M19.md lists the update to M16.md under Documentation; M16.md was not edited.
    • Outcome: fixed in the milestone files. M16's table names the operations of the milestones before it (M06, M07, M09, M11, M15); later milestones state their own demands, as M23 does for Modify. M19 now states its demands 'in the terms of M16's table' instead of adding rows to M16.md, and its Documentation targets the user page where the permission policy is documented, docs/website/docs/guides/encryption.md (M16's), not docs/milestones/M16.md.
  • M09 deferred detecting and removing other tools' watermarks and stamps to M19, but the roadmap's M19 deliverables do not mention it. M19.md includes it as the Watermarks sanitization category (Watermark annotations, Watermark artifacts we did not write, and Acrobat's marks recognized by their page-piece markers).
    • Outcome: roadmap amended. Verified: M09.md line 58 defers '/Artifact /Watermark content we did not write' to M19's sanitization; M19.md has the Watermarks category and an acceptance row (Acrobat's 'Sample' watermark on the OZEV invoice).
  • M15's glyph visibility flags cover render modes, crop box, clip, CoveredByImage and FromAnnotation, but not white-on-white, tiny or covered-by-path text, which M15's own gap table ties to M19. M19 adds a concealment analysis (CoveredByPath, LowContrast, Tiny). The consistency review may prefer moving these flags into M15's glyph model.
    • Outcome: fixed in the milestone files. Kept in M19, where the concealed-text fixtures and the thresholds are (slice 8, priority-1 gap), but M19 now states that CoveredByPath, LowContrast and Tiny join M15's PdfGlyph visibility flags as facts, which extraction may drop by option like the others. M15's own flags already cover crop box and clip (verified in M15 lines 230-236).
  • M19 slice 2 carries an ADR choosing between an in-house Vatti polygon clipper and vendoring Clipper2's C# sources under the Boost Software License 1.0; vendoring adds a NOTICE entry to the core. Slice 3 must check the /Redaction pagination-artifact subtype and the clause numbers cited (ISO 32000-2 §9.3.3, §9.4.4, §14.8.2.2.2) against the standard.
    • Outcome: fixed in the milestone files. Checked against the ISO 32000-2:2020 text: §9.3.3 Word spacing (Tw applies to single-byte code 32 only, never to 32 inside a multi-byte code), §9.4.4 Text space details, and §14.8.2.2.2 Specification of Artifacts, which lists Redaction as a PDF 2.0 Subtype of Pagination artifacts. M19's 'checked in slice 3' hedges removed. The clipper ADR stays slice 2's deliverable.
  • The roadmap's M19 acceptance relies on 'veraPDF's feature report' to find residual active content. Its coverage of every action type is uncertain, so M19 adds pdfid (Didier Stevens) over qpdf's uncompressed rewrite and a pikepdf walk as independent inspectors.
    • Outcome: already resolved. M19.md's integration list names pdfid over qpdf's uncompressed rewrite and pikepdf walks, and the active-content row requires pdfinfo -js, pdfid, pikepdf and veraPDF to agree (PdfidSanitizationRefereeTests).
  • Corpus facts found while writing (checked with pikepdf, which runs locally; no other PDF tool does). The invoice's client SIREN 552 041 319 and its IBAN FR76 3000 4008 2800 0123 4567 890 fail their check digits. The designers' local C:\Users paths sit in the XFA packets of three committed forms, in the XMP of the USCIS I-9, and in Illustrator's AIPDFPrivateData under /PieceInfo in illustrator-irs-pub1-english.pdf. The Chromium report has no outline; only the LibreOffice report does. No committed document has a SubmitForm or ImportData action.
    • Outcome: already resolved. Verified: 552 041 319 fails Luhn and FR76 3000 4008 2800 0123 4567 890 gives 63 mod 97 (not 1), while 123 456 824 passes and its VAT key is 40; C:\Users paths found in the XFA streams of livecycle-irs-1040-2022-xfa-ur3, livecycle-dod-dd293-aes128-xfa and livecycle-es9-cerfa-14880-xfa-form, in the I-9 and in illustrator-irs-pub1-english; a pikepdf walk of the committed corpus finds no SubmitForm or ImportData. M19.md's detector, disclosure and redaction rows already use these facts.

Raised while specifying M20, M21 and M28​

  • Roadmap dependency table: M20's content rules (color used, glyphs drawn, .notdef, render mode 3, q depth) need M15's content interpreter, and its level-a and PDF/UA-1 rules need M15's structure reader. M15 comes before M20 in the order, but the table does not list it, directly or through M14/M13/M12. Suggest adding M15 to M20's 'Depends on'.
    • Outcome: roadmap amended. Verified: M02, M13, M14 do not reach M15. M20.md's header and its 'reported for the roadmap's consistency review' sentence follow the change.
  • Roadmap dependency table: M21 needs M05 (repair runs before conversion), M15 (codes drawn per font, color families), M16 (decryption, form model, XFA and UR3 removal) and M19 (the sanitization pipeline that removes scripts and actions, as ADR 37 requires). None of them is listed, even transitively, and the table gives only M11 and M20. Suggest listing M16 and M19 at least.
    • Outcome: roadmap amended. Verified: from M11 and M20 neither M05, M15, M16 nor M19 is reached; M19 reaches M15 and M16. M21.md's header follows.
  • Roadmap dependency table: M28 needs M17. M17 defers 'fields in PDF 2.0's structure namespace and under PDF/UA-2' to M28, and M28's acceptance uses M17's onboarding pack, but the table lists only M03, M13 and M20.
    • Outcome: roadmap amended. Verified: M03, M13, M20 do not reach M17; M28's slice 5 and acceptance row use M17's onboarding pack. M28.md's header follows.
  • Roadmap M28 names outputs only. The PDF/UA-2 and WTPDF validation profiles (reuse and accessibility) are not assigned anywhere: M20 validates PDF/A-1 to 4 and PDF/UA-1. These files put them in M28 on M20's public API. Suggest updating the roadmap's M28 deliverables and the features.json 'pdf2-conformance' entry to say 'generation and validation profiles'.
    • Outcome: roadmap amended. M20.md (Out: 'PDF/UA-2 and Well-Tagged PDF profiles … M28, on the public API delivered here') and M28.md (scope, slice 9, Ua2DifferentialTests row) agree; the roadmap's M28 line names outputs only. docs/features/features.json's 'pdf2-conformance' name should become 'PDF/UA-2, Well-Tagged PDF and PDF/A-4: generation and validation profiles' when the roadmap changes.
  • Inconsistency in M13: its HTML mapping row for em, strong, b, i, mark, small, sub, sup… says 'PDF 1.7 has no Em, Strong or Sub; M28 adds them'. PDF 2.0's Sub is a sub-division of a block (one line of an address or a poem), not a subscript. M28 keeps HTML sub and sup transparent and maps only em and strong to Em and Strong. M13's note should be reworded.
    • Outcome: already resolved. M28.md maps only em and strong to Em and Strong, keeps sub and sup transparent, and says why in its mapping table and Traps. The remaining fix is M13.md line 218, outside this range (crossFileIssues).
  • ADR 17's Decision sentence is garbled or truncated ('We will when merging, structure trees, OutputIntents, metadata and fonts are genuinely recombined.'). M20 implements what it evidently means (conformance actively preserved through merges); the ADR text needs fixing in a documentation pass.
    • Outcome: ADR 17 amended (D12). Verified: both sections read 'When merging, structure trees, OutputIntents, metadata and fonts are genuinely recombined', the Decision prefixed 'We will'; the text came in with 4bccfe0 (history starts there), so the original D16 wording is not recoverable. The proposal restates D16 as CLAUDE.md lists it and as M20 implements it. ADR 24 shows the same conversion fault (Context and Decision identical, and a list of seven identifiers the architecture table has outgrown).
  • M20 proposes two ADRs 'at the next free number': (1) the public rule engine amending ADR 36 (severity semantics inside conformance profiles, the review outcome, x- caller families); (2) how the PDF/A and PDF/UA rules are sourced (standard text as source; veraPDF's GPL-3.0+/MPL-2.0+ profiles as referee only; use of Matterhorn identifiers). Other milestone files written in parallel also say 'next free number', so the numbers must be assigned in order at consolidation.
    • Outcome: not an issue. No consolidation is needed: an ADR is written when its milestone slice is worked, milestones are worked one after another in their numbered order, and 'the next free number' is resolved at that moment against docs/adr/README.md's index. Pre-assigning numbers now would go stale as soon as an earlier milestone (M03, M05, M08, M12, M16…) writes one of its own.
  • Manifest and schema changes these files rely on, identified and not made: a list-valued claims field with a verdict per claim (PDF/A and PDF/UA); veraPDF's failed (specification, clause, test) sets per flavor, with the veraPDF version; a field recording a disagreement with veraPDF and its reason; a conversion-expectation field for M21 (level reached, or reviewed reasons it cannot be converted). The claim of vendor/verapdf/pdf20-version-mismatch.pdf (its XMP says pdfaid:part 4, rev 2020; the outline says it is veraPDF test 6-1-3-t04-fail-b) is not recorded in the manifest today.
    • Outcome: fixed in the milestone files. Each change belongs to the milestone that reads it, made with its schema and CorpusManifestSchemaTests. M20's slice 2 now explicitly delivers the claims list (each claim with veraPDF's verdict, failed (specification, clause, test) set and version) and the recorded-disagreement field, replacing claimsConformance/conformanceValid; M21's slice 1 and gap row already carry the conversion expectation. Verified that pdf20-version-mismatch.pdf's XMP says pdfaid:part="4" pdfaid:rev="2020" and the manifest records no claim.
  • API naming to settle before a stable release: M14 names the generation option PdfConformanceTarget, and M20 adds PdfConformanceLevel for claims and verdicts. The two could be unified. Separately, the API identifiers here use the PDF specification's spelling (ColorSpace, ColorUse) while the prose uses British English; the project has no stated convention for identifier spelling.
    • Outcome: decided by the maintainer (D4). American English everywhere: public identifiers, package identifiers, CLI verbs, diagnostic codes, manifest keys and prose, rewriting existing preview API where needed (ADR 30 allows it before the first stable release). Applied repository-wide on 2026-09-27.
  • Packaging: M21 wires PDF/A conversion into the tool's 'casefile --pdf-a' verb, not into the AdCodicem.Pdf.CaseFile satellite (M18), to avoid a .CaseFile -> .Conformance package dependency. If M18's court-portal presets are to require PDF/A themselves, the architecture table must accept that dependency.
    • Outcome: fixed in the milestone files. No dependency is needed: M18's portal.pdfa only checks claims, and once M20 exists it can ask the core's IPdfConformanceChecker seam (M20 puts it in the core precisely so that the core and satellites need not reference the Conformance package), registered by the caller through DI. M18's Out section now says conversion goes through the tool's casefile --pdf-a and verification through the core's checker.
  • M21 relies on a caller-supplied CMYK ICC profile for the CMYK branches. Whether the library should ship any CMYK profile, and under which license, is left open; the tests need one vendored (a priority-1 gap).
    • Outcome: fixed in the milestone files. The library ships none: a CMYK intent states a printing condition only the caller knows (M21's trap 'never guessed'). For the tests, M21's gap row now uses M29's approach meanwhile — the U.S. Web Coated (SWOP) v2 profile of the committed EU regulation's output intent (verified: prtr, CMYK, v2.1), read at test time and never copied out, Adobe's terms for committed outputs to verify first — then a redistributable profile (W19).
  • CLI exit codes are proposed per milestone: validate returns 0, 1, 2 or 3 (3 when only review is pending); pdfa convert returns 4 when it converts at a lower level the fallback allows. These should be consolidated with the tool conventions M06 established.
    • Outcome: fixed in the milestone files. They clashed with M06 (3 = unreadable input, 4 = refused by policy). Now: M20's validate uses M06's codes plus 5 (nothing failed, the verdict waits: ConformsPendingReview or Indeterminate); M21's pdfa convert uses 1 not convertible, 4 refused by policy (a certification, a permission) and 6 converted at a lower level the fallback allowed; M27's verify uses 0, 1 (TOTAL-FAILED) and 5 (INDETERMINATE). M06's table should list 5 and 6 (crossFileIssues).
  • Matterhorn Protocol 1.1: M20 names its 31 checkpoints and requires the failure-condition table to be encoded as data and held to the published document. The survey could not fetch pdfa.org (403), and the research's counts (136 conditions, 87 machine, 47 human) were never verified. The table must be obtained and its terms of use checked in slice 2's sourcing ADR.
    • Outcome: fixed in the milestone files. pdfa.org still answers 403 to this environment. M20 claims no counts; it now says the table is obtained from the PDF Association at 1.1 and its terms of use recorded in slice 2's sourcing ADR before a condition is encoded, and that no count is assumed. For reference, the PDF Association's own description of the protocol gives 31 checkpoints and 136 failure conditions, 87 machine-checkable and 47 needing a person — figures published for 1.0, to confirm against 1.1 when the table is read.
  • PDF/UA-2 human conditions: M28 assumes no Matterhorn-equivalent protocol exists for ISO 14289-2 and transcribes its conditions from the standard's text. If the PDF Association has published one, it should replace this list.
    • Outcome: already resolved. M28.md transcribes the conditions from ISO 14289-2 'as far as this milestone knows when written' and replaces them 'by a published protocol if one appears'. A search on 2026-09-27 found none: the Matterhorn Protocol (1.0, 1.1) covers ISO 14289-1 only.
  • Several PDF/A-4 details in M28's requirement table are marked to be re-read against ISO 19005-4 and veraPDF's part-4 profile before encoding: what /Info may contain, whether extension schemas are still required, and whether part-4 base embedded files must be PDF/A. They were not verified against the standard's text while writing.
    • Outcome: fixed in the milestone files. Verified against veraPDF's PDF/A-4 profile (veraPDF-validation-profiles at d513f77, 2026-09-25): no /Info in the trailer unless the catalog has /PieceInfo, and then /ModDate alone (6.1.3 tests 4, 5); plain part 4 embeds only files conforming to ISO 19005-1, -2 or -4 — not part 3 — each with /F, /UF, AFRelationship and a MIME type (6.9 tests 1-4); the part-4 profile checks the identification schema and has no extension-schema rule. M28's rows now say so, ISO 19005-4's text still read at the slice.

Raised while specifying M23 and M29​

  • PDF/X-4 header version: ISO 15930-7 is titled '...using PDF 1.6' and published summaries read it as a 1.6 cap (to verify against the text). ADR 40's writer writes only 1.7 or 2.0 and M03 never lowers a received document's version; if the cap is confirmed, ADR 40 needs an amendment (proposed in M29 slice 5: a Pdf16 output for generated documents only). Decision for the maintainer.
    • Outcome: decided by the maintainer (D11). Decided at the start of M29, in its slice 1: both the PDF/X and PDF/VT referee and whether PDF/X-4 is capped at PDF 1.6.
  • ADR 34 amendment proposed by M23: the piecewise decode keeps MaxDecodedStreamLength as a guard on total decoded length (made a long, Unbounded = long.MaxValue) because a streamed decompression bomb still costs time, whereas ADR 34's 'what would reopen it' anticipated the property bounding memory held instead. Needs the maintainer's confirmation and the ADR at the next free number; docs/architecture.md and the reader-limits page follow.
    • Outcome: roadmap amended. Not a judgment call: invariant 4 forbids a denial of service, and a streamed bomb still costs the time to produce its output, so the length guard must stay; ADR 34's 'what would reopen it' names exactly this event. M23 already writes the amending ADR in slice 4 (and docs/architecture.md and the reader-limits page follow there). What contradicts M23 today is the roadmap's M23 line.
  • Finding while specifying T07: PdfName.Get (src/AdCodicem.Pdf/Objects/PdfName.cs) interns every name into a static, process-wide ConcurrentDictionary that never shrinks, so a server reading hostile files accumulates every distinct name forever; and the object cache is bounded by count only (ObjectCacheCapacity 8192) while an object may reach MaxObjectLength (16 MB). M23 slice 2 bounds both (frozen process-wide table of well-known and Arlington names, per-document table for the rest; a byte weight, ObjectCacheBudget). Proposed change to docs/status.md: extend T07's row with both points, or open a new debt row.
    • Outcome: debt table amended. Verified in the source: PdfName.cs holds a static ConcurrentDictionary<string, PdfName> (GetOrAdd, never removed); PdfReaderOptions.ObjectCacheCapacity is 8192 and PdfReaderLimits.MaxObjectLength 16 MB, so the cache may hold 128 GB. Both are T07's subject; M23 slice 2 closes them.
  • Proposed docs/status.md closures, to be written when M23 closes: T09 by per-PR A/B throughput budgets plus nightly drift check; T22 by memory budgets measured on the AOT binary as a child process; T24/T30 by appending windows (both remote T24 documents lose their unsupported markers, which also unblocks M21's and M27's rows on remote/pdfcpu/acrobat-web-capture8-x509-rsa-sha1-signed.pdf); T28 by DecodeTo/OpenDecoded; T33 by ObjectStreamCacheBudget, classified as an option, not an ADR 34 guard.
    • Outcome: already resolved. M23's exit criteria require T07, T09, T22, T24, T28, T30 and T33 closed in docs/status.md 'each by the behavior its row asked for', its bounds table classifies ObjectStreamCacheBudget as an option, and its acceptance row on the two T24 documents removes their unsupported markers and names the M21 and M27 rows it unblocks. Closures are written when M23 closes, not now.
  • M23 changes a CI policy: benchmarks/README.md says benchmarks never run on push, while M23 runs a budget subset on every pull request (A/B against the base in one job). Consider recording it as an ADR rather than only in the README.
    • Outcome: fixed in the milestone files. The roadmap already requires budgets enforced in CI (M12, M23, T09), but the way — A/B ratio against the base in one job, allocation as tests, nightly drift — is a durable CI policy. M23's slice 1 now delivers an ADR at the next free number recording the change and why the ratio was chosen; Documentation and exit criteria list it.
  • The PDF/X and PDF/VT referee is unresolved by design (M29 slice 1 ADR): no open-source validator is known; a commercial preflight (callas pdfToolbox, Enfocus PitStop, Acrobat Preflight) needs a license usable in CI, or maintainer-run reports committed and bound to the output's SHA-256 (proposed default). Its cost, the vendor's license terms for CI and whether a Linux CLI edition is available are to verify. veraPDF's feature report with our own Schematron policy is only a partial regression check (as an mPDF issue on PDF/X-4 notes).
    • Outcome: decided by the maintainer (D11). Decided at the start of M29, in its slice 1: both the PDF/X and PDF/VT referee and whether PDF/X-4 is capped at PDF 1.6.
  • M25 defines IPdfColorConverter without saying where it lives; the color-management satellite can only implement it without SkiaSharp if it is in the core. Proposed change to M25's 'Where it lives' table (M29 slice 3 moves it otherwise).
    • Outcome: fixed in the milestone files. M25's 'Where it lives' table now places IPdfColorConverter in the core's Graphics/ beside M22's evaluation, and its color section refers to it; M29's conditional paragraph and slice 3's 'moved to the core if needed' now state the fact.
  • Color satellite package identifier proposed as AdCodicem.Pdf.ColorManagement (British spelling in a package ID, unclaimed status to check on nuget.org); docs/architecture.md names the engine without an identifier and ADR 24's list would gain it. Maintainer to decide.
    • Outcome: fixed in the milestone files. No claim check is needed: the AdCodicem. prefix is reserved on nuget.org (ADR 24's status, docs/status.md). M29 now says so, and that the spelling follows the identifier convention, which is the maintainer decision raised under M20's naming point; the identifier enters docs/architecture.md's package table through M29's Documentation (ADR 24's own list is historical and garbled, see the ADR 17 point).
  • Manifest schema changes needed (proposals, not made): claimsConformance's pattern ^PDF/(A-[1-4][abuef]?|UA-[12])$ excludes PDF/X and PDF/VT claims, and there is no field for the print referee's verdict (M29); M23 needs expect fields for pikepdf's distinct decoded streams and duplicate groups, pdffonts' fonts embedded in full and qpdf's unreferenced objects. CorpusManifestSchemaTests and docs/corpus.md follow.
    • Outcome: fixed in the milestone files. Each is its milestone's deliverable: M20's slice 2 now replaces claimsConformance with a list of claims (which M29 extends to PDF/X and PDF/VT and to its referee's verdict, as M29's Documentation lists); M23's gap row adds the optimization expect fields with their schema. One more found: M23's slice 4 now raises readerLimits.maxDecodedStreamLength's schema maximum (2^31−1 today) once the option is a long.
  • Roadmap consistency (proposals for docs/roadmap.md): M29's dependency row lists M20 and M22 only, but the specification also relies on M12 (batch), M14 (Factur-X and sRGB profile), M19 (content-editing pipeline), M23 (named lossy steps, budgets, WebAssembly host, fuzzing campaign) and M25 (color seam, band rendering). M29 also takes separation previews and overprint simulation, which M25 deferred to it but which the roadmap's M29 deliverables do not name. PDF/VT-2 is kept only over PDF/X-4p; VT-2 over PDF/X-5 and VT-2s moved out until a print provider asks.
    • Outcome: roadmap amended. Verified: M12 and M14 are reached through M20; M19, M23 and M25 are not. M25.md defers separations and overprint simulation to M29. M29.md's header follows the table.
  • Many PDF/X-4 and PDF/VT specifics are marked 'to verify' because ISO 15930-7 and ISO 16612-2 are paywalled: fonts used only in render mode 3, the transparency group /CS requirement, optional-content constraints, the exact interactivity and annotation rules, /Alternates and OPI, transfer functions and halftones, the X-4p reference keys, pdfxid as a PDF/A extension schema, the PDF/VT hint keys (GTS_Scope, GTS_Encapsulated), the CIP4-derived DPM vocabulary, and the ISO 32000-2 clause for document parts (cited as 14.12). Also to verify: ICC fallback tables per profile class, ISO 18619's scope beyond relative colorimetric, the CLUT input-count limits per tag type, Ghostscript's and MuPDF's overprint-simulation options, ReportLab's separation/overprint support, Ghostscript's PDF/X output parts.
    • Outcome: fixed in the milestone files. Verified what the ISO 32000-2 text allows: document parts are §14.12 with DPartRoot, DPartRootNode, RecordLevel, NodeNameList, DPart's Parent, DParts (an array of arrays), Start, End (only for a multi-page range) and DPM — M29 now states them without 'to verify'. The rest stays flagged where it is, to be read from the standards at slice 5 (M29: 'written in our own words from the text the maintainer reads').
  • Platform facts to verify in M23: whether the browser-wasm runtime's deflater matches the x64 runtime's (byte equality is only asserted for outputs that were not re-deflated); the name and fontconfig-free status of SkiaSharp.NativeAssets.Linux.NoDependencies and HarfBuzzSharp's Linux assets for the chiseled container; the .NET 10 chiseled runtime-deps tag.
    • Outcome: fixed in the milestone files. Verified: mcr.microsoft.com lists runtime-deps 10.0-noble-chiseled (and 10.0-noble-chiseled-extra, which adds ICU); nuget.org lists SkiaSharp.NativeAssets.Linux.NoDependencies and HarfBuzzSharp.NativeAssets.Linux. M23's container bullet now names them, with an ldd check of HarfBuzzSharp's library in slice 10; the wasm deflater stays 'to verify', compared by decoded bytes meanwhile.
  • Naming choices to confirm: M23's previous draft used PdfOptimizer, PdfOptimizerOptions and PdfOptimizationReport; the rewrite uses PdfOptimizer, PdfOptimizerOptions and PdfOptimizationReport and the tool flag --linearize, consistent with PdfColorSpace and PdfRecognizedPage elsewhere, while diagnostic codes keep 'linearization' as M03's write.linearization-dropped does.
    • Outcome: decided by the maintainer (D4). American English everywhere: public identifiers, package identifiers, CLI verbs, diagnostic codes, manifest keys and prose, rewriting existing preview API where needed (ADR 30 allows it before the first stable release). Applied repository-wide on 2026-09-27.
  • M29's acceptance row on vendor/eu-publications/distiller10-eu-consolidated-regulation-2015.pdf ('the color failures veraPDF reports disappear and nothing else changes') assumes veraPDF's report on that file (conformanceValid false) contains color clauses; check its report before fixing the row's expectation.
    • Outcome: fixed in the milestone files. Ran veraPDF 1.30.2: under 1a it fails 6.2.3.3 test 1 (DeviceRGB under a non-RGB intent, three checks — the three page groups' /CS DeviceRGB; content is sRGB ICCBased) and 6.4 test 3 (a transparency group on each page); under 2a/2b, 6.2.4.3 test 2 and 6.6.4 test 2 (the identification). M29's row now expects 6.2.3.3 to disappear and 6.4 to stay under DeviceOnly, with a new group-blending-space rule; M20's slice 5, M21's goal and rows and M29's corpus text describe the file as veraPDF reads it.
  • The M29 'test output profile' is Adobe's U.S. Web Coated (SWOP) v2, read at test time from the committed EU regulation's output intent and never copied out; whether Adobe's profile license allows its use this way in generated test outputs, and ECI's, the ICC's, iccDEV's, the Ghent Workgroup's and the Altona suite's terms, are to verify before any profile or suite is committed.
    • Outcome: already resolved. M29.md reads the profile at test time and never copies it out, marks every suite and profile 'terms read first' in its gaps, and M21's gap row now does the same. Verified the embedded profile is 'U.S. Web Coated (SWOP) v2', Copyright 2000 Adobe Systems, prtr/CMYK, v2.1. Nothing is committed before the terms are read.

Raised while specifying M30 and M31​

  • Roadmap consistency (docs/roadmap.md, not edited): M31's dependency column says M12 only, but equations (OMML to MathML) and vertical cells (w:textDirection) need M30, and the case-file route uses M18; propose 'M12, M30' (M18 is earlier in the chain anyway).
    • Outcome: roadmap amended. Verified: M12 does not reach M30; M18 (the case-file route) is reached through neither but is earlier in the order and used only by the guide and one acceptance row. M31.md's header follows.
  • Boundary change to the HTML engine made by M31: a new -adc-tab-stops property (inline layout of tab stops with leaders) and a data-adc-annotation attribute (M12's painter emitting an M11 text annotation for DOCX comments), under M12.1's -adc- convention. Propose recording them in M12's declared-level plan or the roadmap so M12 is not surprised.
    • Outcome: already resolved. Later milestones extending the engine is M12's own mechanism (M12.md lists members M13, M14, M17 and M18 add, and the declared level's rows move with each milestone); M31 owns both additions in its design, slice 6 and 9, tests in M12's suites and exit criteria. M12's list should mention them (crossFileIssues).
  • Warichu: M13 (line 62) and M28 (line 58) name 'ruby, warichu and vertical writing' as M30's; M30 now leaves warichu out (no CSS specification lays it out; M13's refusal of a Warichu override stands). Propose correcting M13 and M28 to 'ruby and vertical writing — M30; warichu not planned'.
    • Outcome: fixed in the milestone files. M28.md line 58 now reads 'ruby and vertical writing — M30; warichu — not planned: no CSS specification lays it out (M30)'; M30's out-of-scope line and exit criterion no longer wait on the review. M13 lines 62 and 104 remain (crossFileIssues).
  • Factual error elsewhere: M15 (line 687) and docs/corpus-contributions.md (line 431) say the NTA vertical form is RC4-encrypted; its /Encrypt dictionary is V 4, R 4, /CFM /AESV2 (AES-128), as the manifest and M08 say. Propose correcting both.
    • Outcome: already resolved. Verified with pikepdf: V 4, R 4, /CFM /AESV2, 128-bit, empty user password, as the manifest's encryption-aes128 feature says. docs/corpus-contributions.md's working tree already reads 'AES-128-encrypted under an empty user password' (line 490). M15.md line 687 still says RC4 (crossFileIssues).
  • Maintainer decision needed on non-PDF corpus inputs (shared by M07 images, M10 payloads, M14 XML, M18 e-mails, M31 DOCX); M31 cannot close without it. Also tests/corpus/build/build_word.ps1 must be changed to keep the DOCX and scrub its parts (cp:lastModifiedBy, app.xml Company/Template, w:docVars) — outside the files I may edit.
    • Outcome: decided by the maintainer (D3). One manifest. Its file pattern admits the formats a milestone needs, a format field (pdf by default) selects that format's expectations, and every corpus test filters on it. M07, the first milestone that needs it, does it once for M07, M10, M14, M16, M18 and M31.
  • Proposed new open questions for the roadmap: EMF/WMF/EMF+ pictures to SVG (triggered by the share of corpus DOCX carrying them); rendering Office charts from their XML; DOCX legacy form fields and content controls as AcroForm fields through M17; a converter seam in M18's EmailRenderOptions so DOCX attachments of e-mails become sub-pieces.
    • Outcome: roadmap amended. The fourth needs no open question: M18's EmailRenderOptions now takes attachment converters (media type -> CasePieceSource), so M31's conversion makes a DOCX attachment a sub-piece without CaseFile depending on Docx; M31's case-file route says so. The other three are rows of the roadmap's Open questions table.
  • M08's OFL-set ADR (to be written in M08 slice 10) will need amendments: an OFL math face with a MATH table (M30: STIX Two Math or Noto Sans Math) and Carlito/Caladea as metric-compatible substitutes for Calibri/Cambria (M31); sizes to be measured. Aptos (current Word default) has no known metric-compatible OFL substitute (to verify).
    • Outcome: already resolved. Both are written as deliverables where they belong: M30's slice 8 adds the math face 'by an amendment of M08's ADR with its measured size' and M31's substitution section proposes Carlito and Caladea the same way (NOTICE in its Documentation); Aptos stays 'to verify' in M31. The amendments are made when those slices run.
  • Design decisions taken that the maintainer may want to confirm: (1) M30's MathML carriage defaults to an associated file (as the previous file decided), with StructureElements and Both as options, and a PDF/UA-1 or PDF/A-1/2 1.7 document carries Alt only (never raised to 2.0); PDF/A-4 plain forces structure elements. (2) Footnotes called from vertical flow are placed horizontally and reported, since no referee implements GCPM footnotes in vertical writing. (3) text-combine-upright: digits is left out (no browser implements it). (4) M31 defaults: tracked changes shown as Final but reported as a warning with counts; comments dropped with a warning; hidden text dropped; nothing fetched.
    • Outcome: already resolved. Each is written with its reason and is reversible at its slice: the carriage table follows what each claim admits (PDF/A-1/2 embed no MathML file, part 4 only PDF/A files, 1.7 has no namespaces); vertical footnotes have no referee and are reported; digits has no implementation (MDN); M31's Final view warns with counts, so a draft is never taken for the agreed text in silence. None needs the maintainer before M30 or M31 starts.
  • docs/corpus-contributions.md says M22, M23 and M25 to M31 are not in its gathered table; the M30 and M31 gaps above should be added there when the corpus work is done.
    • Outcome: already resolved. The working tree of docs/corpus-contributions.md now says its tables cover 'every milestone that has a specification, M01 to M31' and holds sections M20 to M31 (another part of this review).
  • Claims flagged 'to verify' in the files: HarfBuzz's default vertical features (vrt2); whether HarfBuzzSharp binds hb_ot_math_*; CSS Writing Modes 3 section numbers (§7.3 orthogonal flows, §9.1 combine compression) and its synthesized-vertical-metrics rule; whether orthogonal flows are monolithic under CSS Fragmentation 3; veraPDF accepting one subset shared by Identity-H and Identity-V fonts; PyMuPDF's wmode key; ISO 32000-2 WritingMode values for vertical-lr and sideways modes and the RubyAlign mapping; Matterhorn checkpoint 17-002; whether PDF/UA-2 demands /Alt when MathML is carried; MathML Core's columnspan/rowspan clamping; MathML 4 intent status; the W3C MathML schema version for lxml; AngleSharp building MathML in its namespace; Open XML SDK trim/AOT cleanliness and Strict support; ECMA-376 font obfuscation clause and byte order; the lastRenderedPageBreak clause; w:ilvl range; Word's spacing/line-height and numbering-instance sharing behaviors; element(..., start) matching Word's header choice; M12.7's page-scoped footnote counter; the license of Office's OMML2MML stylesheet; GOV.UK publishing CCS Schedule 12 as DOCX; TeX Live packaging of LaTeX math tagging; Japan's Standard Terms of Use 2.0 compatibility.
    • Outcome: fixed in the milestone files. Verified and fixed where the sources were at hand: ISO 32000-2 §14.8.5.4.2 lists WritingMode LrTb, RlTb, TbRl, TbLr (sideways-lr has none, decided at the slice); §14.8.5.4.4 gives RubyAlign Start, Center, End, Justify, Distribute and RubyPosition Before, After, Warichu — M30 maps them; §14.8.6.3 only names the MathML namespace, so the 'math only child' rule is now attributed to ISO 14289-2 (Fischer); M12.7 resets the footnote counter per page through counter-reset in @page, so M31's 'to verify' went. The rest stays flagged in place for its slice.
  • Sources used for the external claims written as fact: Chrome 128 line-breakable ruby and ruby-align (developer.chrome.com/blog/line-breakable-ruby); Chrome 132 sideways-rl/lr (Chrome 132 release notes); MathML Core in Chrome 109 (Igalia / Frédéric Wang); text-combine-upright digits unsupported in every browser (MDN); ISO 14289-2 §8.2.5.29.1 and ISO 32000-2 §14.8.6.3 as quoted in Ulrike Fischer, 'Tagging of math with MathML', TUG 2025 slides (also Foxit reads AF, Acrobat reads structure elements; LaTeX adds MathML since 11/2024); OpenSettings.MaxCharactersInPart and MarkupCompatibilityProcessSettings (Microsoft Learn); ZipArchiveEntry.Open truncating to the declared size (Microsoft's ZIP best-practices page); DTD refusal (Open-XML-SDK issue 815); SDK trimming since 2.19 and AOT size reduction in 3.4.1 (NuGet release notes); Carlito and Caladea under OFL 1.1 (Debian/Font Squirrel).
    • Outcome: already resolved. The files cite these sources in place; nothing further is owed. One claim of the same kind was checked in M22 (pdf.js's source: 'No support for cubic spline interpolation', linear as in poppler).