Skip to content

spike(pdf): PDF route decision for T8 - #3

Merged
wiggdevin merged 8 commits into
zs/mainfrom
spike/pdf
Sep 4, 2026
Merged

wiggdevin merged 8 commits into
zs/mainfrom
spike/pdf

Conversation

@wiggdevin

@wiggdevin wiggdevin commented Sep 4, 2026

Copy link
Copy Markdown

spike(pdf): PDF route decision for T8

Summary

Throwaway spike per T8. Renders one real Broken English document (an approval
page from brand/render-approval.py, with a table, a code block, a remote
image, and enough text for three pages) through route (b), one Rust
HTML-to-PDF crate (headless_chrome print-to-pdf), and picks that route for
T9. Route (a) — a webview print-to-PDF Tauri command — was disqualified by
reading the pinned wry 0.55.1 source: createPDFWithConfiguration is
#[cfg(target_os = "ios")]-only, so it does not exist on the desktop targets
this app ships. No production code lands from this branch; the deliverable is
the memo at docs/plans/2026-09-04-pdf-route.md plus the throwaway harness
and validator scripts under scripts/pdf-spike/ and scripts/zs/.

Validation per T8: pdftotext extracts the three headings, the table cells,
and the code line; pdftoppm renders all 3 pages without error; size and wall
time are recorded. The offline case points the fixture's remote image at a
local sentinel HTTP server that logs and refuses every request — the sentinel
log shows the attempt and the PDF shows the placeholder. No firewall rules
were changed.

Fork deviations

  • Finding 1 fix: added an explicit "Owner sign-off needed" memo section
    rather than executing route (a) live or building a
    WKWebView.createPDF harness — both are the finding's alternative
    remediation, but building either is itself a scope decision that needs
    Devin's sign-off, the same gap this round is fixing; doing it unasked would
    pre-empt his reject-path choice.
  • Finding 6 fix used the cheaper of the finding's two named options (assert
    each fixture's external-reference count) rather than CDP-level request
    interception, matching the finding's own "or" and this ticket's throwaway-
    tooling scope.

Gates

Fast gates (builder, on the pre-rebase branch):

  • just fmt-check clippy desktop-check desktop-tauri-fmt-check desktop-tauri-clippy file-size-check — pass
  • just desktop-test — pass
  • just desktop-tauri-test — pass
  • scripts/pdf-spike/validate.sh — exit 0
  • scripts/zs/pdf-validate.sh (non-vacuous check: a 1-page PDF fails with 7 FAILs, exit 1) — verified
  • just fmt-check — pass
  • just file-size-check — pass

Rebase and re-verification (PR opener, this pass):

  • git rebase --signoff origin/zs/main — clean, no conflicts
  • just ci on the rebased branch — exit 0
  • Tree clean after the run (git status --porcelain empty)
  • All 6 commits carry a Signed-off-by trailer (one commit carries two,
    from an earlier rebase pass; every commit has at least one)

Tested base OID: 35d4e2c9aa311837365b1708e3e28f870c483c46

Gemini 3.8 Flash tester

{"verdict":"SKIPPED","repro":"docs-only ticket","missing_tests":[],"dirty_files":[],"report_path":""}

Critic

Winner: parity.

Biggest gap: the picked route's only hard dependency is never measured —
it drives a separately-installed Google Chrome, and the memo hands T9 an
unpriced "Chrome may be absent — evaluate the fetch feature" note instead
of a number (no download size, no cold-fetch time, no behaviour on a machine
without Chrome, no bundle cost). It also never raises that Chrome is Blink
while the app's T2 viewer is WebKit, so the exported PDF is rendered by a
different engine than the page the user sees. For a ticket whose whole
deliverable is a route decision, the winning route's decisive shipping cost
is the one number missing.

Checklist: parity true, deviations named true, acceptance met false,
nothing above NIT false.

Notes: reproduced everything from the worktree after
. ./bin/activate-hermit: scripts/pdf-spike/validate.sh exit 0, all 6 PNG
sha256s and both fixture hashes byte-identical to the memo, page count 3,
headings/table cells/code marker extracted, sentinel logged exactly one
refused GET; just fmt-check and just file-size-check pass; all 4 commits
(at review time) signed off; tree clean. Falsified the online gate directly
(img127.0.0.1:1 gives image_state=failed and exactly 110,006 bytes as
claimed) and proved scripts/zs/pdf-validate.sh is non-vacuous (1-page PDF →
7 FAILs, exit 1). Route (a)'s disqualification is verified against pinned
wry 0.55.1 source, including that createPDFWithConfiguration is
#[cfg(target_os = "ios")]-only.

acceptance_met false: the ticket says render through (a) AND (b); route (a)
produced no artifact, no page, no hash, no wall time — it was decided, not
measured, and the fixture is synthetic rather than the real approval page
(both deviations are named in the memo).

nothing_above_nit false: three WARNs open — no route-(a) render; unmeasured
Chrome dependency plus unmentioned Blink/WebKit divergence; and the landed
spike Cargo manifest is Renovate-visible (config:recommended,
automerge: true, cargo manager, no ignorePaths) so it will attract
auto-merging bumps on zs/main forever.

Full reasoning:
/private/tmp/claude-501/-Users-zero-suminc-/a0fab389-7ac7-4f57-88e6-599d452e8e3a/scratchpad/wave1/spike-pdf/critic.md;
blind pair at critic-A (memo) and critic-B (ticket text).

GPT-5.6 Sol audit

Sol ran. Verdict: BLOCK — 3 blockers and 3 warnings, recalibrated by the
driver against the code to 0 BLOCK, 4 WARN, 2 NIT (every BLOCK either
contradicted the ticket text or was already covered at WARN by a real but
non-critical defect).

Report: /private/tmp/claude-501/-Users-zero-suminc-/a0fab389-7ac7-4f57-88e6-599d452e8e3a/scratchpad/wave1/spike-pdf/audit.md

Verified findings:

  1. WARNscripts/pdf-spike/validate.sh:77check_external_reference_count
    uses grep -cE, which counts matching lines, not occurrences.
    Reproduced: a line holding both the sentinel <img> and a second
    https:// reference returns 1, so the offline fixture's expected count of
    1 still PASSes while Chrome (launched with unrestricted networking)
    fetches the extra resource. The memo asserts the property the check lacks
    (docs/plans/2026-09-04-pdf-route.md:452: "A later fixture edit that adds
    any other external reference now fails this check"). No leak exists today
    — all four fixtures verified lines==occurrences. Recalibrated from Sol's
    BLOCK: T8 prescribes the local sentinel and forbids firewall changes, so
    this is a broken static check plus a memo overclaim, not a missing egress
    boundary.
    Fix: count occurrences
    (got=$(grep -oE '...' "$fixture" | wc -l | tr -d ' ')), and add a
    two-references-on-one-line control fixture the check must reject — or
    soften the memo claim to what the check actually enforces.

  2. WARNscripts/pdf-spike/validate.sh:278
    run_offline_negative_control re-implements the image_state comparison
    instead of calling the production gate at validate.sh:196. Loosen :196
    back to != "loaded" and the suite stays green: the offline run reports
    failed (passes the loosened gate) and the control reports missing (its
    own independent branch still PASSes). Nothing else reads image_state for
    offline mode. This is AGENTS.md Review-Proven Rule 3 — a guard whose
    loosening fails no check protects nothing — and both the memo (:437-441)
    and the script comment (validate.sh:270-277) claim the control proves
    the run_offline gate would reject the broken-instrumentation case, which
    it does not.
    Fix: extract one require_offline_image_failure <state> helper, call it
    from run_offline, and have the negative control assert that same
    function rejects missing (add timeout and loaded cases too).

  3. WARNdocs/plans/2026-09-04-pdf-route.md:337 — the memo's three
    "committed" measurement rows and both committed PDF hashes contradict the
    committed evidence log the memo cites. Memo: online 4719/27/195 ms sha
    2a6c997f, offline 3937/21/189 ms sha 6e6e7cd3, negative 3901/65/183 ms.
    scripts/pdf-spike/out/validate.log: 3501/38/176 sha 6c057e5b,
    3384/87/154 sha f721364c, 3400/40/175. Byte counts and all six PNG
    hashes DO match, so the memo was only partially refreshed after a re-run.
    The memo's own PDF-nondeterminism note explains a differing hash but not
    differing timings both labelled "committed". The pick does not depend on
    these numbers; the defect is evidence integrity in the deliverable.
    Fix: regenerate the three committed rows and both PDF sha256 values from
    out/validate.log, or retain and name the separate run artifact that
    produced the memo's numbers.

  4. WARNdocs/plans/2026-09-04-pdf-route.md:8 — landing gate, not a
    code defect: the route pick rests on two scope deviations from T8 the
    owner has not accepted — a synthetic fixture instead of the real
    brand/render-approval.py page (deviation 1), and route (a) never
    executed, disqualified by reading pinned wry 0.55.1 source rather than
    by measurement (deviation 3). The memo discloses both openly ("Owner
    sign-off status: pending" at :8, explicit accept/reject with consequences
    at :477-511), so nothing is hidden and no code edit resolves it. Sol's
    third sub-claim — that the ticket required actual outbound-network
    blocking — is dropped: T8 prescribes the local sentinel and states "No
    firewall rules are changed."
    Fix: do not land this branch or start T9 until Devin's explicit
    accept/reject of deviations 1 and 3 is recorded in the PR body, per the
    implementation plan's loop step 3. Recorded here: pending Devin's
    accept/reject — see this PR's description above for both deviations.

  5. NITscripts/pdf-spike/src/sentinel.rs:83Err(_) => continue in
    the listener.incoming() loop swallows every accept failure with no
    diagnostic, no backoff and no terminal state — the shape Review-Proven
    Rules 1 and 4 name. Downgraded from Sol's WARN because it cannot produce a
    false PASS: the sentinel is killed by cleanup_sentinel after each
    render, so any spin is bounded by one render, and if accept never
    succeeds the log carries no nonce-bearing GET and validate.sh:184 fails
    the run. Failure direction is safe; the diagnostic is poor.
    Fix: log the error and exit nonzero on the first accept failure, or bound
    retries with backoff and a terminal error.

  6. NITscripts/pdf-spike/validate.sh:356
    fixtures/remote-image.png (sha256 c4e870d3) is called "hash-pinned"
    three times in the memo (:81, :268, :272) but nothing in validate.sh or
    scripts/zs/pdf-validate.sh asserts its digest — the digest appears only
    in the memo. A different noisy 800x600 PNG over 300 KB passes
    image_state=loaded, the pdfimages 800x600 check, the 300,000-byte
    floor and the external-reference check while the memo keeps citing the
    original digest. NIT rather than Sol's WARN: the digest is recorded so a
    reviewer can check by hand, and the dimension and size floors bound the
    substitution loosely.
    Fix: assert the exact sha256 of fixtures/remote-image.png before
    rendering (shasum -a 256 -c), plus a control proving a same-dimension
    replacement fails.

Discarded (Sol claims not carried forward):

  • Sol finding 1 sub-claim (branch substituted a sentinel for the "actual
    outbound-network blocking" T8 required) — contradicted by the ticket text:
    T8 says to point the fixture's remote image at a local sentinel HTTP
    server that logs and refuses every request, with no firewall rules
    changed. The branch implements exactly that. The unapproved-deviations
    half of the finding survives as WARN 4.
  • Sol finding 2 sub-claim (offline behaviour must be enforced at the browser
    seam via CDP request interception because a regex check is not an egress
    boundary) — same ticket text: the harness was never asked to be an egress
    boundary, and firewall changes are explicitly forbidden. The underlying
    counting bug (reproduced against a two-URL line) is real and kept as
    WARN 1, at WARN rather than BLOCK.
  • Sol finding 3 (BLOCK severity for the non-binding offline negative
    control) — the defect is real and kept as WARN 2, recalibrated to WARN: no
    production code lands from this throwaway branch, and the committed run
    does exhibit the intended failed/missing split
    (out/validate.log lines 36 and 80). The exposure is a future edit of a
    spike harness plus a false claim in the memo, not a live wrong result.

Test plan

  • scripts/pdf-spike/validate.sh exits 0 against the committed fixture
    set (online, offline, negative-control cases)
  • pdftotext extracts the three headings, the table cells and the code
    line from the route-(b) output
  • pdftoppm renders all 3 pages without error for the route-(b) output
  • Sentinel log shows exactly one refused GET for the offline case; PDF
    shows the placeholder image
  • scripts/zs/pdf-validate.sh proven non-vacuous (1-page PDF input →
    7 FAILs, exit 1)
  • just fmt-check clippy desktop-check desktop-tauri-fmt-check desktop-tauri-clippy file-size-check, just desktop-test,
    just desktop-tauri-test — pass (fast gates)
  • just ci on the rebased branch — pass (exit 0)
  • Route (a) rendered and measured (open — see Critic and Sol WARN 4;
    pending Devin's accept/reject of the memo's deviations)

🤖 Generated with Claude Code

https://claude.ai/code/session_01E51uwemNnQ6wdrBWU9EhPE


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

wiggdevin and others added 8 commits September 4, 2026 06:16
T8 (spike/pdf): standalone, non-workspace Cargo project that renders a
synthetic vendor-approval fixture (three headings, a table, a code block, a
remote image, three pages of body text) to PDF via headless_chrome's
Page.printToPDF, in both a normal and a network-blocked (--offline) mode.
validate.sh runs pdftotext/pdftoppm checks against the output and records
size, wall time, and hashes. Not wired into the app; kept for reproducing
the measurements in docs/plans/2026-09-04-pdf-route.md.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Records the T8 spike measurements: route (a), Tauri/wry webview
print-to-pdf, is disqualified on pinned-dependency evidence (wry 0.55.1's
print() is a modal NSPrintOperation with no bytes-out option, and the only
headless alternative needs unsafe WKWebView FFI this repo's AGENTS.md bars).
Route (b), headless_chrome driving Chrome's Page.printToPDF, passed every
scripted check (3 pages, all headings/table/code-marker text present,
correct offline image-placeholder behavior) in both online and offline
runs. Picks route (b) for T9, and flags that the raw PDF bytes are not
stable across runs (Chrome embeds a timestamp) even though page count,
rendered pixels, and file size are.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
The critic flagged that the offline run had no measured evidence the
renderer actually attempted the fixture's remote image fetch: Chrome was
launched with --proxy-server=127.0.0.1:1 against a live upload.wikimedia.org
URL, so a broken-image placeholder and a byte-count delta were the only
signal, and both are equally consistent with the request never being
issued.

Replace the proxy trick with the ticket's own text: the offline fixture's
remote <img> now points at a local sentinel HTTP server (src/sentinel.rs)
that accepts every connection, logs the peer and request line, and refuses
with 403 before closing. validate.sh starts the sentinel, waits for its
readiness line, runs the offline render, then asserts the log is non-empty
- a measurement, not an inference. No firewall rules are touched.

Also: add scripts/zs/pdf-validate.sh <pdf>, the validator contract the
ticket text names by path and signature (previously only the orchestrator
existed, with no argument); correct the memo's wry print_with_options quote,
which had silently elided the respondsToSelector guard and the margin
setters; and name, rather than leave implicit, the two remaining
deviations from ticket text - the Cargo.lock-dominated diff size against
"no production code lands", and the branch having been cut before plan
revision 3's sentinel-server wording.

Re-ran validate.sh five times after the fix; PDF byte counts, all six
PNG hashes, and the sentinel log line are stable across every run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E51uwemNnQ6wdrBWU9EhPE
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Replace the 800ms flat sleep in render_headless_chrome.rs with a
load/error-event promise awaited in-page before printing. The renderer
now reports the settled image_state (loaded/failed/timeout/missing) and
a separately-measured image_wait_ms, and validate.sh asserts on that
field: online must see loaded, offline must not. Falsified directly by
pointing the online fixture at an unreachable address (reproduces
image_state:"failed" and the offline byte count).

Also: drop the memo's false claim of edits to two other plan docs (the
branch only adds the memo itself), correct hand-authored line counts,
re-measure wall/image-wait/print times and PDF/PNG hashes against the
new harness (six fresh runs, PNG hashes unchanged from the prior
harness), check pdftoppm's exit code explicitly, and pin the
poppler/Chrome versions used for reproduction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E51uwemNnQ6wdrBWU9EhPE
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
…, hash-pinned local image

Addresses Sol audit findings on T8:

- scripts/zs/pdf-validate.sh: the table-cell and code-marker checks were
  not falsifiable (AGENTS.md Review-Proven Rule 3) — the fixture's own
  prose repeated every marker token verbatim, so deleting the table body
  or the code block still left the checks passing on the leftover prose
  occurrence, and only two of the four row tokens were checked at all.
  Fixed the fixtures so each token appears exactly once, in its real
  structural location; the validator now asserts all four row tokens and
  the code block's full comment line, not one bare token each; added
  fixtures/approval-negative.html (page-count-preserving, tokens removed)
  and a run_negative step in validate.sh that proves each affected check
  actually fails against it while unrelated checks (headings, page count)
  still pass.
- scripts/pdf-spike/validate.sh: a function-scoped `trap ... RETURN` never
  fires when `set -e` unwinds the whole shell on an unguarded renderer
  failure, leaking the sentinel process and its listening socket. Hoisted
  the sentinel pid to script scope, installed the cleanup trap on
  EXIT/INT/TERM, and made every renderer invocation's failure explicit
  (`if ! json=$(...)`) instead of relying on set -e to unwind past
  cleanup. Verified the leak is closed with a minimal repro that forces
  an unguarded failure mid-run.
- render_headless_chrome.rs: dropped `.sandbox(false)` (no root process
  here, so it bought nothing) and added `.ignore_certificate_errors(false)`
  explicitly rather than leaving the crate's ignore-TLS-errors default.
  The online fixture's remote <img> now loads a one-time local,
  hash-pinned copy of the reference image instead of fetching a live
  remote host on every run, removing the unverified-TLS surface entirely.
- docs/plans/2026-09-04-pdf-route.md: corrected "does not exist in the
  buzz repository" to "not reachable from this worktree" (the script does
  exist, just outside this worktree's scope) and labeled every
  measurement as fixture-relative; recorded route (a) never having been
  executed as an accepted, disclosed scope deviation carrying the
  verified wry source citation, rather than leaving it implicit; updated
  every measurement, hash, and line count for the fixture and harness
  changes above.

Signed-off-by: wiggdevin
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
… sign-off

Sol audit fix round on the spike/pdf harness.

- scripts/pdf-spike/src/sentinel.rs: bound the per-connection read with a
  2s timeout and a request-line cap, and classify every connection as
  refused (a real, parsed GET)/empty-request/malformed-request/read-error
  instead of discarding the read result. A bare TCP connect with no HTTP
  request previously produced a "refused ... request=" log line that
  satisfied both the non-empty-log and (potentially) a naive request-line
  match; it now logs "empty-request" and cannot be mistaken for proof of a
  fetch.
- scripts/pdf-spike/validate.sh: always rebuilds (drops the
  executability-only guard that could validate a stale binary); each
  offline run bakes a fresh nonce into the sentinel URL and requires a
  well-formed, nonce-bearing GET line in the sentinel log plus
  image_state=="failed" exactly (not "missing"/"timeout"); adds
  run_offline_negative_control, a falsifiability control proving the
  tightened image_state check actually rejects broken instrumentation;
  floors online/offline PDF size per mode as a cheap independent signal;
  asserts each fixture's external-reference count so an "offline" fixture
  that later gains an external resource fails loudly instead of silently
  leaking.
- scripts/zs/pdf-validate.sh: binds the rendered image to the PDF's own
  image XObjects (via pdfimages -list) instead of only the DOM's load
  event, which proved nothing about the printed output — reproduced:
  display:none on the online fixture's <img> still reports
  image_state=="loaded" while the PDF drops from ~491KB to 105,374 bytes
  with zero embedded images. Online/negative PDFs must embed the reference
  image at its native 800x600; offline PDFs must show the small
  broken-image placeholder and have its alt text extracted by pdftotext.
- scripts/pdf-spike/fixtures/approval-offline-noinstrument.html: new
  falsifiability control (approval-offline.html with only class="remote"
  removed from the <img>) used by run_offline_negative_control.
- docs/plans/2026-09-04-pdf-route.md: added an explicit "Owner sign-off
  needed" section — deviations 1 and 3 (fixture-relative measurements;
  route (a) never executed) are the builder's recommendation, not a
  builder-accepted scope change, until Devin signs off in the landing PR
  body. Refreshed measurements, validation-check descriptions, and the
  hand-authored line-count accounting for this round's changes.

All findings from the Sol audit fix-round brief addressed or refuted with
cited evidence in the build log; none discarded without cause.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
@wiggdevin
wiggdevin merged commit f673195 into zs/main Sep 4, 2026
90 of 93 checks passed
@wiggdevin
wiggdevin deleted the spike/pdf branch September 4, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant