v1.0.19
Stop restarts from extending job file retention. jobs.Store.load() turned every job left queued or running by a hard kill into a job_interrupted failure, but it raised UpdatedAt to the current time while doing so and never wrote that transition back to disk, so the next start read the original state from disk again and pushed the timestamp forward once more. Because the retention window (PII_MASKER_JOB_RETENTION_HOURS, 24 hours by default) is measured from UpdatedAt, a server that restarts more often than the retention window never deleted the uploads behind interrupted jobs — the original documents users handed over precisely so their personal data would be masked stayed on disk indefinitely. The interrupted marking now keeps the time the job actually last changed, and the transition is written to disk once through persistLocked (a failed write is harmless — the next start simply repeats the same idempotent marking), so it no longer repeats on every boot. Since load() fills the job map directly, it now takes s.mu explicitly.
Verified with two new internal/jobs tests — a running job written 48 hours ago keeps its timestamp across a reload and is actually removed by DeleteExpired(now-24h), and the job.json on disk after a reload is failed with job_interrupted and an unchanged timestamp — plus gofmt -l (no output), go vet ./..., go build ./..., go test -count=1 ./... and go test -race -count=3 ./.... Both new tests were confirmed to fail against the previous behaviour (timestamp bumped, nothing persisted). The shutdown section of the README now notes that retention deadlines are not pushed back.