Landing blocker for #61 and #63: the deployment checkout is 12 commits behind and `merge --ff-only` refuses while tracked paths are dirty. Discard is not available here (workspace policy: no `checkout`/`stash` on shared state), so the dirt is committed. - `agent/APPEND_SYSTEM.md`: the point-of-use / one-work-log / PR-link doctrine, already byte-identical to `origin/main` (`cmp` against the trunk blob: no difference). No new prose — the commit exists so the trunk merge can proceed. - `agent/config.yml` and `agent/managed-skills/**`: the machine-live tier the heartbeat owns (AGENTS.md § Live-sync tiers). `ALLOW_SECRETS=1` was used, deliberately and after auditing the staged diff by hand: the only credential-shaped strings in it are two copies of `AKIA0000000000000EXAMPLE` — the inert all-zero AWS example id — and they appear inside the two skill pages that teach that exact placeholder shape. This checkout is still running the pre-#61 hook; the merged gate (value-scoped placeholder filter, `71601f7`) exempts this value, so the refusal dies with this landing. No `--no-verify` was used: the gate ran and its verdict is recorded here. Refs #61
2.5 KiB
name, description
| name | description |
|---|---|
| pdf-lib-determinism-moddate-leak | Diagnose pdf-lib output that isn't byte-identical across fetches: PDFDocument.load restamps /ModDate; fake-clock determinism test; inflate object streams to diff. |
pdf-lib byte-identical output: the /ModDate clock leak
Symptom
A "reconstruct-on-read, byte-identical forever" PDF endpoint passes its back-to-back refetch test but returns different bytes when fetches cross a wall-clock second. Same-second fetches match; a fetch 1s later differs (often a different stream /Length, e.g. object stream 411→420 bytes).
Root cause
PDFDocument.load() has its own updateMetadata option (defaults true) and stamps /Info /ModDate from the wall clock at load time, not just at save. doc.save({updateMetadata:false}) on its own is not enough (and in pdf-lib 1.17 updateMetadata isn't even in the SaveOptions type — only in PDFOptions on load); passing it to save also fails typecheck.
Fix
const doc = await PDFDocument.load(templateBytes, { updateMetadata: false });
// ... fill, stamp print layer ...
return doc.save(); // save never stamps when load didn't
Output becomes a pure function of (template bytes, values).
Determinism test law
A determinism test MUST straddle a time boundary. Back-to-back fetches/fills are structurally blind to per-second stamping.
- Unit: fake the clock (
@sinonjs/fake-timersorvi.useFakeTimers) → fill → jump +2 months → re-fill →expect(Buffer.from(second)).toEqual(first). No sleeps, no flake window. FakingDateoutside a test framework requires patchingglobalThis.DateBEFORE the module import that uses it. - Prove it red first against the old code (expect the byte/length drift).
Byte-diff forensics (when lengths differ)
- Dump the differing region raw:
Buffer.subarray(idx-60, idx+60)rendered latin1 with non-printables stripped — locate the exact field (D:YYYYMMDDHHmmss= clock leak). - If the diff is inside a compressed object stream, inflate and compare streams:
import { inflateSync } from "node:zlib";
// find /Type /ObjStm ... stream chunks, inflateSync each, string-compare
- pdf-lib escapes text-show operands — inflate + unescape
\\(\\[()\\\\]\\)before substring-asserting printed values.
Live proof recipe
Seed a submission, then fetch both retrieval doors at t, t+1.5s, t+3s; sha256sum all three → one hash. Same-second comparisons prove nothing. And kill any long-running dev service holding the app port first — a live :3000-style stack can hang or false-fail an e2e gate.