fix(state): skip cancelled slices during dispatch
This commit is contained in:
parent
62d63f111e
commit
8e85a6e673
3 changed files with 35 additions and 6 deletions
|
|
@ -41,7 +41,7 @@ import {
|
|||
readMilestoneValidationVerdict,
|
||||
stripMilestonePrefix,
|
||||
} from "./state-shared.js";
|
||||
import { isClosedStatus, isDeferredStatus } from "./status-guards.js";
|
||||
import { isClosedStatus, isInactiveStatus } from "./status-guards.js";
|
||||
import { logWarning } from "./workflow-logger.js";
|
||||
|
||||
// ─── DB-backed State Derivation ────────────────────────────────────────────
|
||||
|
|
@ -502,7 +502,7 @@ function resolveSliceDependencies(activeMilestoneSlices) {
|
|||
const sliceLock = process.env.SF_SLICE_LOCK;
|
||||
if (sliceLock) {
|
||||
const lockedSlice = activeMilestoneSlices.find((s) => s.id === sliceLock);
|
||||
if (lockedSlice) {
|
||||
if (lockedSlice && !isInactiveStatus(lockedSlice.status)) {
|
||||
return {
|
||||
activeSlice: { id: lockedSlice.id, title: lockedSlice.title },
|
||||
activeSliceRow: lockedSlice,
|
||||
|
|
@ -519,8 +519,7 @@ function resolveSliceDependencies(activeMilestoneSlices) {
|
|||
let bestFallback = null;
|
||||
let bestFallbackSatisfied = -1;
|
||||
for (const s of activeMilestoneSlices) {
|
||||
if (isStatusDone(s.status)) continue;
|
||||
if (isDeferredStatus(s.status)) continue;
|
||||
if (isInactiveStatus(s.status)) continue;
|
||||
if (s.depends.every((dep) => doneSliceIds.has(dep))) {
|
||||
return { activeSlice: { id: s.id, title: s.title }, activeSliceRow: s };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ export function isDeferredStatus(status) {
|
|||
}
|
||||
/**
|
||||
* Returns true when a slice should be skipped during active-slice selection.
|
||||
* This includes both closed (complete/done) and deferred slices.
|
||||
* This includes closed, deferred, and cancelled slices.
|
||||
*/
|
||||
export function isInactiveStatus(status) {
|
||||
return isClosedStatus(status) || isDeferredStatus(status);
|
||||
return (
|
||||
isClosedStatus(status) || isDeferredStatus(status) || status === "cancelled"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,6 +134,34 @@ test("deriveState_when_generated_projections_are_stale_uses_db_slice_and_task_se
|
|||
assert.equal(state.activeTask?.id, "T02");
|
||||
});
|
||||
|
||||
test("deriveState_when_all_slices_cancelled_does_not_select_cancelled_slice", async () => {
|
||||
const project = mkdtempSync(join(tmpdir(), "sf-db-runtime-state-"));
|
||||
tmpDirs.push(project);
|
||||
mkdirSync(join(project, ".sf", "milestones", "M779", "slices", "S01"), {
|
||||
recursive: true,
|
||||
});
|
||||
openDatabase(join(project, ".sf", "sf.db"));
|
||||
insertMilestone({
|
||||
id: "M779",
|
||||
title: "Cancelled slice selection",
|
||||
status: "active",
|
||||
});
|
||||
insertSlice({
|
||||
milestoneId: "M779",
|
||||
id: "S01",
|
||||
title: "Cancelled work",
|
||||
status: "cancelled",
|
||||
sequence: 1,
|
||||
});
|
||||
|
||||
const state = await deriveState(project);
|
||||
|
||||
assert.equal(state.phase, "blocked");
|
||||
assert.equal(state.activeMilestone?.id, "M779");
|
||||
assert.equal(state.activeSlice, null);
|
||||
assert.deepEqual(state.blockers, ["No slice eligible — check dependency ordering"]);
|
||||
});
|
||||
|
||||
test("deriveState_when_db_has_no_tasks_refuses_runtime_plan_file_import", async () => {
|
||||
const project = mkdtempSync(join(tmpdir(), "sf-db-runtime-state-"));
|
||||
tmpDirs.push(project);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue