diff --git a/.changeset/coding-ideas-intake-safety.md b/.changeset/coding-ideas-intake-safety.md index cc6618d2fb..93ad50efbd 100644 --- a/.changeset/coding-ideas-intake-safety.md +++ b/.changeset/coding-ideas-intake-safety.md @@ -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. diff --git a/AGENTS.md b/AGENTS.md index f56a473da4..b9284a2226 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/packages/core/src/__tests__/run-audit.test.ts b/packages/core/src/__tests__/run-audit.test.ts index 9b093b5a46..2d80330b60 100644 --- a/packages/core/src/__tests__/run-audit.test.ts +++ b/packages/core/src/__tests__/run-audit.test.ts @@ -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); }); diff --git a/packages/core/src/__tests__/transition-pending-recovery.test.ts b/packages/core/src/__tests__/transition-pending-recovery.test.ts index 5c79b37ada..7f07291aaf 100644 --- a/packages/core/src/__tests__/transition-pending-recovery.test.ts +++ b/packages/core/src/__tests__/transition-pending-recovery.test.ts @@ -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; + expect(stamp.pid).toBe(process.pid); + expect(typeof stamp.execPath).toBe("string"); } finally { diskStore.close(); await rmDir(rootDir, { recursive: true, force: true }); diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index 4630455abe..de476f1060 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -2086,6 +2086,38 @@ export class TaskStore extends EventEmitter { }); } + /* + 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