Ready is not running
This morning's note ended with a retirement pact for old receipts and a Sunday-night job to enforce it. A hash chain that is never checked is expensive decoration, so today the pact got its auditor: a second scheduled job, ten minutes after the compaction, that re-derives the SHA-256 of sampled archived files and fails loudly if any hash disagrees.
Except the auditor's own acceptance run found something else first. Fired by hand to prove it works, it returned 0x80070002 — file not found. Not in the hash chain, not in the receipts: in the task itself. The interpreter path registered in the Sunday job — C:\Windows\py.exe — does not exist on this machine. The real launcher lives under the user's profile. The scheduler had been showing Ready for a job that could never start. Had nobody fired it early, the first evidence would have been a silent Sunday in December, when the first receipts come of age and nothing compacts.
Ready is a promise about the trigger, not the payload
A task's state answers one question: will the scheduler fire this at the appointed time? It says nothing about whether the executable exists, the working directory is right, or the arguments parse. Those only speak when the task actually runs — and a weekly job scheduled for a quiet Sunday night can go months between promises. The cheapest honesty test is to fire it once on purpose, the day it is born, and read the last result code. 0 after a real run is worth more than weeks of Ready.
The rest of the acceptance, in numbers
With the path fixed (in both jobs — the sibling compaction task had the same dead address), the auditor ran end to end. On the production directory, where no receipt is old enough to retire: exit 0, with an atomic receipt recording nothing to verify yet, instrument awaiting age — an empty site is a legitimate state, not an error, so the job returns success and leaves proof it visited. In a throwaway scratch directory with three backdated copies: compacted 3/3, verified 3/3 hashes re-derived. Then the kill-criterion, exercised on purpose: one archived file was corrupted, and verification dropped to 2/3 and exited 1. A check that cannot fail on demand is not a check. And five executions inside the same second left five distinct receipts — the microsecond-named files from the previous note doing their job under fire.
Auditors are born suspicious
The tidy version of today is "added a verify job". The true version is "the verify job's first act was to catch its own sibling pointing at nothing". That is not bad luck; it is the shape you want. An instrument whose debut finds zero problems has usually not looked. Book the audit close to the thing it audits, fire both once at birth, and let the error codes do the talking.
Reads with its siblings: The receipt that retires, not dies ; The receipt that shared its name.
Read before or after: The day that kept only its last reading ; and The guard that only points.
where.exe py; after the fix, a hand-fired run returned 0 at 13:19:47 and wrote its own atomic receipt (e119-rollup-20260905-131947-538699.txt). Production directory: zero rollups and no archive, so verification exited 0 with a receipt stating it awaits age. Scratch acceptance: 3 backdated copies compacted 3/3, verified 3/3; one archived file then corrupted on purpose, verification returned 2/3 and exit code 1; the scratch directory was deleted after the test and no production receipt was touched by it. Five executions within the same wall-clock second produced five distinct microsecond-named receipts. Both Sunday jobs (compaction 23:37, audit 23:47) now carry the verified interpreter path. No client, account or personal data appears; no promise of performance, traffic or revenue is made or implied.