feat: stamp store-open provenance into run-audit

Every TaskStore.init() now records a `store:open` run-audit event with
pid/ppid/execPath/entry/cwd/node version. Motivated by the FN-7910 incident:
a stale pre-fix binary opened the shared fusion.db and evacuated Ideas cards,
and the audit trail (agentId:"system", no PID) could not identify the writer.
Any future mystery mutation is now attributable to the process that opened
the store.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-12 21:57:29 -07:00
parent 1ea185daa5
commit a628a13888
5 changed files with 50 additions and 2 deletions

View File

@@ -4,4 +4,4 @@
summary: Ideas-intake cards no longer auto-process on restart; replan and Retry work from Todo; All-workflows shows every card.
category: fix
dev: Store init now always runs the workflow-aware integrity pass instead of the retired flag-off evacuation (`evacuateCustomColumnsToLegacy` remains toggle-only), with a mis-mapping guard so stale selections are never physically rehomed into auto-triaged lanes; engine replan/stale-spec/fs-validation rebounds resolve `resolveReplanTargetColumn` instead of hardcoding `triage`; `needs-replan` counts as unplanned for hold-release dispatch; triage discovers `needs-replan` todo cards and refinement seed prompts via `isUnplannedSeedPrompt`/`buildRefinementSeedPrompt`; Board's aggregate grouping renders column-orphaned tasks (hidden columns stay hidden) and the FN-7591 refetch also fires on present-but-unrepresentable mappings.
dev: Store init records a `store:open` run-audit provenance stamp (pid/ppid/execPath/entry/cwd/node version) so mystery DB mutations are attributable to their process; init also now always runs the workflow-aware integrity pass instead of the retired flag-off evacuation (`evacuateCustomColumnsToLegacy` remains toggle-only), with a mis-mapping guard so stale selections are never physically rehomed into auto-triaged lanes; engine replan/stale-spec/fs-validation rebounds resolve `resolveReplanTargetColumn` instead of hardcoding `triage`; `needs-replan` counts as unplanned for hold-release dispatch; triage discovers `needs-replan` todo cards and refinement seed prompts via `isUnplannedSeedPrompt`/`buildRefinementSeedPrompt`; Board's aggregate grouping renders column-orphaned tasks (hidden columns stay hidden) and the FN-7591 refetch also fires on present-but-unrepresentable mappings.

View File

@@ -221,6 +221,7 @@ Scoped exception (FN-5819): shared-branch-group members (`branchContext.assignme
### Run Audit
- Store-open provenance: every `TaskStore.init()` emits `store:open` with ids/paths-only metadata (`pid`, `ppid`, `execPath`, `entry`, `cwd`, `nodeVersion`). Purpose: attribute shared-DB mutations to the process that opened the store (the FN-7910 Ideas-evacuation writer was unidentifiable without it). Tests reading unfiltered `runAuditEvents` must filter out `store:open` rather than assert exact counts.
- FN-7158: agent performance reflections emit `reflection:generated`, `reflection:skipped`, and `reflection:failed` with ids/counts/outcomes-only metadata; never persist reflection prose or prompt text in run-audit.
- FN-7528: a deterministic, non-LLM post-task performance capture (`AgentReflectionService.captureTaskPerformance`) runs once per completed task and emits `reflection:captured` with ids/counts/outcomes-only metadata (`retryReworkCount?`, `filesTouchedCount?`, `packagesTouchedCount?`, `verificationFileScoped?`, `durationMs?`); never persists `verificationScopeReason` free-text or summary prose in run-audit.
- FN-7787: `createResolvedAgentSession` enriches `session:runtime-resolved` with `noModelResolved: true` and `runtimeBuiltInFallbackModel` when a non-mock/non-test session reaches runtime creation without a complete provider/model pair; this is a visibility signal for runtime built-in fallback usage, not a fabricated model-resolution verdict.

View File

@@ -274,7 +274,9 @@ describe("Run Audit", () => {
});
it("returns all events when no filters provided", () => {
const events = store.getRunAuditEvents();
// Store init records a `store:open` provenance stamp, so an unfiltered read
// returns the five seeded events plus the stamp(s).
const events = store.getRunAuditEvents().filter((event) => event.mutationType !== "store:open");
expect(events).toHaveLength(5);
});

View File

@@ -215,6 +215,19 @@ describe("#1409 flag ON→OFF evacuation", () => {
diskStore = new TaskStore(rootDir, globalDir);
await diskStore.init();
expect((await diskStore.getTask(task.id)).column).toBe("intake");
// Store-open provenance stamp: every init records which process opened the
// store so mystery mutations (e.g. a stale binary's evacuation) are
// attributable after the fact.
const stampRows = (diskStore as unknown as {
db: { prepare: (s: string) => { all: (...a: unknown[]) => unknown[] } };
}).db
.prepare(`SELECT metadata FROM runAuditEvents WHERE mutationType = 'store:open'`)
.all() as Array<{ metadata: string }>;
expect(stampRows.length).toBeGreaterThanOrEqual(2); // first open + reopen
const stamp = JSON.parse(stampRows[stampRows.length - 1].metadata) as Record<string, unknown>;
expect(stamp.pid).toBe(process.pid);
expect(typeof stamp.execPath).toBe("string");
} finally {
diskStore.close();
await rmDir(rootDir, { recursive: true, force: true });

View File

@@ -2086,6 +2086,38 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
});
}
/*
FNXC:RunAudit 2026-07-13-13:10:
Store-open provenance stamp. Every store open runs the init recovery passes below against
the SHARED project DB, and a store open by a stale binary is how the FN-7910 incident
happened (a pre-fix process's init evacuated Ideas cards; the run-audit row said only
agentId:"system", so the writer could not be identified after the fact). Stamp pid /
parent pid / executable / entry script / cwd / node version — ids/paths only, no prose —
so any future mystery mutation can be attributed to the process that opened the store.
Best-effort: a failed stamp never blocks startup.
*/
try {
this.insertRunAuditEventRow({
agentId: "store",
domain: "database",
mutationType: "store:open",
target: this.rootDir,
metadata: {
pid: process.pid,
ppid: process.ppid,
execPath: process.execPath,
entry: process.argv[1] ?? null,
cwd: process.cwd(),
nodeVersion: process.version,
},
});
} catch (err) {
storeLog.warn("store-open provenance stamp failed during init", {
phase: "init:store-open-stamp",
error: err instanceof Error ? err.message : String(err),
});
}
// U12: workflow-columns integrity pass. Audit + re-home any task whose
// stored column is no longer valid in its resolved workflow (KTD-1
// guarantees zero rewrites for healthy legacy rows, so this is a no-op for