FN-5738: make mission loop no-assertion auto-pass deterministic
Ensure mission validation loop advancement is deterministic when no assertions are linked and audit output is emitted once. - update mission execution loop handling to auto-pass zero-assertion validations deterministically - emit and guard validation_auto_passed_no_assertions audit behavior against duplicate recovery re-fire - expand mission loop and reliability interaction tests for loop state, validator status, and replay safety - refresh mission/architecture docs and add the FN-5738 changeset entry Files changed: .changeset/fn-5732a-loop-advance.md | 5 ++ AGENTS.md | 1 + docs/architecture.md | 3 +- docs/missions-completion-contract.md | 2 +- docs/missions.md | 4 +- .../src/__tests__/mission-execution-loop.test.ts | 27 ++++++++++- .../mission-validation-trigger-gap.test.ts | 22 +++++++-- packages/engine/src/mission-execution-loop.ts | 54 +++++++++++++--------- 8 files changed, 86 insertions(+), 32 deletions(-) Fusion-Task-Id: FN-5738 Fusion-Task-Lineage: 141b73e3-fa49-468a-87b0-f36e091e716f
This commit is contained in:
5
.changeset/fn-5732a-loop-advance.md
Normal file
5
.changeset/fn-5732a-loop-advance.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix mission loop no-assertions auto-pass handling so completion deterministically advances feature `loopState` to `passed`, sets `lastValidatorStatus` to `passed`, and emits the structured `validation_auto_passed_no_assertions` audit event exactly once.
|
||||||
@@ -160,6 +160,7 @@ When `settings.autoMerge: false`, `in-review` is terminal-until-merged by a huma
|
|||||||
- FN-5403 backstop: `packages/engine/src/__tests__/reliability-interactions/engine-stop-aborts-execution.test.ts` locks stop-ordering behavior so engine shutdown aborts executor AI sessions before drain wait and preserves task-row lifecycle semantics.
|
- FN-5403 backstop: `packages/engine/src/__tests__/reliability-interactions/engine-stop-aborts-execution.test.ts` locks stop-ordering behavior so engine shutdown aborts executor AI sessions before drain wait and preserves task-row lifecycle semantics.
|
||||||
- FN-5704 backstop: `packages/engine/src/__tests__/reliability-interactions/reclaim-self-owned-resume-limbo-escalation.test.ts` guards reclaim/unpause no-progress oscillation recovery by capping repeated no-progress resumes, escalating to preserve-work `todo` rebound, and emitting `task:resume-limbo-escalated` audit metadata while exempting progress/user-paused/autoMerge-off cases.
|
- FN-5704 backstop: `packages/engine/src/__tests__/reliability-interactions/reclaim-self-owned-resume-limbo-escalation.test.ts` guards reclaim/unpause no-progress oscillation recovery by capping repeated no-progress resumes, escalating to preserve-work `todo` rebound, and emitting `task:resume-limbo-escalated` audit metadata while exempting progress/user-paused/autoMerge-off cases.
|
||||||
- FN-5715 backstop: `packages/engine/src/__tests__/reliability-interactions/mission-validation-trigger-gap.test.ts` guards mission validation trigger continuity so done task completion and startup recovery both route assertion-linked features through validator runs before completion.
|
- FN-5715 backstop: `packages/engine/src/__tests__/reliability-interactions/mission-validation-trigger-gap.test.ts` guards mission validation trigger continuity so done task completion and startup recovery both route assertion-linked features through validator runs before completion.
|
||||||
|
- FN-5738 backstop: `packages/engine/src/__tests__/reliability-interactions/mission-validation-trigger-gap.test.ts` extends mission-loop coverage so zero-assertion auto-pass deterministically advances to `loopState="passed"` and emits `validation_auto_passed_no_assertions` without duplicate recovery re-fire.
|
||||||
- FN-5741 backstop: `packages/engine/src/__tests__/reliability-interactions/merge-request-shadow-handoff.test.ts` guards Phase-1 write-only-shadow merge-request record + handoff-accepted marker seam (flag OFF = no-op, ON = shadow-only non-authoritative).
|
- FN-5741 backstop: `packages/engine/src/__tests__/reliability-interactions/merge-request-shadow-handoff.test.ts` guards Phase-1 write-only-shadow merge-request record + handoff-accepted marker seam (flag OFF = no-op, ON = shadow-only non-authoritative).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1768,7 +1768,8 @@ Reliability-layer changes are in scope. Interaction regression backstops live in
|
|||||||
- FN-5168 backstop: `packages/engine/src/__tests__/reliability-interactions/non-progress-churn.test.ts` covers loop→compact recovery followed by ignored-step-update churn escalation, terminal `beforeRequeue(false)` behavior, audit/log payloads, and FN-5147 autoMerge-off composition.
|
- FN-5168 backstop: `packages/engine/src/__tests__/reliability-interactions/non-progress-churn.test.ts` covers loop→compact recovery followed by ignored-step-update churn escalation, terminal `beforeRequeue(false)` behavior, audit/log payloads, and FN-5147 autoMerge-off composition.
|
||||||
- FN-5219 backstop: `packages/engine/src/__tests__/reliability-interactions/in-progress-limbo-recovery.test.ts` covers `recoverInProgressLimbo` composition with `recoverOrphanedExecutions` (no double-recovery), `reconcile-task-worktree-metadata` (live rebindable worktree wins), `recoverMissingWorktreeReviewFailures` (in-review vs in-progress disjoint), and executor task-id claim skip, plus an explicit FN-5149 reproduction case.
|
- FN-5219 backstop: `packages/engine/src/__tests__/reliability-interactions/in-progress-limbo-recovery.test.ts` covers `recoverInProgressLimbo` composition with `recoverOrphanedExecutions` (no double-recovery), `reconcile-task-worktree-metadata` (live rebindable worktree wins), `recoverMissingWorktreeReviewFailures` (in-review vs in-progress disjoint), and executor task-id claim skip, plus an explicit FN-5149 reproduction case.
|
||||||
- FN-5704 backstop: `packages/engine/src/__tests__/reliability-interactions/reclaim-self-owned-resume-limbo-escalation.test.ts` covers bounded no-progress reclaim/resume detection, preserve-work escalation to `todo`, `task:resume-limbo-escalated` audit metadata, progress-signal reset behavior, and user-paused/autoMerge-off non-escalation guards.
|
- FN-5704 backstop: `packages/engine/src/__tests__/reliability-interactions/reclaim-self-owned-resume-limbo-escalation.test.ts` covers bounded no-progress reclaim/resume detection, preserve-work escalation to `todo`, `task:resume-limbo-escalated` audit metadata, progress-signal reset behavior, and user-paused/autoMerge-off non-escalation guards.
|
||||||
- FN-5715 backstop: `packages/engine/src/__tests__/reliability-interactions/mission-validation-trigger-gap.test.ts` locks the mission-validation trigger invariant so done mission-linked tasks still start validation when the mission loop was stopped, startup recovery replays done implementing features with unpassed assertions, no-assertions auto-pass remains unchanged, and recovery remains idempotent for already-passed features.
|
- FN-5715 backstop: `packages/engine/src/__tests__/reliability-interactions/mission-validation-trigger-gap.test.ts` locks the mission-validation trigger invariant so done mission-linked tasks still start validation when the mission loop was stopped, startup recovery replays done implementing features with unpassed assertions, and recovery remains idempotent for already-passed features.
|
||||||
|
- FN-5738 backstop: `packages/engine/src/__tests__/reliability-interactions/mission-validation-trigger-gap.test.ts` extends coverage so zero-assertion auto-pass deterministically advances `loopState` to `passed`, sets `lastValidatorStatus="passed"`, emits `validation_auto_passed_no_assertions`, and does not re-fire on repeated recovery passes.
|
||||||
- FN-5741 backstop: `packages/engine/src/__tests__/reliability-interactions/merge-request-shadow-handoff.test.ts` guards Phase-1 merge-request contract shadow writes: flag OFF is a no-op, flag ON writes marker/record strictly after legacy handoff, and `autoMerge:false` remains `manual-required` without shadow running transitions.
|
- FN-5741 backstop: `packages/engine/src/__tests__/reliability-interactions/merge-request-shadow-handoff.test.ts` guards Phase-1 merge-request contract shadow writes: flag OFF is a no-op, flag ON writes marker/record strictly after legacy handoff, and `autoMerge:false` remains `manual-required` without shadow running transitions.
|
||||||
- FN-5337 backstop: `packages/engine/src/__tests__/reliability-interactions/orphan-detected-no-requeue.test.ts` locks observation-only orphan detection across FN-5279 repro metadata desync, worktree-present and worktree-missing candidates, FN-5219 ordering, FN-5147 in-review isolation, FN-5083 branch-cleared composition, lease-manager non-invocation, and per-sweep idempotent audit emission.
|
- FN-5337 backstop: `packages/engine/src/__tests__/reliability-interactions/orphan-detected-no-requeue.test.ts` locks observation-only orphan detection across FN-5279 repro metadata desync, worktree-present and worktree-missing candidates, FN-5219 ordering, FN-5147 in-review isolation, FN-5083 branch-cleared composition, lease-manager non-invocation, and per-sweep idempotent audit emission.
|
||||||
- FN-5256 backstop: `packages/engine/src/__tests__/reliability-interactions/dependency-cycle-reconcile.test.ts` covers persisted dependency-cycle detection via `reconcileDependencyCycles`, bounded umbrella-back-edge auto-repair, ambiguous-cycle observe-only behavior, composition ordering with `reconcileSelfDefeatingDependencies`, and the post-sweep write-time guard invariant. Core write-boundary regressions (FN-5240/5241/5242 signature, indirect cycle, umbrella back-edge rejection) live in `packages/core/src/__tests__/store-dependency-cycle.test.ts`.
|
- FN-5256 backstop: `packages/engine/src/__tests__/reliability-interactions/dependency-cycle-reconcile.test.ts` covers persisted dependency-cycle detection via `reconcileDependencyCycles`, bounded umbrella-back-edge auto-repair, ambiguous-cycle observe-only behavior, composition ordering with `reconcileSelfDefeatingDependencies`, and the post-sweep write-time guard invariant. Core write-boundary regressions (FN-5240/5241/5242 signature, indirect cycle, umbrella back-edge rejection) live in `packages/core/src/__tests__/store-dependency-cycle.test.ts`.
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ Target surface: `packages/dashboard/app/components/MissionManager.tsx`
|
|||||||
## Engineering Acceptance Criteria (follow-on implementation)
|
## Engineering Acceptance Criteria (follow-on implementation)
|
||||||
|
|
||||||
✅ Implemented in FN-5733:
|
✅ Implemented in FN-5733:
|
||||||
- Auto-pass path now advances `loopState` to `passed` and emits mission event code `feature_auto_passed_no_assertions` while preserving the `validation:passed` emit contract (`"No assertions linked"` summary).
|
- Auto-pass path now advances `loopState` to `passed` and emits mission event code `validation_auto_passed_no_assertions` while preserving the `validation:passed` emit contract (`"No assertions linked"` summary).
|
||||||
- Milestone rollup/store guard now exposes `hasProseButNoAssertions` and emits warning mission event code `milestone_missing_structured_assertions` (debounced on transition into condition).
|
- Milestone rollup/store guard now exposes `hasProseButNoAssertions` and emits warning mission event code `milestone_missing_structured_assertions` (debounced on transition into condition).
|
||||||
- MissionManager UI now distinguishes enforced assertion gate vs informational feature acceptance criteria.
|
- MissionManager UI now distinguishes enforced assertion gate vs informational feature acceptance criteria.
|
||||||
|
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ A feature transitions to `blocked` when:
|
|||||||
|
|
||||||
On engine restart, `recoverActiveMissions()` re-enqueues features in `validating` or `needs_fix` states from the `activeValidations` set, ensuring no validation work is lost. It also re-triggers `implementing` features whose linked task is already `done`/`archived` and whose assertion validation has not passed yet.
|
On engine restart, `recoverActiveMissions()` re-enqueues features in `validating` or `needs_fix` states from the `activeValidations` set, ensuring no validation work is lost. It also re-triggers `implementing` features whose linked task is already `done`/`archived` and whose assertion validation has not passed yet.
|
||||||
|
|
||||||
For features with zero linked assertions, the completion path is explicit: the loop marks the feature `done`, advances `loopState` to `passed`, emits `validation:passed` with summary `"No assertions linked"`, and records mission event code `feature_auto_passed_no_assertions`.
|
For features with zero linked assertions, the completion path is explicit: the loop marks the feature `done`, advances `loopState` to `passed`, emits `validation:passed` with summary `"No assertions linked"`, and records mission event code `validation_auto_passed_no_assertions`.
|
||||||
|
|
||||||
### Autopilot / Scheduler Interplay
|
### Autopilot / Scheduler Interplay
|
||||||
|
|
||||||
@@ -427,7 +427,7 @@ These are independent tracking mechanisms — autopilot monitors mission progres
|
|||||||
**MissionEvent audit types:**
|
**MissionEvent audit types:**
|
||||||
- `slice_activated`, `feature_planned`, `feature_completed`
|
- `slice_activated`, `feature_planned`, `feature_completed`
|
||||||
- `validation:started`, `validation:passed`, `validation:failed`, `validation:blocked`
|
- `validation:started`, `validation:passed`, `validation:failed`, `validation:blocked`
|
||||||
- `feature_auto_passed_no_assertions` (reason: `no_assertions_linked`)
|
- `validation_auto_passed_no_assertions` (reason: `"No assertions linked"`)
|
||||||
- `milestone_missing_structured_assertions` (warning when prose criteria exist with zero structured assertions)
|
- `milestone_missing_structured_assertions` (warning when prose criteria exist with zero structured assertions)
|
||||||
- `fix_feature:created`, `feature:blocked`
|
- `fix_feature:created`, `feature:blocked`
|
||||||
|
|
||||||
|
|||||||
@@ -523,15 +523,38 @@ describe("MissionExecutionLoop", () => {
|
|||||||
"warning",
|
"warning",
|
||||||
expect.stringContaining("auto-passed"),
|
expect.stringContaining("auto-passed"),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
code: "feature_auto_passed_no_assertions",
|
code: "validation_auto_passed_no_assertions",
|
||||||
featureId: "F-001",
|
featureId: "F-001",
|
||||||
reason: "no_assertions_linked",
|
reason: "No assertions linked",
|
||||||
taskId: "FN-001",
|
taskId: "FN-001",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expectNoValidationBoardTaskMutation(taskStore);
|
expectNoValidationBoardTaskMutation(taskStore);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("emits no-assertions auto-pass event exactly once across re-entry", async () => {
|
||||||
|
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-001" });
|
||||||
|
missionStore._setFeature(feature);
|
||||||
|
taskStore._setTask({ id: "FN-001", title: "Test", description: "Test task", log: [] });
|
||||||
|
missionStore.getFeatureByTaskId = vi.fn().mockReturnValue(feature);
|
||||||
|
missionStore.listAssertionsForFeature = vi.fn().mockReturnValue([]);
|
||||||
|
|
||||||
|
loop = new MissionExecutionLoop({
|
||||||
|
taskStore: taskStore as any,
|
||||||
|
missionStore: missionStore as any,
|
||||||
|
rootDir: "/tmp",
|
||||||
|
});
|
||||||
|
loop.start();
|
||||||
|
|
||||||
|
await loop.processTaskOutcome("FN-001");
|
||||||
|
await loop.processTaskOutcome("FN-001");
|
||||||
|
|
||||||
|
const noAssertionEvents = missionStore.logMissionEvent.mock.calls.filter(
|
||||||
|
([, , , payload]) => payload?.code === "validation_auto_passed_no_assertions",
|
||||||
|
);
|
||||||
|
expect(noAssertionEvents).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("uses validator path for later-added feature with managed assertion", async () => {
|
it("uses validator path for later-added feature with managed assertion", async () => {
|
||||||
const feature = createMockFeature({
|
const feature = createMockFeature({
|
||||||
id: "F-LATER",
|
id: "F-LATER",
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
|
|||||||
expect(missionStore.updateFeatureStatus).not.toHaveBeenCalledWith("F-001", "done");
|
expect(missionStore.updateFeatureStatus).not.toHaveBeenCalledWith("F-001", "done");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps no-assertion completion path unchanged", async () => {
|
it("keeps assertion-linked completion path unchanged", async () => {
|
||||||
const feature = makeFeature();
|
const feature = makeFeature();
|
||||||
const missionStore = {
|
const missionStore = {
|
||||||
getFeatureByTaskId: vi.fn(() => feature),
|
getFeatureByTaskId: vi.fn(() => feature),
|
||||||
@@ -99,6 +99,7 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
|
|||||||
milestones: [{ status: "active", slices: [{ status: "active", features: [feature] }] }],
|
milestones: [{ status: "active", slices: [{ status: "active", features: [feature] }] }],
|
||||||
})),
|
})),
|
||||||
listAssertionsForFeature: vi.fn(() => [{ id: "CA-1" }]),
|
listAssertionsForFeature: vi.fn(() => [{ id: "CA-1" }]),
|
||||||
|
getFeature: vi.fn(() => feature),
|
||||||
transitionLoopState: vi.fn(),
|
transitionLoopState: vi.fn(),
|
||||||
};
|
};
|
||||||
const taskStore = {
|
const taskStore = {
|
||||||
@@ -129,6 +130,7 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
|
|||||||
milestones: [{ status: "active", slices: [{ status: "active", features: [feature] }] }],
|
milestones: [{ status: "active", slices: [{ status: "active", features: [feature] }] }],
|
||||||
})),
|
})),
|
||||||
listAssertionsForFeature: vi.fn(() => [{ id: "CA-1" }]),
|
listAssertionsForFeature: vi.fn(() => [{ id: "CA-1" }]),
|
||||||
|
getFeature: vi.fn(() => feature),
|
||||||
transitionLoopState: vi.fn(),
|
transitionLoopState: vi.fn(),
|
||||||
};
|
};
|
||||||
const taskStore = {
|
const taskStore = {
|
||||||
@@ -151,6 +153,7 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
|
|||||||
|
|
||||||
it("recovery replays implementing done tasks with zero assertions and advances loop state", async () => {
|
it("recovery replays implementing done tasks with zero assertions and advances loop state", async () => {
|
||||||
const feature = makeFeature({ status: "done", lastValidatorStatus: undefined, loopState: "implementing" });
|
const feature = makeFeature({ status: "done", lastValidatorStatus: undefined, loopState: "implementing" });
|
||||||
|
const currentFeature = { ...feature };
|
||||||
const missionStore = {
|
const missionStore = {
|
||||||
listMissions: vi.fn(() => [{ id: "M-001", status: "active" }]),
|
listMissions: vi.fn(() => [{ id: "M-001", status: "active" }]),
|
||||||
getMissionWithHierarchy: vi.fn(() => ({
|
getMissionWithHierarchy: vi.fn(() => ({
|
||||||
@@ -158,10 +161,13 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
|
|||||||
status: "active",
|
status: "active",
|
||||||
milestones: [{ status: "active", slices: [{ status: "active", features: [feature] }] }],
|
milestones: [{ status: "active", slices: [{ status: "active", features: [feature] }] }],
|
||||||
})),
|
})),
|
||||||
getFeatureByTaskId: vi.fn(() => feature),
|
getFeatureByTaskId: vi.fn(() => currentFeature),
|
||||||
getFeature: vi.fn(() => feature),
|
getFeature: vi.fn(() => currentFeature),
|
||||||
updateFeatureStatus: vi.fn(),
|
updateFeatureStatus: vi.fn((featureId: string, status: "done") => ({ ...currentFeature, id: featureId, status })),
|
||||||
updateFeature: vi.fn(),
|
updateFeature: vi.fn((_featureId: string, patch: Partial<MissionFeature>) => {
|
||||||
|
Object.assign(currentFeature, patch);
|
||||||
|
return { ...currentFeature };
|
||||||
|
}),
|
||||||
listAssertionsForFeature: vi.fn(() => []),
|
listAssertionsForFeature: vi.fn(() => []),
|
||||||
getSlice: vi.fn(() => ({ id: "SL-001", milestoneId: "MS-001", status: "active" })),
|
getSlice: vi.fn(() => ({ id: "SL-001", milestoneId: "MS-001", status: "active" })),
|
||||||
getMilestone: vi.fn(() => ({ id: "MS-001", missionId: "M-001" })),
|
getMilestone: vi.fn(() => ({ id: "MS-001", missionId: "M-001" })),
|
||||||
@@ -181,12 +187,18 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
|
|||||||
});
|
});
|
||||||
loop.start();
|
loop.start();
|
||||||
|
|
||||||
|
await loop.recoverActiveMissions();
|
||||||
await loop.recoverActiveMissions();
|
await loop.recoverActiveMissions();
|
||||||
|
|
||||||
|
expect(missionStore.updateFeature).toHaveBeenCalledTimes(1);
|
||||||
expect(missionStore.updateFeature).toHaveBeenCalledWith(
|
expect(missionStore.updateFeature).toHaveBeenCalledWith(
|
||||||
"F-001",
|
"F-001",
|
||||||
expect.objectContaining({ loopState: "passed", lastValidatorStatus: "passed" }),
|
expect.objectContaining({ loopState: "passed", lastValidatorStatus: "passed" }),
|
||||||
);
|
);
|
||||||
|
const noAssertionEvents = missionStore.logMissionEvent.mock.calls.filter(
|
||||||
|
([, type, , payload]) => type === "warning" && payload?.code === "validation_auto_passed_no_assertions",
|
||||||
|
);
|
||||||
|
expect(noAssertionEvents).toHaveLength(1);
|
||||||
loop.stop();
|
loop.stop();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -210,11 +210,16 @@ export class MissionExecutionLoop extends EventEmitter {
|
|||||||
|
|
||||||
// Features that remained implementing while their linked task already finished
|
// Features that remained implementing while their linked task already finished
|
||||||
// can be stranded after restart; recover by re-triggering task outcome.
|
// can be stranded after restart; recover by re-triggering task outcome.
|
||||||
if (
|
if (feature.loopState === "implementing" && feature.taskId) {
|
||||||
feature.loopState === "implementing"
|
const currentFeature = this.missionStore.getFeature(feature.id) ?? feature;
|
||||||
&& feature.taskId
|
if (
|
||||||
&& feature.lastValidatorStatus !== "passed"
|
this.activeValidations.has(feature.id)
|
||||||
) {
|
|| currentFeature.loopState === "passed"
|
||||||
|
|| currentFeature.lastValidatorStatus === "passed"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const linkedTask = await this.taskStore.getTask(feature.taskId).catch(() => null);
|
const linkedTask = await this.taskStore.getTask(feature.taskId).catch(() => null);
|
||||||
if (linkedTask && (linkedTask.column === "done" || linkedTask.column === "archived")) {
|
if (linkedTask && (linkedTask.column === "done" || linkedTask.column === "archived")) {
|
||||||
@@ -803,23 +808,30 @@ ${taskContext ? `\n\nImplementation context:\n${taskContext}` : ""}`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!runId && feature) {
|
if (!runId && feature) {
|
||||||
// Auto-pass path has no validator run, so we must advance the loop state directly.
|
const alreadyAutoPassed =
|
||||||
if (feature.loopState !== "passed" || feature.lastValidatorStatus !== "passed") {
|
feature.status === "done" &&
|
||||||
this.missionStore.updateFeature(featureId, {
|
feature.loopState === "passed" &&
|
||||||
loopState: "passed",
|
feature.lastValidatorStatus === "passed";
|
||||||
lastValidatorStatus: "passed",
|
|
||||||
});
|
if (!alreadyAutoPassed) {
|
||||||
|
// Auto-pass path has no validator run, so we must advance loop bookkeeping here.
|
||||||
|
if (feature.loopState !== "passed" || feature.lastValidatorStatus !== "passed") {
|
||||||
|
this.missionStore.updateFeature(featureId, {
|
||||||
|
loopState: "passed",
|
||||||
|
lastValidatorStatus: "passed",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logFeatureWarningEvent(
|
||||||
|
featureId,
|
||||||
|
"validation_auto_passed_no_assertions",
|
||||||
|
`Feature ${featureId} auto-passed because no assertions were linked.`,
|
||||||
|
{
|
||||||
|
taskId: feature.taskId,
|
||||||
|
reason: "No assertions linked",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.logFeatureMissionEvent(
|
|
||||||
featureId,
|
|
||||||
"warning",
|
|
||||||
"feature_auto_passed_no_assertions",
|
|
||||||
`Feature ${featureId} auto-passed because no assertions were linked.`,
|
|
||||||
{
|
|
||||||
taskId: feature.taskId,
|
|
||||||
reason: "no_assertions_linked",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loopLog.log(`Feature ${featureId} passed validation`);
|
loopLog.log(`Feature ${featureId} passed validation`);
|
||||||
|
|||||||
Reference in New Issue
Block a user