FN-126: fix inert sync-lane validation
Prevent inert synchronization lanes from bypassing merge-gate validation while preserving task-lane cache and archive lifecycle behavior. - Reject sync-resolved lane conversions in the static validator. - Preserve task-lane cache emissions and active-session cleanup across task mutations and archival. - Add regression coverage, baseline updates, documentation, and a release changeset. Files changed: .changeset/fn-126-inert-sync-lane.md | 7 + .../a-falling-count-is-not-evidence.md | 12 + .../task-lane-cache-emitter-preservation.test.ts | 269 +++++++++++++++++++++ .../core/src/task-store/archive-lifecycle-2.ts | 3 +- packages/core/src/task-store/moves.ts | 3 +- packages/core/src/task-store/task-artifacts-ops.ts | 3 +- packages/core/src/task-store/task-update.ts | 3 +- packages/core/src/task-store/update-task-deps.ts | 3 +- ...xecutor-archive-releases-active-session.test.ts | 80 ++++++ packages/engine/src/executor.ts | 4 +- .../src/executor/executor-side-effect-hosts.ts | 2 +- .../executor/is-backward-move-out-of-planning.ts | 7 +- .../src/executor/task-executor-graph-facades.ts | 2 +- .../engine/src/executor/task-executor-imports.ts | 1 - .../engine/src/executor/wire-executor-lifecycle.ts | 18 +- .../check-inert-sync-lane-conversions.test.mjs | 83 +++++-- scripts/check-inert-sync-lane-conversions.mjs | 19 +- scripts/lib/inert-sync-lane-baseline.json | 6 +- 18 files changed, 479 insertions(+), 46 deletions(-) Fusion-Task-Id: FN-126 Fusion-Task-Lineage: 0c7a1a3a-9446-44f8-955d-df6c402dfd31 Co-authored-by: Fusion <noreply@runfusion.ai>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* FNXC:CodeOrganization 2026-08-04-07:15:
|
||||
* Single side-effect import for TaskExecutor FNXC/doc hosts (U4) so executor.ts
|
||||
* does not spend a line per host module. isBackwardMoveOutOfPlanning body stays
|
||||
* on TaskExecutor for inert-sync-lane (2 guards).
|
||||
* on TaskExecutor for payload/cache/legacy lane tiering; no sync lane resolver is permitted.
|
||||
*/
|
||||
import "./is-backward-move-out-of-planning.js";
|
||||
import "./task-executor-fields.js";
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/**
|
||||
* FNXC:CodeOrganization 2026-08-04-06:20:
|
||||
* Host for isBackwardMoveOutOfPlanning requirement history (U4). The method body stays on
|
||||
* TaskExecutor so `check-inert-sync-lanes` keeps counting the two resolvePlannerLanes guards
|
||||
* in executor.ts — do not free-peel that body without re-proving the inert-sync baseline.
|
||||
* TaskExecutor and consumes only task:moved payload/cache lanes; it must never resolve lanes synchronously.
|
||||
*
|
||||
* FNXC:WorkflowResolvedColumns 2026-08-22-00:13:
|
||||
* Measurement showed the PostgreSQL sync reader returns exactly the legacy defaults, so its fallback
|
||||
* bought no renamed-board correctness. The listener now tiers payload, TaskLaneCache, then literals.
|
||||
*
|
||||
* FNXC:WorkflowLifecycleColumns 2026-07-30-16:55 (PR #2628 review, greptile P1):
|
||||
* THE FORWARD EXCLUSIONS MUST RESOLVE TOO, and leaving them literal made this branch WORSE
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* FNXC:CodeOrganization 2026-08-04-09:20:
|
||||
* Workflow graph / merge-boundary / graph-failure routing facades peeled from TaskExecutor (U4).
|
||||
* isBackwardMoveOutOfPlanning stays on TaskExecutor for inert-sync-lane (2 guards).
|
||||
* isBackwardMoveOutOfPlanning stays on TaskExecutor for payload/cache/legacy lane tiering; no sync lane resolver is permitted.
|
||||
*/
|
||||
import type { Task, TaskDetail, Settings, Agent, ResolvedTaskOutputLanguage, WorkflowIr, WorkflowColumnAgent } from "@fusion/core";
|
||||
import * as impl from "./impl-bindings.js";
|
||||
|
||||
@@ -12,7 +12,6 @@ export type {
|
||||
Agent, MergeResult, WorkflowIrNode, WorkflowIr, WorkflowColumnAgent, TaskMoveLanes,
|
||||
ApprovalRequestStore,
|
||||
} from "@fusion/core";
|
||||
export { resolvePlannerLanes } from "../execution/replan-target.js";
|
||||
export type { WorkflowGraphTaskRunResult } from "../workflows/workflow-graph-task-runner.js";
|
||||
export type { WorkflowLegacySeams } from "../workflows/workflow-node-handlers.js";
|
||||
export type { WorkflowRuntimePrimitives } from "../execution/runtime-primitives.js";
|
||||
|
||||
@@ -269,6 +269,7 @@ export function wireExecutorLifecycle(deps: WireExecutorLifecycleDeps): WireExec
|
||||
asserts every `task:moved` emit site supplies it. Until one of those lands, treat the fallback
|
||||
as a live inertness path rather than defensive dead code.
|
||||
*/
|
||||
/* FNXC:WorkflowResolvedColumns 2026-08-22-00:13: This supersedes the prior residual-risk note: optional emitter payloads now consult TaskLaneCache before legacy ids; making lanes required and bridge forwarding remain separate follow-ups. */
|
||||
deps.store.on("task:moved", ({ task, from, to, source, lanes }) => {
|
||||
/*
|
||||
FNXC:Diagnostics 2026-08-10-18:32:
|
||||
@@ -292,9 +293,18 @@ export function wireExecutorLifecycle(deps: WireExecutorLifecycleDeps): WireExec
|
||||
of this payload. `wipLane`/`archivedLane`/`holdLane` are read as SINGLE ids rather than sets
|
||||
because each branch below is a lane-identity test on one column, which is what the literals were.
|
||||
*/
|
||||
const wipLane = lanes?.wip ?? "in-progress";
|
||||
const archivedLane = lanes?.archived ?? "archived";
|
||||
const holdLane = lanes?.hold ?? "todo";
|
||||
/*
|
||||
FNXC:WorkflowResolvedColumns 2026-08-22-00:13:
|
||||
Optional payload lanes win, then the store's synchronous TTL cache preserves the last real
|
||||
answer after an emitter resolution miss; literals are only the cold-cache compatibility tier.
|
||||
Runtime/project bridges re-emit on their own EventEmitters, not TaskStore, so they cannot feed
|
||||
this listener and intentionally remain outside this contract.
|
||||
*/
|
||||
const effectiveLanes = lanes ?? deps.store.laneCache?.get(task.id);
|
||||
/* FNXC:WorkflowResolvedColumns 2026-08-22-00:28: fall back only when no lane answer exists; an answer with an absent role must not invent a legacy role-named column. */
|
||||
const wipLane = effectiveLanes ? effectiveLanes.wip : "in-progress";
|
||||
const archivedLane = effectiveLanes ? effectiveLanes.archived : "archived";
|
||||
const holdLane = effectiveLanes ? effectiveLanes.hold : "todo";
|
||||
if (to === wipLane) {
|
||||
deps.userCanceledTaskIds.delete(task.id);
|
||||
if (deps.recoveringCompleted.has(task.id)) {
|
||||
@@ -354,7 +364,7 @@ export function wireExecutorLifecycle(deps: WireExecutorLifecycleDeps): WireExec
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else if (deps.isBackwardMoveOutOfPlanning(task.id, from, to, lanes)) {
|
||||
} else if (deps.isBackwardMoveOutOfPlanning(task.id, from, to, effectiveLanes)) {
|
||||
/*
|
||||
FNXC:PlanningEvacuation 2026-07-25-23:00:
|
||||
A card pulled BACKWARD out of a planner lane (the reported case: todo → Ideas) must stop all
|
||||
|
||||
Reference in New Issue
Block a user