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