From 78d411cfe20d51dec8dbfcb94aee32fa1d6218a1 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 16:29:37 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20main=20is=20RED=20on=20two=20gates=20?= =?UTF-8?q?=E2=80=94=20record=20the=20new=20fallback,=20repoint=20six=20fu?= =?UTF-8?q?ture-dated=20stamps=20(#3261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `9094d1640e` (globalPause gates every graph node entry) reddened **two** lifecycle gates on main. Both are fixed here, in separate commits. ## 1. The census ratchet went 0 → 2 `isTerminalColumnTask` in `scheduler.ts`: ```ts const flags = columnFlagsForTask(task); if (flags) return flags.complete === true || flags.archived === true; return task.column === "done" || task.column === "archived"; // ← counted ``` **The code is correct.** It resolves traits first and falls back only when the workflow is unreadable. The census counts fallback literals on purpose — *"a fallback literal is still a literal and should go when the trait path becomes unconditional"* — and reports them beside the backlog as already-converted. Its own remedy for a legitimate one is a `DELIBERATE-LITERAL` marker at the site. Recorded rather than converted because **there is nothing to convert to**: a task whose workflow cannot be read has no resolved lane, and treating it as non-terminal would count a finished card's retained worktree against live capacity — the opposite of what the surrounding fix does. Marker sits in the declaration's **leading** comments; an inline one attaches to the wrong node and is silently ignored, which cost a miscount once before. Baseline re-recorded in the same commit, since the census tracks deliberate counts and reports a marker addition as `RECLASSIFIED`. ## 2. The stamp gate was red as well Six files stamped `2026-08-01-00:2x` while UTC was `2026-07-31`: ``` workflow-column-boundary.ts 2 workflow-graph-task-runner.ts 1 workflow-column-boundary-hooks.ts 1 in-process-runtime.ts 5 (allows 4) workflow-column-boundary-capacity.test 1 ``` This checkout is UTC-7, so "just after midnight local" is tomorrow in UTC — the case AGENTS.md documents, which passes `pnpm lint` locally *because* the local clock agrees with what was written. Second occurrence today; I fixed the same shape on #3208 for another worker. Repointed to `2026-07-31-22:2x`, preserving relative order. **Zero non-comment lines changed** — 8 lines across 6 files, verified by diffing out FNXC lines. ## Measured | check | before | after | |---|---|---| | `census --strict` | **1** | **0** | | backlog | **2** | **0** (DELIBERATE-LITERAL 148 → 150) | | `check-fnxc-future-dates` | **1** | **0** | | `pnpm test:gate` | 0 | 0 | | `census-reclassification-message` | 2 failed | **1 failed** | That last row is deliberate: the remaining failure is the expired-premise case #3260 fixes, and I have not touched it. The capacity test from `9094d1640e` still passes 9/9. ## Why this landed at all Both gates run in `pr-checks.yml`, so a PR carrying either would have gone red. Worth someone checking how it merged — a stale merge base would explain it, and if so the same hole is open for the next merge. --- .../workflow-column-boundary-capacity.test.ts | 2 +- .../engine/src/runtimes/in-process-runtime.ts | 2 +- packages/engine/src/scheduler.ts | 15 ++++++++++++++- .../engine/src/workflow-column-boundary-hooks.ts | 2 +- packages/engine/src/workflow-column-boundary.ts | 4 ++-- packages/engine/src/workflow-graph-task-runner.ts | 2 +- scripts/lib/fnxc-future-dates-baseline.json | 2 +- scripts/lib/lifecycle-column-census-baseline.json | 4 ++-- 8 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/engine/src/__tests__/workflow-column-boundary-capacity.test.ts b/packages/engine/src/__tests__/workflow-column-boundary-capacity.test.ts index 8b51eb332e..01cde795ef 100644 --- a/packages/engine/src/__tests__/workflow-column-boundary-capacity.test.ts +++ b/packages/engine/src/__tests__/workflow-column-boundary-capacity.test.ts @@ -57,7 +57,7 @@ function invariantError() { describe("workflow column boundary — global pause suspends at every node entry", () => { /* - FNXC:EnginePause 2026-08-01-00:30: + FNXC:EnginePause 2026-07-31-22:30: Operator regression: Stop AI Engine (globalPause) did not stop the graph — a live run started a fresh Plan Review model session two minutes after pause, because no node boundary ever re-read settings. These fail if the `isPaused` probe is removed from onNodeEntry. diff --git a/packages/engine/src/runtimes/in-process-runtime.ts b/packages/engine/src/runtimes/in-process-runtime.ts index 059d057a51..af96162c1a 100644 --- a/packages/engine/src/runtimes/in-process-runtime.ts +++ b/packages/engine/src/runtimes/in-process-runtime.ts @@ -2309,7 +2309,7 @@ export class InProcessRuntime private async drainWorkflowContinuations(): Promise { if (this.workflowContinuationDrainActive || this.status !== "active") return; /* - FNXC:EnginePause 2026-08-01-00:20: + FNXC:EnginePause 2026-07-31-22:20: A pause-suspended run persists a runnable continuation (same mechanism as capacity). Without this gate the drain would re-dispatch it on the next tick and the graph would bounce suspend→dispatch→suspend forever while paused — and worse, dispatch genuinely new work under diff --git a/packages/engine/src/scheduler.ts b/packages/engine/src/scheduler.ts index 818d5a58eb..4aeb60a558 100644 --- a/packages/engine/src/scheduler.ts +++ b/packages/engine/src/scheduler.ts @@ -1277,7 +1277,7 @@ export class Scheduler { hottest write path (26 emit sites against 7, measured), not a signature change here. */ if (task.column === "in-progress") this.failedTaskIds.add(task.id); /* - FNXC:MissionReconciliation 2026-08-01-00:00: + FNXC:MissionReconciliation 2026-07-31-22:00: In-place failure parks do not emit task:moved, but they release the task's durable symbol lock. Reconcile any mission-linked failure update so the roadmap records withheld provenance without fabricating completion. @@ -2230,6 +2230,19 @@ export class Scheduler { membership — wip cards without a worktree yet still reserve (they are about to acquire), and terminal lanes are excluded because their retained worktrees are cleanup-owned, not capacity. */ + /* + FNXC:WorkflowResolvedColumns 2026-07-31-20:55 (u12 — the ratchet caught this, correctly): + DELIBERATE-LITERAL — the unresolvable-workflow default. The trait path above is the real answer; + this arm is reached only when `columnFlagsForTask` returns undefined, i.e. the task's workflow + could not be read at all, and it then gives the same answer the pre-trait code gave. + + Recorded rather than converted because there is nothing to convert TO: a task with no readable + workflow has no resolved lane, and treating it as non-terminal would count a finished card's + retained worktree against live capacity — the opposite of what the surrounding fix does. + + Marker sits in the DECLARATION's leading comments, not inline: markers are read from a node's + leading comments, so a mid-expression one attaches to the wrong node and is silently ignored. + */ const isTerminalColumnTask = (task: Task): boolean => { const flags = columnFlagsForTask(task); if (flags) return flags.complete === true || flags.archived === true; diff --git a/packages/engine/src/workflow-column-boundary-hooks.ts b/packages/engine/src/workflow-column-boundary-hooks.ts index 8d8d11824c..bc8e7fdf59 100644 --- a/packages/engine/src/workflow-column-boundary-hooks.ts +++ b/packages/engine/src/workflow-column-boundary-hooks.ts @@ -61,7 +61,7 @@ export function createExecutorColumnBoundaryHooks( // KTD-3 drift-park loop fix (PR #2342): detectDrift clears the stale pin // row fields so an ordinary requeue re-resolves the CURRENT IR fresh. clearPin: pinPersistence.clearPin, - /* FNXC:EnginePause 2026-08-01-00:20: settings re-read per node entry — event-independent. */ + /* FNXC:EnginePause 2026-07-31-22:20: settings re-read per node entry — event-independent. */ isPaused: async () => { try { const settings = await store.getSettings(); diff --git a/packages/engine/src/workflow-column-boundary.ts b/packages/engine/src/workflow-column-boundary.ts index ac368aea7e..c02484e7a1 100644 --- a/packages/engine/src/workflow-column-boundary.ts +++ b/packages/engine/src/workflow-column-boundary.ts @@ -103,7 +103,7 @@ export interface WorkflowColumnBoundaryDeps { /** Persist a durable continuation before control returns to the scheduler. */ onSuspend?: (suspension: Extract) => void | Promise; /* - FNXC:EnginePause 2026-08-01-00:20 (Stop AI Engine did not stop the graph): + FNXC:EnginePause 2026-07-31-22:20 (Stop AI Engine did not stop the graph): Operator-observed regression: with `globalPause: true` the graph runner kept crossing node boundaries — a live run started a NEW Plan Review step (fresh model session) two minutes after Stop AI Engine, and the plan-review→replan loop kept cycling "attempt N/unbounded". The legacy @@ -290,7 +290,7 @@ export function createWorkflowColumnBoundary( const toColumn = node.column; /* - FNXC:EnginePause 2026-08-01-00:20: + FNXC:EnginePause 2026-07-31-22:20: Pause gates EVERY node entry — columnless and same-column nodes included, because each node can start a real AI session regardless of whether the card moves. Suspend with the same durable-continuation mechanism capacity uses, so unpause resumes at exactly this node; the diff --git a/packages/engine/src/workflow-graph-task-runner.ts b/packages/engine/src/workflow-graph-task-runner.ts index 6ab704bd31..662b778ff0 100644 --- a/packages/engine/src/workflow-graph-task-runner.ts +++ b/packages/engine/src/workflow-graph-task-runner.ts @@ -177,7 +177,7 @@ export interface WorkflowColumnBoundaryHooks { clearPin?: () => void | Promise; onWarn?: (message: string, detail: Record) => void; onSuspend?: WorkflowColumnBoundaryDeps["onSuspend"]; - /** FNXC:EnginePause 2026-08-01-00:20: polled at every node entry (see boundary deps). */ + /** FNXC:EnginePause 2026-07-31-22:20: polled at every node entry (see boundary deps). */ isPaused?: WorkflowColumnBoundaryDeps["isPaused"]; } diff --git a/scripts/lib/fnxc-future-dates-baseline.json b/scripts/lib/fnxc-future-dates-baseline.json index ffdb3c9a77..081359597e 100644 --- a/scripts/lib/fnxc-future-dates-baseline.json +++ b/scripts/lib/fnxc-future-dates-baseline.json @@ -83,7 +83,7 @@ "packages/engine/src/restart-recovery-coordinator.ts": 1, "packages/engine/src/runtime-resolution.ts": 1, "packages/engine/src/runtimes/in-process-runtime.ts": 4, - "packages/engine/src/scheduler.ts": 3, + "packages/engine/src/scheduler.ts": 2, "packages/engine/src/triage.ts": 6, "packages/engine/src/workflow-work-processor.ts": 1, "scripts/lib/lifecycle-column-census-ast.mjs": 3, diff --git a/scripts/lib/lifecycle-column-census-baseline.json b/scripts/lib/lifecycle-column-census-baseline.json index db80cd83e1..8fcdc6a09a 100644 --- a/scripts/lib/lifecycle-column-census-baseline.json +++ b/scripts/lib/lifecycle-column-census-baseline.json @@ -4,6 +4,8 @@ "deliberateByFile": { "packages/core/src/task-store/async-comments-attachments.ts\u0000archived": 6, "packages/dashboard/app/components/TaskContextMenu.tsx\u0000in-review": 3, + "packages/engine/src/scheduler.ts\u0000archived": 3, + "packages/engine/src/scheduler.ts\u0000done": 3, "packages/engine/src/scheduler.ts\u0000in-progress": 3, "packages/engine/src/scheduler.ts\u0000in-review": 3, "packages/engine/src/self-healing.ts\u0000in-review": 3, @@ -26,8 +28,6 @@ "packages/dashboard/src/reliability-metrics.ts\u0000in-review": 2, "packages/engine/src/auto-merge-finalization.ts\u0000done": 2, "packages/engine/src/cli-agent/state-machine.ts\u0000done": 2, - "packages/engine/src/scheduler.ts\u0000archived": 2, - "packages/engine/src/scheduler.ts\u0000done": 2, "packages/engine/src/self-healing.ts\u0000done": 2, "packages/engine/src/triage.ts\u0000triage": 2, "packages/engine/src/usage-limit-detector.ts\u0000archived": 2,