Why
Ruling D189 (2026-09-30, scope: repo, applied-unless-struck) answers presswerk#118:
Yes: record machine-filled provenance as metadata in the print path so audit/routing can distinguish it.
A filled application form is a print job, and today the print path cannot tell one that arrived already filled by a machine from one being printed blank to be completed by hand. Nothing in the queue or in the audit trail records which it saw, so neither routing nor audit can act on it afterwards.
The cross-repository contract is now written down as FFP v1.0.0 (Form-Fill Provenance), standards PR #1142:
- spec:
1-formats/sub-specs/form-fill-provenance/ once merged; branch-readable at arena/01a1046c-standards
- the part that binds this repo: FFP/3 — The print path (obligations P1–P7)
- the detection rules: FFP/2 — Detection and classification
- the acceptance oracle:
spec/conformance/ — 14 vectors + a reference probe + a runner
What to build
Classify every document that enters the print path and record the result on the job and in the audit trail. No rendering. No mutation of the document bytes.
Acceptance criteria
- Detector —
presswerk-document::provenance::classify(&[u8]) -> FormProvenance implements FFP/2, never panics, never mutates the input.
- Conformance —
FFP_DETECTOR=<binary> bash 1-formats/sub-specs/form-fill-provenance/spec/conformance/run-conformance.sh from a standards checkout: 14/14, including the negative controls (blank-form-need-appearances → blank-form; viewer-filled → filled-unknown, not "hand"; unreadable → unreadable, never blank-form).
- Recorded with the job — the JSON record (FFP/2 §The record) persists in the job's own metadata, so a later reader can distinguish
machine-filled from machine-filled-suspected from blank-form without re-parsing the document (filled_fields/total_fields and the evidence codes included).
- Recorded in the audit trail — the submission audit entry carries the classification (and evidence), so the trail can answer "did we already know it was machine-filled?" after the print.
- Surfaced (P4) — the jobs list/detail distinguishes
blank-form / machine-filled / machine-filled-suspected without opening the document.
- Blank-print hazard (P5) — when
appearances == incomplete and filled_fields > 0, the job records the hazard and the detail surface shows it. This is a real failure mode: NeedAppearances is deprecated by ISO 32000-2 (PDF 2.0), and viewers/rasterisers that ignore it print those fields empty.
- Network intake — jobs received by the embedded IPP server (and any other inbound path) are classified at receipt, not only local file picks.
- No silent normalisation (P3) — nothing in this change generates appearances, rewrites
/V, or strips NeedAppearances. A repair, if ever offered, is explicit and user-visible.
Integration checklist (this repo)
| File |
Change |
crates/presswerk-core/src/provenance.rs |
new — the FFP record types + serde wire vocabulary (ffp = "1.0") |
crates/presswerk-core/src/lib.rs |
pub mod provenance; + re-export |
crates/presswerk-document/src/provenance.rs |
new — the detector (FFP/2) |
crates/presswerk-document/src/lib.rs |
pub mod provenance; |
crates/presswerk-print/src/queue.rs |
new form_provenance TEXT column + migration (the existing MIGRATE_* style), insert + all three SELECTs + row_to_print_job |
crates/presswerk-app/src/services/app_services.rs |
classify in print_document (PDF only) before insert; put the record in the audit details |
crates/presswerk-app/src/pages/jobs.rs |
badge/label from FormProvenance::label() + hazard marker |
crates/presswerk-print/src/ipp_server.rs |
same classification at receipt for inbound jobs |
crates/presswerk-document/examples/ffp-classify.rs (or a --bin) |
the thin CLI the conformance runner drives: arg → path → prints the canonical line |
A draft of the core half
Posted as a follow-up comment: presswerk-core types + the presswerk-document detector with 8 unit tests covering the vector shapes. It has not been compiled — this was written on a machine with no Rust toolchain and no crates.io access, verified only against lopdf 0.40's source (fetched from the pinned tag) and the idioms already in presswerk-document/src/pdf/reader.rs. Treat it as a draft to make compile and review, not as merge-ready. The parts that matter and are verified are the semantics: the 14 vectors.
Notes for the reviewer
machine-filled-suspected is a first-class outcome, not a fallback: blocky-writer's current output (values + NeedAppearances, no appearance streams) is exactly that shape, and calling it plain machine-filled would be a claim the bytes do not support.
- The detector deliberately has no "hand-filled" output.
/AP appearances can be written by a program as easily as by an editor, so the honest answer for that shape is filled-unknown (FFP/2 §honesty rule).
- Downstream:
blocky-writer gets the producer-side marker as a separate issue; until it lands, its output classifies as machine-filled-suspected.
Ref: hyperpolymath/standards#1142, #118
Why
Ruling D189 (2026-09-30,
scope: repo, applied-unless-struck) answers presswerk#118:A filled application form is a print job, and today the print path cannot tell one that arrived already filled by a machine from one being printed blank to be completed by hand. Nothing in the queue or in the audit trail records which it saw, so neither routing nor audit can act on it afterwards.
The cross-repository contract is now written down as FFP v1.0.0 (Form-Fill Provenance), standards PR #1142:
1-formats/sub-specs/form-fill-provenance/once merged; branch-readable atarena/01a1046c-standardsspec/conformance/— 14 vectors + a reference probe + a runnerWhat to build
Classify every document that enters the print path and record the result on the job and in the audit trail. No rendering. No mutation of the document bytes.
Acceptance criteria
presswerk-document::provenance::classify(&[u8]) -> FormProvenanceimplements FFP/2, never panics, never mutates the input.FFP_DETECTOR=<binary> bash 1-formats/sub-specs/form-fill-provenance/spec/conformance/run-conformance.shfrom a standards checkout: 14/14, including the negative controls (blank-form-need-appearances→blank-form;viewer-filled→filled-unknown, not "hand";unreadable→unreadable, neverblank-form).machine-filledfrommachine-filled-suspectedfromblank-formwithout re-parsing the document (filled_fields/total_fieldsand the evidence codes included).blank-form/machine-filled/machine-filled-suspectedwithout opening the document.appearances == incompleteandfilled_fields > 0, the job records the hazard and the detail surface shows it. This is a real failure mode:NeedAppearancesis deprecated by ISO 32000-2 (PDF 2.0), and viewers/rasterisers that ignore it print those fields empty./V, or stripsNeedAppearances. A repair, if ever offered, is explicit and user-visible.Integration checklist (this repo)
crates/presswerk-core/src/provenance.rsffp="1.0")crates/presswerk-core/src/lib.rspub mod provenance;+ re-exportcrates/presswerk-document/src/provenance.rscrates/presswerk-document/src/lib.rspub mod provenance;crates/presswerk-print/src/queue.rsform_provenance TEXTcolumn + migration (the existingMIGRATE_*style), insert + all three SELECTs +row_to_print_jobcrates/presswerk-app/src/services/app_services.rsprint_document(PDF only) before insert; put the record in the audit detailscrates/presswerk-app/src/pages/jobs.rsFormProvenance::label()+ hazard markercrates/presswerk-print/src/ipp_server.rscrates/presswerk-document/examples/ffp-classify.rs(or a--bin)A draft of the core half
Posted as a follow-up comment:
presswerk-coretypes + thepresswerk-documentdetector with 8 unit tests covering the vector shapes. It has not been compiled — this was written on a machine with no Rust toolchain and no crates.io access, verified only against lopdf 0.40's source (fetched from the pinned tag) and the idioms already inpresswerk-document/src/pdf/reader.rs. Treat it as a draft to make compile and review, not as merge-ready. The parts that matter and are verified are the semantics: the 14 vectors.Notes for the reviewer
machine-filled-suspectedis a first-class outcome, not a fallback: blocky-writer's current output (values +NeedAppearances, no appearance streams) is exactly that shape, and calling it plainmachine-filledwould be a claim the bytes do not support./APappearances can be written by a program as easily as by an editor, so the honest answer for that shape isfilled-unknown(FFP/2 §honesty rule).blocky-writergets the producer-side marker as a separate issue; until it lands, its output classifies asmachine-filled-suspected.Ref: hyperpolymath/standards#1142, #118