FN-5645: fix mission autopilot progression to wait for assertion runs
Fusion-Task-Id: FN-5645 Fusion-Task-Lineage: ed9d2045-2bfe-4070-a7e3-caca010e6cc6
This commit is contained in:
@@ -13,7 +13,7 @@ function makeHarness({
|
||||
withAssertions?: boolean;
|
||||
initialFeatureStatus?: string;
|
||||
initialTaskColumn?: string;
|
||||
runValidationImpl?: () => Promise<{ status: "pass"; assertions: []; summary: string }>;
|
||||
runValidationImpl?: () => Promise<{ status: "pass" | "error"; assertions: []; summary: string }>;
|
||||
} = {}) {
|
||||
const mission = {
|
||||
id: "M-001",
|
||||
@@ -231,4 +231,26 @@ describe("mission autopilot end-to-end wiring", () => {
|
||||
expect(h.missionStore.startValidatorRun).toHaveBeenCalledTimes(1);
|
||||
h.scheduler.stop();
|
||||
});
|
||||
|
||||
it("emits mission error event when validator returns error", async () => {
|
||||
const h = makeHarness({
|
||||
withAssertions: true,
|
||||
runValidationImpl: async () => ({ status: "error", assertions: [], summary: "runtime unavailable" }),
|
||||
});
|
||||
|
||||
await h.emitTaskMoved("done");
|
||||
|
||||
expect(h.missionStore.completeValidatorRun).toHaveBeenCalledWith("VR-001", "error", "runtime unavailable");
|
||||
expect(h.missionStore.logMissionEvent).toHaveBeenCalledWith(
|
||||
"M-001",
|
||||
"error",
|
||||
expect.stringContaining("Validation error"),
|
||||
expect.objectContaining({
|
||||
code: "validation_error",
|
||||
featureId: "F-001",
|
||||
error: "runtime unavailable",
|
||||
}),
|
||||
);
|
||||
h.scheduler.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,6 +155,7 @@ function createMockMissionStore() {
|
||||
milestones: [createMockMilestone({ missionId: id })],
|
||||
};
|
||||
}),
|
||||
logMissionEvent: vi.fn(),
|
||||
|
||||
// Feature methods
|
||||
getFeature: vi.fn((id: string) => features.get(id)),
|
||||
@@ -449,6 +450,17 @@ describe("MissionExecutionLoop", () => {
|
||||
await loop.processTaskOutcome("FN-001");
|
||||
|
||||
expect(missionStore.startValidatorRun).not.toHaveBeenCalled();
|
||||
expect(missionStore.logMissionEvent).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
"warning",
|
||||
expect.stringContaining("Validation skipped"),
|
||||
expect.objectContaining({
|
||||
code: "validation_skipped_loop_state",
|
||||
featureId: "F-001",
|
||||
taskId: "FN-001",
|
||||
loopState: "idle",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should auto-pass if feature has no linked assertions", async () => {
|
||||
@@ -543,6 +555,16 @@ describe("MissionExecutionLoop", () => {
|
||||
await loop.processTaskOutcome("FN-001");
|
||||
|
||||
expect(missionStore.startValidatorRun).not.toHaveBeenCalled();
|
||||
expect(missionStore.logMissionEvent).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
"warning",
|
||||
expect.stringContaining("duplicate trigger"),
|
||||
expect.objectContaining({
|
||||
code: "validation_deduplicated",
|
||||
featureId: "F-001",
|
||||
taskId: "FN-001",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("calls startValidatorRun without a board task ID", async () => {
|
||||
@@ -1169,6 +1191,16 @@ describe("MissionExecutionLoop", () => {
|
||||
error: "Invalid status in validation response",
|
||||
}),
|
||||
);
|
||||
expect(missionStore.logMissionEvent).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
"error",
|
||||
expect.stringContaining("Validation error"),
|
||||
expect.objectContaining({
|
||||
code: "validation_error",
|
||||
featureId: "F-001",
|
||||
error: "Invalid status in validation response",
|
||||
}),
|
||||
);
|
||||
expectNoValidationBoardTaskMutation(taskStore);
|
||||
});
|
||||
});
|
||||
@@ -1339,6 +1371,7 @@ describe("MissionExecutionLoop", () => {
|
||||
rootDir: "/tmp",
|
||||
});
|
||||
const processTaskOutcomeSpy = vi.spyOn(loop, "processTaskOutcome");
|
||||
taskStore._setTask({ id: "FN-VALIDATING", column: "done" });
|
||||
loop.start();
|
||||
|
||||
await loop.recoverActiveMissions();
|
||||
@@ -1385,6 +1418,7 @@ describe("MissionExecutionLoop", () => {
|
||||
rootDir: "/tmp",
|
||||
});
|
||||
const processTaskOutcomeSpy = vi.spyOn(loop, "processTaskOutcome");
|
||||
taskStore._setTask({ id: "FN-NEEDS-FIX", column: "done" });
|
||||
loop.start();
|
||||
|
||||
await loop.recoverActiveMissions();
|
||||
@@ -1438,6 +1472,53 @@ describe("MissionExecutionLoop", () => {
|
||||
expect(missionStore.transitionLoopState).toHaveBeenCalledWith("F-VALIDATING", "implementing");
|
||||
});
|
||||
|
||||
it("should not call processTaskOutcome when validating feature task is still in-progress", async () => {
|
||||
const feature = createMockFeature({
|
||||
id: "F-VALIDATING-IN-PROGRESS",
|
||||
sliceId: "SL-001",
|
||||
loopState: "validating",
|
||||
taskId: "FN-IN-PROGRESS",
|
||||
});
|
||||
missionStore._setFeature(feature);
|
||||
|
||||
missionStore.getMissionWithHierarchy = vi.fn().mockReturnValue({
|
||||
id: "M-TEST1",
|
||||
title: "Test Mission",
|
||||
status: "active",
|
||||
interviewState: "not_started",
|
||||
autoAdvance: true,
|
||||
autopilotEnabled: true,
|
||||
autopilotState: "inactive",
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
milestones: [
|
||||
{
|
||||
...createMockMilestone(),
|
||||
slices: [
|
||||
{
|
||||
...createMockSlice(),
|
||||
features: [feature],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
loop = new MissionExecutionLoop({
|
||||
taskStore: taskStore as any,
|
||||
missionStore: missionStore as any,
|
||||
rootDir: "/tmp",
|
||||
});
|
||||
const processTaskOutcomeSpy = vi.spyOn(loop, "processTaskOutcome");
|
||||
taskStore._setTask({ id: "FN-IN-PROGRESS", column: "in-progress" });
|
||||
loop.start();
|
||||
|
||||
await loop.recoverActiveMissions();
|
||||
|
||||
expect(processTaskOutcomeSpy).not.toHaveBeenCalled();
|
||||
expect(missionStore.transitionLoopState).toHaveBeenCalledWith("F-VALIDATING-IN-PROGRESS", "implementing");
|
||||
});
|
||||
|
||||
it("should not call processTaskOutcome for needs_fix features without taskId", async () => {
|
||||
const feature = createMockFeature({
|
||||
id: "F-NO-TASK",
|
||||
|
||||
Reference in New Issue
Block a user