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:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user