Repository navigation
feat(print): record machine-filled form provenance in the print path (#118) - #123
Merged
Merged
Conversation
A filled application form is a print job more often than it is anything else, and blocky-writer's fill_blocks emits ordinary PDF bytes with no marker saying who wrote the values (#118). Ruling D189 settled it: record machine-filled provenance as metadata in the print path so audit and routing can distinguish it. This does that, on both print paths. Determination (presswerk-document::pdf::form) Reads /AcroForm field values, /NeedAppearances, values with no /AP in their subtree, /DV rewrites, /Producer and /Creator, and an explicit /FillOrigin marker in the document /Info dictionary (or a fillOrigin attribute in an uncompressed XMP packet). Every result carries a confidence and the evidence for it; anything that stops inspection is recorded as "not inspected" rather than guessed at. Recording FormProvenance is stored on every PrintJob, persisted in two new job queue columns (migrated in place for existing databases) and written to the audit trail as form_provenance. Network-received IPP jobs are audited too: the server now takes an audit log at start-up, which it previously had no access to. Routing FormProvenancePolicy defaults to Record, which changes nothing for the user. HoldForReview additionally parks a positively machine-filled job in Held — locally and over IPP — and the jobs page gains a Release action that sends it on, keeping the job's identity and provenance. An undetermined document is never held. Also: a Print-Job response now reports the job's real job-state, so a held job is no longer reported to the client as pending. presswerk-print now depends on presswerk-document. This is a deliberate change to the documented crate graph: a job received over IPP never passes through the application layer, so the server must classify it itself. There is no cycle. Position, marker convention and known limitations are recorded in docs/ecosystem/FORM-PROVENANCE.adoc, including the ask of upstream: two /Fill* entries in /Info turn every downstream determination from Probable into Explicit. Closes #118 Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (21)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
`dtolnay/rust-toolchain@v1` takes a rustup toolchain specification (channel or version). It was being passed `v1`, which is not one, so the step failed on every run — on main as well as on every branch — and Check, Test, Clippy and Format check were all skipped. This repository's CI has not compiled the workspace since that input was written. Separate commit from the feature work so it can be reverted on its own. The action references are unchanged, so actions.lock still matches. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
hyperpolymath
enabled auto-merge (squash)
October 4, 2026 01:37
hyperpolymath
disabled auto-merge
October 4, 2026 01:38
|
Autopilot could not be updated. Open Coding to check access and billing. |
hyperpolymath
added a commit
that referenced
this pull request
Oct 4, 2026
… then fix the check failure (#124) ## Follow-up to #123: make CI actually report the compile error, then fix it #123 was merged, and merging it exposed two things that were previously invisible: 1. `.github/workflows/ci.yml` asked `dtolnay/rust-toolchain@v1` for a toolchain literally named `v1`. The step failed on **every** run — including `main` — before a single `cargo` command ran, so `cargo check`, `cargo test`, `clippy` and `cargo fmt --check` never executed anywhere. Fixed to `stable` (carried in from #123). 2. With the toolchain actually installing, `cargo check --workspace` now **fails with exit code 101**. That is a real compile error in the code merged from #123, and the sandbox this work was produced in has no Rust toolchain and no crates.io access, so CI is the only compiler available. GitHub Actions logs are not readable from that sandbox (the log and blob hosts are unreachable), so the one commit on this branch temporarily replaces the four cargo steps with a single step that tees each command's output and republishes the first error block of each as an `::error` **annotation** — the one channel that is readable. That commit is explicitly temporary and will be reverted here once it has done its job. The follow-up commits on this branch are the actual compile fixes. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this closes
Closes #118 — blocky-writer: a filled application form is a print job — should it be distinguishable?
Ruling D189 (2026-09-30): record machine-filled provenance as metadata in the print path so audit/routing can distinguish it. This implements the ruling on both print paths, and records Presswerk's position in the ecosystem docs.
Position
Yes — and it is the print path's job to record it. Presswerk sits at the last step where the document is still a document: it keeps the audit trail and it routes jobs. So it records provenance on entry, whether or not upstream said anything.
Three commitments the implementation follows:
inspected: false, never as "hand-filled".Record) changes nothing for the user. Only an explicit opt-in policy holds a job, and only on a positive machine determination.And the part the issue cannot answer from here: this repository is the right place for the consuming half, not the authoritative half. Only
blocky-writerknows it filled the form. Until it emits a marker, everything downstream is inference, and the code says so viaProvenanceConfidencerather than pretending otherwise. The ask of upstream is in the docs: two/Fill*entries in/Info, no change to/V,/DVor/AS, turns every downstream determination fromProbableintoExplicit.What changed
Determination — new
crates/presswerk-document/src/pdf/form.rs:/FillOriginmarker (/InfoorfillOriginin raw bytes / uncompressed XMP)Explicit/Produceror/Creatornames a known form-filling toolStrong/NeedAppearancessetProbable/APin the field's subtreeProbable/DVrewritesSpeculativeProbable(human)/AcroFormwith no values / no/AcroFormStrong(empty / not a form)Bounded: 8 MiB inspection budget, 100k-node and depth-32 field-tree walk,
/Offtreated as unselected.Domain types —
FormOrigin,ProvenanceConfidence,FormProvenance,FormProvenancePolicyinpresswerk-core;AppConfig::form_provenance_policy(#[serde(default)], so pre-existingconfig.jsonfiles still load).Persistence —
form_origin(token, filterable in SQL) andform_provenance(full JSON) columns on the job queue, migrated in place, plusJobQueue::get_jobs_with_form_origin.Both print paths —
AppServices::print_document(local) andhandle_print_job(network IPP). The server previously had no access to the audit trail at all, sostart_with_auditwas added;startdelegates to it unchanged.Routing —
HoldForReviewparks a machine-filled job inHeld(locally and over IPP); the jobs page gains aReleaseaction backed byAppServices::release_job, which keeps the job's identity and provenance. A held job's bytes are persisted so it survives until release; documents that print immediately are still never written to disk.UI — provenance badge on the jobs page, plain-language summary for
form_provenanceaudit entries, policy selector in Settings.Bug fixed along the way — a Print-Job response hard-coded
job-state = 3 (pending); it now reports the job's real state, so a held job is reported as4 (held).Architecture note
presswerk-printnow depends onpresswerk-document— a deliberate change to the documented crate graph (README,0-AI-MANIFEST.a2ml). A job received over IPP never passes through the application layer, so the server has to classify it itself. No cycle:presswerk-documentdepends only onpresswerk-core.Verification — please read
I could not run
cargo test,cargo clippyorcargo fmt --checkin the sandbox I worked in. There is no Rust toolchain there and none is installable:static.rust-lang.organdcrates.ioare both unreachable (verified with curl), so neither rustup nor a dependency fetch can complete. This needs a CI run before merge.What I was able to check mechanically:
.rsfiles parsed with the tree-sitter Rust grammar:HEAD=0 WORKTREE=0errors. (The grammar handles this codebase's let-chains andrsx!macros cleanly at HEAD, so the zero is meaningful rather than vacuous.)CREATE_TABLE_SQL,MIGRATE_FORM_PROVENANCE_SQL, the 19-columnINSERTand all threeSELECTs fromqueue.rsand ran them against a real SQLite engine: fresh schema has 19 columns, round-trips both new ones, and a legacy 17-column database with an existing row migrates to 19 columns and reads back as'Unknown'/'{}'— i.e. "not inspected", which is what the new tests assert. Re-running the migration raisesduplicate column name, which the migration loop swallows by design.lopdf0.40.0 API — fetched the crate source from GitHub and checked every symbol I used against it:pub trailer: Dictionary,Dictionary::{get,get_mut,set,iter,new},set<K: Into<Vec<u8>>, V: Into<Object>>,Document::{with_version,add_object,get_object,get_object_mut,catalog,save_to,load_mem},Stream::{new,dict,content},StringFormat::Literal, and theObjectvariant shapes.Cargo.tomlfiles parse as TOML;presswerk-documentis a workspace dependency.PrintJob,SharedStateandFormProvenanceliteral in the tree carries the new fields.50
#[test]functions were added (188 → 238 in the workspace, static count), covering the classifier against real serialised PDFs built with lopdf, the queue round-trip and migration, and the IPP path end-to-end including the audit entries and the hold policy. They have never been executed.Unrelated pre-existing drift I did not touch:
0-AI-MANIFEST.a2mlsays "68 tests expected" for the four library crates; the static#[test]count there was already 187 before this PR.