FN-5695: sync feature assertions for late-added mission features

Close the assertion-graph gap by making mission feature assertions store-managed and automatically synchronized.

- add `sourceFeatureId` to contract assertions (types, schema migration, persistence, snapshot restore)
- auto-create/update/delete managed assertions when features are added, edited, or removed in `MissionStore`
- remove duplicate/manual assertion creation from mission routes and rely on centralized store behavior
- expand core, dashboard, engine, and plugin tests plus docs to cover validator behavior for later-added features

Files changed:
 docs/missions.md                                   | 11 ++-
 docs/storage.md                                    |  2 +-
 packages/core/src/__tests__/db-migrate.test.ts     | 12 ++--
 packages/core/src/__tests__/db.test.ts             | 34 ++++-----
 packages/core/src/__tests__/goals-schema.test.ts   |  2 +-
 packages/core/src/__tests__/insight-store.test.ts  | 10 +--
 packages/core/src/__tests__/mission-store.test.ts  | 82 +++++++++++++++++++---
 packages/core/src/__tests__/run-audit.test.ts      |  2 +-
 packages/core/src/__tests__/secrets-schema.test.ts |  6 +-
 .../core/src/__tests__/store-merge-queue.test.ts   |  2 +-
 packages/core/src/__tests__/task-documents.test.ts |  2 +-
 packages/core/src/db.ts                            | 11 ++-
 packages/core/src/mission-store.ts                 | 69 ++++++++++++++++--
 packages/core/src/mission-types.ts                 |  4 ++
 .../dashboard/src/__tests__/mission-e2e.test.ts    | 70 ++++++++++++------
 packages/dashboard/src/mission-routes.ts           | 16 +----
 .../src/__tests__/mission-execution-loop.test.ts   | 46 +++++++++++-
 .../src/store/__tests__/roadmap-store.test.ts      |  4 +-
 18 files changed, 288 insertions(+), 97 deletions(-)

Fusion-Task-Id: FN-5695
Fusion-Task-Lineage: 337f030c-e883-41aa-b982-13ebe45bd5ee
This commit is contained in:
gsxdsm
2026-05-29 13:19:51 -07:00
parent c5d03ae9de
commit bac12d1e28
18 changed files with 288 additions and 97 deletions

View File

@@ -134,6 +134,7 @@ function createMockValidatorRun(overrides: Partial<MissionValidatorRun> = {}): M
function createMockMissionStore() {
const missions = new Map<string, Mission>();
const features = new Map<string, MissionFeature>();
const assertionsByFeature = new Map<string, Array<{ id: string; milestoneId: string; title: string; assertion: string; status: "pending" | "passed" | "failed" | "blocked"; orderIndex: number; createdAt: string; updatedAt: string; sourceFeatureId?: string }>>();
const validatorRuns = new Map<string, MissionValidatorRun>();
const store = {
@@ -180,8 +181,8 @@ function createMockMissionStore() {
features.set(id, updated);
return updated;
}),
listAssertionsForFeature: vi.fn(() => []),
getAssertionsForFeature: vi.fn(() => []),
listAssertionsForFeature: vi.fn((featureId: string) => assertionsByFeature.get(featureId) ?? []),
getAssertionsForFeature: vi.fn((featureId: string) => assertionsByFeature.get(featureId) ?? []),
getSlice: vi.fn((id: string) => {
// Return a mock slice with milestoneId for the hierarchy
return createMockSlice({ id });
@@ -254,10 +255,26 @@ function createMockMissionStore() {
// Internal setters for test setup
_setMission: (m: Mission) => missions.set(m.id, m),
_setFeature: (f: MissionFeature) => features.set(f.id, f),
_addFeatureWithManagedAssertion: (f: MissionFeature) => {
features.set(f.id, f);
const now = new Date().toISOString();
assertionsByFeature.set(f.id, [{
id: `CA-${f.id}`,
milestoneId: "MS-001",
title: f.title,
assertion: f.acceptanceCriteria || f.description || `Verify implementation of: ${f.title}`,
status: "pending",
orderIndex: 0,
createdAt: now,
updatedAt: now,
sourceFeatureId: f.id,
}]);
},
_getValidatorRun: (id: string) => validatorRuns.get(id),
_clear: () => {
missions.clear();
features.clear();
assertionsByFeature.clear();
validatorRuns.clear();
},
};
@@ -490,6 +507,31 @@ describe("MissionExecutionLoop", () => {
expectNoValidationBoardTaskMutation(taskStore);
});
it("uses validator path for later-added feature with managed assertion", async () => {
const feature = createMockFeature({
id: "F-LATER",
loopState: "implementing",
taskId: "FN-LATER",
title: "Later Feature",
acceptanceCriteria: "Later criteria",
});
(missionStore as any)._addFeatureWithManagedAssertion(feature);
taskStore._setTask({ id: "FN-LATER", title: "Later task", description: "done", log: [] });
missionStore.getFeatureByTaskId = vi.fn().mockReturnValue(feature);
loop = new MissionExecutionLoop({
taskStore: taskStore as any,
missionStore: missionStore as any,
rootDir: "/tmp",
});
loop.start();
await loop.processTaskOutcome("FN-LATER");
expect(missionStore.listAssertionsForFeature).toHaveBeenCalledWith("F-LATER");
expect(missionStore.startValidatorRun).toHaveBeenCalledWith("F-LATER", "task_completion");
});
it("does NOT create a board task for single-feature validation", async () => {
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-001", sliceId: "SL-001" });
missionStore._setFeature(feature);