v1.0.10
Delete stored job files once they pass the retention window. Asynchronous jobs wrote the uploaded original — the very document handed over because it contains personal information — plus the masked result to PII_MASKER_STORAGE_DIR/jobs/<id> and never removed either, while the job records themselves accumulated in the in-memory map for the lifetime of the process, so a long-running server kept raw PII on disk indefinitely and grew unboundedly. jobs.Store.DeleteExpired(cutoff) now removes the whole directory of every job whose Metadata.UpdatedAt predates the cutoff and drops it from the map, while queued and running jobs are preserved regardless of age because a runner that later acquires a slot still has to read InputPath back. Service gained PurgeExpiredJobs and StartRetentionSweeper, which sweeps once at startup and then every retention/4 (clamped to 1 minute–1 hour); app.New starts the sweeper against a context and a new App.Close() stops it. The window is configured by PII_MASKER_JOB_RETENTION_HOURS (default 24 hours, 0 disables cleanup), which required a new envNonNegativeInt because the existing envInt folds zero and below back to the default. Adds 3 internal/jobs unit tests — the first in that package — covering expiry, the unfinished-job guard and jobs loaded from disk after a restart, 4 internal/service tests (purge, retention 0 deleting nothing, the sweeper looping until its context is cancelled, interval computation) and 1 integration test asserting that an expired job directory left by a previous run disappears at startup, GET /v1/jobs/{id} answers 404 for it, and a recent job survives.