Files
fusion/packages/engine/src/mission-feature-sync.ts
gsxdsm 3255965ad5 FN-5715: gate mission feature completion on assertion validation
Prevent mission-linked done tasks from bypassing assertion validation and recover stalled validation triggers after restart.

- require assertion-linked done features to have a passed validator result before slice/milestone completion
- keep assertion-linked features in progress when linked tasks move to done/archived until validation passes
- restart mission loop recovery when needed and re-trigger done/archived implementing features that still need validation
- add regression coverage for scheduler, mission loop recovery, mission store completion gating, and reliability trigger continuity
- document the new FN-5715 reliability backstop and recovery behavior

Files changed:
 .changeset/fn-5715-mission-validation-trigger.md   |   7 +
 AGENTS.md                                          |   1 +
 docs/architecture.md                               |   1 +
 docs/missions.md                                   |  11 +-
 packages/core/src/__tests__/mission-integration.test.ts |   2 +-
 packages/core/src/__tests__/mission-store.test.ts  |  30 +++-
 packages/core/src/mission-store.ts                 |  19 ++-
 packages/engine/src/__tests__/mission-execution-loop.test.ts   |  95 +++++++++++++
 packages/engine/src/__tests__/reliability-interactions/mission-validation-trigger-gap.test.ts         | 151 +++++++++++++++++++++
 packages/engine/src/__tests__/scheduler.test.ts    |  54 ++++++++
 packages/engine/src/mission-autopilot.ts           |   7 +-
 packages/engine/src/mission-execution-loop.ts      |  25 ++++
 packages/engine/src/mission-feature-sync.ts        |  32 ++++-
 packages/engine/src/scheduler.ts                   |  12 +-
 14 files changed, 431 insertions(+), 16 deletions(-)

Fusion-Task-Id: FN-5715

Fusion-Task-Lineage: 98b1cbd6-6b68-40a0-b4b3-c512183adf8b
2026-05-30 09:43:02 -07:00

109 lines
2.9 KiB
TypeScript

import type { MissionFeature, Task, TaskStore } from "@fusion/core";
import { getTaskCompletionBlockerForStore } from "./task-completion.js";
export type MissionFeatureSyncTargetStatus = "done" | "in-progress" | "triaged";
export interface MissionFeatureSyncContext {
hasLinkedAssertions?: boolean;
}
export type MissionFeatureSyncDecision =
| { kind: "failure"; reason: string }
| { kind: "blocked"; reason: string }
| { kind: "update"; status: MissionFeatureSyncTargetStatus; reason: string }
| { kind: "noop" };
export async function reconcileMissionFeatureState(
taskStore: Pick<TaskStore, "getTask">,
task: Task,
feature: Pick<MissionFeature, "id" | "status" | "lastValidatorStatus">,
context: MissionFeatureSyncContext = {},
): Promise<MissionFeatureSyncDecision> {
if (task.status === "failed" && feature.status === "in-progress") {
return {
kind: "failure",
reason: `task ${task.id} failed while feature ${feature.id} is in-progress`,
};
}
const hasUnvalidatedAssertions = context.hasLinkedAssertions === true
&& feature.lastValidatorStatus !== "passed";
if (task.column === "done") {
const blocker = await getTaskCompletionBlockerForStore(taskStore, task);
if (blocker) {
return { kind: "blocked", reason: blocker };
}
if (hasUnvalidatedAssertions) {
if (feature.status !== "in-progress") {
return {
kind: "update",
status: "in-progress",
reason: `task ${task.id} completed; awaiting assertion validation`,
};
}
return { kind: "noop" };
}
if (feature.status !== "done") {
return {
kind: "update",
status: "done",
reason: `task ${task.id} completed`,
};
}
return { kind: "noop" };
}
if (task.column === "archived") {
if (hasUnvalidatedAssertions) {
if (feature.status !== "in-progress") {
return {
kind: "update",
status: "in-progress",
reason: `task ${task.id} archived; awaiting assertion validation`,
};
}
return { kind: "noop" };
}
if (feature.status !== "done") {
return {
kind: "update",
status: "done",
reason: `task ${task.id} was archived after completion`,
};
}
return { kind: "noop" };
}
if (
(task.column === "in-progress" || task.column === "in-review")
&& (feature.status === "triaged" || feature.status === "defined")
) {
return {
kind: "update",
status: "in-progress",
reason: task.column === "in-review"
? `task ${task.id} is in review`
: `task ${task.id} started`,
};
}
if (
(task.column === "triage" || task.column === "todo")
&& feature.status === "in-progress"
) {
return {
kind: "update",
status: "triaged",
reason: `task ${task.id} returned to triage`,
};
}
return { kind: "noop" };
}