NewJobManager (jobs.go:83) unconditionally calls reconcileInterruptedJobs, and NewServiceWithProductLine (service.go:45) eagerly builds the JobManager — including the read-only tools subcommand (make tools, loadServiceForProduct). Any second AniGate invocation sharing the same state_dir as a running server walks jobs/*.json, rewrites every state=running record to failed ("interrupted: anigate process restarted"), and appends a job_finished audit event with reconciled: true — for jobs that are actually still running in the live server.
Two defects: (1) a read-only command mutates state and pollutes the append-only audit stream; (2) there is no process-level guard against two AniGate processes sharing a state_dir.
Fix: move reconcile behind an explicit recovery entry point taken only on the serve path (stdio/http), not on tools/version; and hold a state_dir flock while serving so a second server (or a tools run against a live state dir) cannot clobber it. Ties into the locking primitive proposed for the concurrency work.
NewJobManager(jobs.go:83) unconditionally callsreconcileInterruptedJobs, andNewServiceWithProductLine(service.go:45) eagerly builds the JobManager — including the read-onlytoolssubcommand (make tools,loadServiceForProduct). Any second AniGate invocation sharing the samestate_diras a running server walksjobs/*.json, rewrites everystate=runningrecord tofailed("interrupted: anigate process restarted"), and appends ajob_finishedaudit event withreconciled: true— for jobs that are actually still running in the live server.Two defects: (1) a read-only command mutates state and pollutes the append-only audit stream; (2) there is no process-level guard against two AniGate processes sharing a
state_dir.Fix: move reconcile behind an explicit recovery entry point taken only on the serve path (stdio/http), not on
tools/version; and hold astate_dirflock while serving so a second server (or atoolsrun against a live state dir) cannot clobber it. Ties into the locking primitive proposed for the concurrency work.