05 — Battle order: phases → lanes
One lane = one agent, one isolated worktree under ~/ on big-1 (TMPDIR=~/tmp; /tmp is
over quota), commits through a temporary index with blobs built from HEAD for shared files,
compare-and-swap on update-ref, HEAD verified to compile from a clean worktree, report
in French with measured numbers, cargo xtask prove <ids> before any implemented. At most
six lanes in parallel. Every lane cites the specification clause it implements and the
oracle it proves against, and adds a negative control.
Phase 0 — Resume what the weekly quota interrupted (⏸ → 🔧)
| Lane | Feature | Where the work is (2026-09-14) | Oracle | Exit |
|---|---|---|---|---|
| AC text | F-001 | ~/ac-work/wt-dev at a6b7d93 (majority-of-three alignment, §7.3.10 generation rule, /Count bound committed); 4 modified, uncommitted: crates/apdf-content/src/{cmap,font,interpret,text}.rs; ~/ac-work/wt-after, wt-base are scratch |
pdftotext, mutool, pdf.js via oracle_pdfjs.rs, external_corpora.rs, oracle_text.rs |
ratios up on all three, 0 regression, KNOWN_GAPS emptied or classified |
| AE modifier | F-002 | ~/wt/ae at a6b7d93; uncommitted: crates/apdf-write/src/edit.rs, crates/apdf/src/edit.rs, crates/apdf/tests/edit_oracle.rs, plus modified pages.rs, revision.rs, document.rs, objects_oracle.rs, support/{mod.rs,qpdf_json_diff.py} |
qpdf, pdfcpu, mutool, pdftk, pyHanko; qpdf --json equality on 200 files after a neutral edit | conformance-processors, prepress-boxes implemented; CLI verbs (pages/merge/split/rotate/boxes/annots/form/meta/attach/bookmarks) |
| AF PDF/A | F-003 | ~/wt-AF at aafc997 (check/a/mod.rs, verapdf_agreement.rs, Cargo.toml modified); ~/wt-AF-g1 (examples/ untracked); ~/wt-AF-g2 at b150945; ~/wt-AF-g3 at 6cfcf51 (syntax.rs modified) — reconcile the four onto HEAD first |
veraPDF 1.30.2 + sources; fontTools as third witness | 0 disagreement on veraPDF corpora or residuals classified; real corpus 3b/4 done |
| AG performance | F-004 (small scale) | ~/wt-AG at f28ff11: tests/tools/bench_corpus.sh, bench_report.py untracked; ~/bench-AG/{before,apdf-before,smoke} baseline runs |
/usr/bin/time, criterion, perf |
docs/PERFORMANCE.md, ADR-benchmarks, ≥3 hot spots fixed with before/after, oracle tests unchanged |
| AH fuzzing | F-005 | ~/wt-AH clean at f28ff11; ~/fuzz-AH/{corpus,extract} seeds prepared; fuzz/ crate not yet written |
libFuzzer/cargo-fuzz, ASan | docs/FUZZING.md, docs/LIMITES.md, 0 open crash |
| AI FDF/XFDF/AF/PDF-X | F-006 | ~/wt-AI clean at 66a4ae4; PDF/X fixtures at ~/tmp-AB/pdfx-nutshell-fixtures/ (copy under ~/ first) |
pdftk, xmllint, qpdf, pdf.js, veraPDF 3b, Ghent | rows updated with proofs |
| P0-a hygiene | F-007 | shared tree normalised 2026-09-14; 34 mixed files remain (git status in the main checkout) — resolve each against the owning lane's worktree, then cargo fmt in one commit, then prove all from a clean worktree; remove dead worktrees (apdf-base, apdf-laneY, wt/ad-pub, wt-AF-g* once reconciled) and stale cargo targets |
prove all |
green gate at new HEAD; docs/COUVERTURE.md regenerated and committed |
| P0-b upstream | F-007 | RUSSL branches fix/cms-interop-and-crl-signing, lane-r-pkcs12-rsa; CODECS laneM-jbig2-2026-09-13, laneY-jpx-2026-09-13, lane F jpeg2000 commits on argon-full-corpus-2026-08-16 — merge into the mainline once the sessions holding those trees are done (🧭 D-7); nothing to republish (Kellnr already carries the built crates) |
upstream suites | branches merged, tags on the published commits |
Phase 0 exit: prove all green at a HEAD that contains AC/AE/AF/AG/AH/AI, formatted tree,
docs/PERFORMANCE.md, docs/FUZZING.md, docs/LIMITES.md present.
State at the pause (2026-09-14, 20:45 — implementation frozen by the founder until 06-decisions.md is answered)
Committed on main between the quota stop and the pause (no lane, main session):
89a7d7e (.gitignore /ISO_SOURCES/), e972e0a (lane AG resumed: bench harness +
docs/PERFORMANCE.md baseline — CSV quoting bug fixed), 56b9277 (COUVERTURE.md
regenerated), 0c5092f (worktree corpus fallback in six tests/support modules +
scripts/worktree-corpus.sh: a linked worktree had an empty tests/corpus and every
oracle suite ran over zero files — this explained the "failures" of AC and AE, not their
code). Shared checkout normalised: stale index reset, 59 stale + 9 rustfmt-churn files
restored from HEAD, 5 AC mirrors restored, 28 unattributed patches parked under
~/mixed-patches-2026-09-15/ (README inside), checkout clean.
Measured but not committed (worktrees kept as they are; resume = review, then commit through a temporary index onto the HEAD of that day):
| Worktree | Content | Measured (2026-09-14) | Resume note |
|---|---|---|---|
~/ac-work/wt-dev (AC, base a6b7d93) |
block/segment reading order in apdf-content/src/text.rs (+ 3 unit tests), form resources merged behind the page's (interpret.rs, unit test), cmap.rs/font.rs small changes; apdf-doc/src/store.rs: declared /Length kept when no keyword follows (bad39.pdf → « Potato », D-25); external_corpora.rs and oracle_text.rs compare --layout with pdftotext -layout and the default order with mutool (D-22) |
with like-for-like conventions: pdf.js in-order ≥0.9 67.9→74.1 %, mutool 64.8→71.0 % (pdf.js sweep); pinned corpus vs pdftotext -layout 85.5→86.0 % (mean 0.909→0.912), vs mutool 73.6→80.7 %; external corpora vs -layout 85.1→85.2 %; "disagreements where the oracles agree" 15→11 |
blockers: robustness.rs case 9 (200 000 (x) Tj) takes 190 s — build_blocks scans all blocks for interleaving (quadratic on hostile input): bound the scan; apdf-content/tests/oracle_text.rs (crate-level) still compares default order with -layout (73.7 % < threshold): align like the CLI tests; unclassified misses issue13211.pdf (Thai text in a form XObject, CID fonts 22/23 with Ghostscript ToUnicode — the CMap parser is exonerated by two scratch tests; the wrong code points U+0E3C/0E61/0E65 come from a later fallback: instrument decode_composite) and bad39.pdf (fixed by the store change, to reclassify); KNOWN_GAPS lists to prune (12 entries fixed); ZapfDingbats.pdf regression (glyph table cells merged) to explain or fix |
~/wt-ae-new (AE rebased on a6b7d93+HEAD) |
apdf-write/src/edit.rs (1 569 lines: page tree edits, annotations, flatten, Touched), apdf/src/edit.rs (1 385 lines: Editor with snapshot-diff incremental save, DocMDP/approval policy, forms, info/XMP, outline, attachments, labels, viewer prefs), apdf/tests/edit_oracle.rs (708 lines), Document::refresh, façade doc merged |
all 6 oracle tests pass (qpdf --pages/--json equality on 200 corpus files, pyHanko signatures, mutool/pdfdetach read-back) once the corpus is found; page-edit candidate pool widened (40→200); clippy: matches! fixed; one warning left — edit_oracle.rs:169 wants .rfind(..) instead of .filter(..).next_back() (~/ae-new-clippy2.log); tests re-run green (~/ae-new-tests2.log: 5 + 6 passed) |
fix the one-line clippy, commit; then conformance-processors/prepress-boxes rows + CLI verbs (not written yet) |
~/wt-AF-new (AF reconciled on HEAD aed2f39) |
encrypted files opened with the empty user password before PDF/A rules (as veraPDF), check/a/syntax.rs: linearised-file /ID rule per GFCosDocument.getlastID, agreement report prints every miss |
veraPDF corpora: 1b 565/569 (was 563), 2b 960/986 (959), 3b 12/12, 4: 455/487 (was 456 — one new false positive: 6-1-3-t02-fail-a.pdf flagged 6.1.6.1-1 by us alone; caused by the linearised-/ID change at level 4 — check before commit), 4e 19/19, 4f 11/11; Isartor 203/205 |
fix or explain the PDF/A-4 −1, then commit; the four old ~/wt-AF* worktrees can be removed after |
~/wt-corpus |
committed (0c5092f) |
— | remove worktree |
| AH, AI | nothing written | — | start from the briefs (docs/roadmap F-005, F-006) |
Also measured today, for D-22: performance baseline (docs/PERFORMANCE.md): open 6 ms vs qpdf 12 / pdfinfo 19 / pdf.js 305 ms; text 6 ms vs pdftotext 20 / mutool 28 / pdf.js 339 ms; render 109 ms vs pdftoppm 53 / mutool 38 ms (2–3× slower, the layer to optimise first); max RSS 898 MB vs 4.9 GB / 6.8 GB.
Second check, same evening (21:20–21:25), a tooling bug and two worktree notes. A round of
oracle/clippy re-checks across the AC/AE/AF worktrees was wrapped in
until ! pgrep -f "<cmd>" >/dev/null; do sleep N; done loops to notify on completion. Bug: each
loop's own process text (passed to zsh -c) contains its own pgrep -f search string, so
pgrep -f matched the wrapper itself forever, even after the real cargo command had long since
exited — all 7 loops were still reported "running" 75+ minutes later. Diagnosed via pgrep -af
showing the wrapper PID as its own match, and via ps/uptime showing no matching cargo
process left and normal system load; killed by exact PID (not a broad pkill, per the shared-box
rule), then the real results were read straight from the log files the loops were pointed at.
Lesson for future polling scripts: never pgrep -f a pattern that is a substring of the wrapper's
own command line — use a PID file or wait $! instead. Findings from the logs:
- AC, in ~/ac-work/wt-dev (still based on a6b7d93, i.e. before 0c5092f): the
apdf-content oracle suite showed a wall of FAILED tests (CFF encodings, CJK CMaps, font
dicts, /SH and ExtGState dicts, fill/stroke counts, text, Type 3, robustness). This is the
pre-0c5092f empty-corpus symptom recurring in a worktree that predates the fix, not a new
defect in this lane's text.rs/store.rs/interpret.rs changes — the numbers in the table
above (measured with the corpus reachable) stand; this worktree needs the corpus-fallback
fix (or scripts/worktree-corpus.sh) before it can be re-measured. The six-crate
cargo check of the fallback itself (~/wt-corpus, already committed as 0c5092f) is clean.
- AE, a further rebase at ~/wt/ae (distinct from ~/wt-ae-new above): reproduced the same
symptom on page_edits_agree_with_qpdf_pages_and_render_identically (1 document / 7 pages
found, needs ≥ 4); pinning APDF_CORPUS at the main worktree's real corpus made all 6 oracle
tests pass, confirming the diagnosis rather than a real regression. Clippy on this rebase also
flags the matches! pattern again, now at edit.rs:1238 — on top of the already-known
edit_oracle.rs:169 (.rfind instead of .filter(..).next_back()) — so whichever of
~/wt-ae-new / ~/wt/ae is carried forward at resume needs both one-line clippy fixes
reapplied before commit.
- AF: cargo test -p apdf-conform --release --no-run compiles clean (isartor, pdfa,
verapdf_agreement test binaries built). This only checks the build; it does not re-run the
suite, so the PDF/A-4 455/487 regression noted above is still open and still unverified either
way by this check.
Phase 1 — Reachability (F-010…F-017)
| Lane | Content | Depends on | Proof |
|---|---|---|---|
| P1-adr | ADR-bindings (cbindgen, napi-rs, wasm-bindgen, wit-bindgen, PyO3/maturin, UniFFI, jni, csbindgen, magnus, ext-php-rs, flutter_rust_bridge, rustler — build-time generators) and ADR-benchmarks (criterion) for founder validation | — | ADR accepted (🧭 D-6) |
| P1-a | C ABI apdf-capi + apdf.h + C test project |
P1-adr | fresh C project; abi-checker |
| P1-b | napi-rs (Node 22, Bun) + WASM (browser) + WASI 0.2 component | P1-a (API shape) | fresh Node/Bun/Deno/browser projects; size table |
| P1-c | Python (maturin wheels), JVM (Maven), .NET (NuGet) | P1-a | fresh projects per language |
| P1-d | Swift/iOS, Kotlin/Android, Go, Ruby, PHP, Dart, Elixir, C++ header | P1-a | fresh projects per language |
| P1-e | pdfium FPDF_* shim, poppler-glib-shaped API, CLI aliases (pdftotext/pdftoppm/pdfinfo/pdfimages/pdffonts/pdfsig/qpdf/pdftk/cpdf), docs/MIGRATION.md |
P1-a | pypdfium2 smoke against our .so; poppler-utils golden outputs |
| P1-f | Documentation site, CI matrix (7 targets), reproducible builds, system packages | P1-b/c | site + green matrix |
Phase 2 — Inspector, linter, forensics (F-020…F-027)
| Lane | Content | Proof |
|---|---|---|
| P2-a | Object graph + views + revision history (apdf tree, apdf revisions) |
qpdf --json, pdfresurrect, pdfsig; negative controls |
| P2-b | Linter + SARIF + suggested fixes + industry profiles (eCTD, court, Factur-X, ZATCA, gov presets) | sarif-multitool, GitHub code scanning, veraPDF/callas cross-checks |
| P2-c | Query language + QDF + apdf json (qpdf-compatible) |
qpdf --qdf, --json=2 |
| P2-d | Diff (structural/textual/visual/revision) | Draftable/pdftk/pdftoppm witnesses |
| P2-e | Forensic mode + apdf scrub + --rebuild (CDR) + hardening report |
peepdf/pdfid, published PoCs, UNSAFE-DOCS |
| P2-f | GUI (egui → WASM), VS Code extension, GitHub Action, browser extension — reuse candidate: UI_VITRAIL_PROJECT (see 08-reuse-candidates.md §D, needs its own ADR) |
Action run on the repo; screenshots |
Phase 3 — Creation and trust (F-030…F-037)
| Lane | Content | Proof |
|---|---|---|
| P3-adr | ADRs: shaping engine, hyphenation, OCR, HTML paged subset | 🧭 D-6/D-9 |
| P3-a | Document builder (shaping, hyphenation, tables, SVG, Markdown, autotag) | HarfBuzz, LuaTeX, veraPDF UA-2/A-4 |
| P3-b | E-invoicing hybrids: Factur-X/ZUGFeRD → XRechnung → ZATCA → Thai → PDF representations with QR (CFDI, DANFE, DIAN, SII, IRN, ATCUD, KSeF, VERI*FACTU, TicketBAI, myDATA) | Mustang, KoSIT, ZATCA SDK, national validators, Peppol Schematron |
| P3-c | C2PA 2.4 | c2patool |
| P3-d | PQ PAdES (RFC 9882, composites) — measure RUSSL availability first; upstream lane if missing | OpenSSL 3.5, Bouncy Castle |
| P3-e | External-signer trait + adapters (PKCS#11, CNG, CTK, SKF, cloud KMS, CSC 2.2, Aadhaar eSign, SingPass), trust stores (LOTL, AATL, national), DSS oracle, signature policies (PAdES-BR, Part 11, e-Apostille, court) — reuse candidates: asklera-signing-server, asklera-trustlist, asklera-tsa-server, asklera-preservation-server (see 08-reuse-candidates.md §E, production-grade and already tested against DSS/OpenSSL TS oracles) |
DSS, SoftHSM2, CSC sandboxes, ITI verifier |
| P3-f | Smartcard layer: PC/SC transport, PKCS#15/IAS-ECC/EN 14890 discovery, card profile modules (PIV/CAC, IAS-ECC/CPS, EstEID, BELPIC, DNIe, CNS/CIE, JPKI, OpenPGP, health-card family) — reuse candidates: russl-pkcs11 + russl-iso7816 (transport still missing), HSM_KEY_Project, deprecated libcps interfaces (see 08-reuse-candidates.md §C, §F) |
OpenSC tools, NIST PIV test cards, vendor test cards |
| P3-g | Forms: designer, XFA rendering (SAP/AEM/IRS/cerfa), JS subset sandbox, pre-fill catalogues | pdfium XFA, Acrobat, pdf.js |
| P3-h | TS 32004 write + /ISO_ declarations (upstream russl-pades), EA-PDF, PDF/raster, PDF Declarations write, ASiC/XAdES containers — reuse candidate: russl-asic already bundles a strict ZIP reader/writer for exactly this (see 08-reuse-candidates.md §C, ADR-zip) |
pyHanko, libdigidocpp, DSS |
Phase 4 — Verticals (F-040…F-047)
Lanes P4-health, P4-prepress, P4-archival, P4-accessibility, P4-extraction, P4-legal-defence,
P4-mrc, P4-barcodes-geo — each with the oracles listed in 03b. Order by market timing:
P4-barcodes-geo and P4-accessibility first (e-invoicing QR dependencies and ADA Title II
deadlines 2026-04/2027-04), then P4-archival and P4-health, then P4-prepress, P4-legal-defence,
P4-mrc, P4-extraction.
Phase 5 — Products (F-050…F-055)
P5-viewer-wasm, P5-viewer-native, P5-server, P5-saas, P5-os-hooks (CUPS, IFilter, QuickLook, add-ins, Moodle), P5-ocr (after ADR), P5-html-paged (after D-9).
Phase 6 — Ecosystem (F-060…F-067), in parallel from the moment Phase 0 exits
P6-observatory, P6-conformance-suite, P6-prefix-ASKL, P6-standardisation (PDF Association, AFNOR/ISO TC 171, ETSI ESI liaison, errata), P6-safedocs, P6-continuous-fuzz, P6-registries (after D-1).
Cross-cutting — performance at every layer
Every lane touching a layer measures before/after with the F-004 harness (criterion + ~50 files) and does not ship a regression above 5 % without explaining it. Layers: codecs (RUSSL), filters, parse/xref, fonts, content/text, render, write, conformance. Small scale first; the full-corpus benchmark only once F-004 is stable.
Lane reporting template
Objective (F-xx, clauses) · what was delivered · numbers against each oracle with negative
controls · defects found in existing code · what was deliberately not done and why · what
remains · founder decisions raised (→ 06-decisions.md) · commits (SHA, temp-index) ·
worktree and scratch locations (never in the repo).