Propagate cache-warmed workflow lanes through task updates so synchronous engine consumers support renamed boards. - Add task lane cache and attach resolved lanes to task:updated metadata. - Update scheduler, triage, and notification consumers to use carried lanes with bridge-safe fallbacks. - Cover lane propagation and renamed-lane event behavior with core and engine tests. Files changed: .changeset/fn-8677-manual-merge-hold-lanes.md | 7 ++ .changeset/task-updated-carries-lanes.md | 7 ++ ...orkflow-ir-readers-always-return-the-default.md | 22 +++++ .../sync-workflow-ir-second-blocker.test.ts | 43 +++----- .../core/src/__tests__/task-lane-cache.test.ts | 30 ++++++ .../task-updated-lanes-emit-surfaces.test.ts | 92 ++++++++++++++++++ .../__tests__/task-updated-lanes-payload.test.ts | 42 ++++++++ packages/core/src/index.ts | 1 + packages/core/src/store.ts | 36 ++++++- packages/core/src/task-lane-cache.ts | 63 ++++++++++++ .../core/src/task-store/archive-lifecycle-2.ts | 3 + packages/core/src/task-store/moves.ts | 1 + packages/core/src/task-store/task-artifacts-ops.ts | 1 + packages/core/src/task-store/task-update.ts | 1 + packages/core/src/task-store/update-task-deps.ts | 4 +- .../core/src/task-store/workflow-definitions.ts | 71 +++++--------- .../__tests__/scheduler-task-updated-lanes.test.ts | 108 +++++++++++++++++++++ .../task-updated-lanes-bridge-compat.test.ts | 94 ++++++++++++++++++ ...task-updated-lanes-engine-emit-surfaces.test.ts | 101 +++++++++++++++++++ .../src/__tests__/triage-pause-abort.test.ts | 22 +++++ .../src/__tests__/triage-planning-wake.test.ts | 25 +++++ .../notification-renamed-lifecycle-columns.test.ts | 84 +++++++++++++++- .../__tests__/task-wedge-notification.test.ts | 19 ++++ .../src/notification/notification-service.ts | 56 ++++------- packages/engine/src/scheduler.ts | 62 +++--------- packages/engine/src/triage.ts | 105 ++++++-------------- scripts/lib/inert-sync-lane-baseline.json | 5 +- 27 files changed, 858 insertions(+), 247 deletions(-) Fusion-Task-Id: FN-8677 Fusion-Task-Lineage: d8fef9db-0f88-4dfd-9813-be25e10e3588 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
64 lines
2.1 KiB
TypeScript
64 lines
2.1 KiB
TypeScript
import type { TaskMoveLanes } from "./workflow-lifecycle-traits.js";
|
|
|
|
export interface TaskLaneCacheOptions {
|
|
ttlMs?: number;
|
|
maxSize?: number;
|
|
now?: () => number;
|
|
}
|
|
|
|
interface TaskLaneCacheEntry {
|
|
lanes: TaskMoveLanes | undefined;
|
|
workflowId?: string;
|
|
at: number;
|
|
}
|
|
|
|
/*
|
|
FNXC:WorkflowEvents 2026-08-01-06:11:
|
|
PostgreSQL deployments can rewrite a task workflow selection from another node, so an in-process lane
|
|
answer must expire as well as being invalidated by local selection writes. Returning an old answer
|
|
would make a synchronous listener confidently choose the wrong lane; a cache miss is deliberately
|
|
"unknown" and lets that listener retain its existing fallback.
|
|
|
|
The clock is injectable so TTL coverage advances a deterministic fake clock instead of sleeping. The
|
|
cache never resolves an IR: update-event decoration must remain a synchronous Map lookup on hot paths.
|
|
*/
|
|
export class TaskLaneCache {
|
|
private readonly entries = new Map<string, TaskLaneCacheEntry>();
|
|
private readonly ttlMs: number;
|
|
private readonly maxSize: number;
|
|
private readonly now: () => number;
|
|
|
|
constructor({ ttlMs = 30_000, maxSize = 1_000, now = Date.now }: TaskLaneCacheOptions = {}) {
|
|
this.ttlMs = ttlMs;
|
|
this.maxSize = maxSize;
|
|
this.now = now;
|
|
}
|
|
|
|
set(taskId: string, lanes: TaskMoveLanes | undefined, options: { workflowId?: string } = {}): void {
|
|
this.entries.delete(taskId);
|
|
this.entries.set(taskId, { lanes, workflowId: options.workflowId, at: this.now() });
|
|
while (this.entries.size > this.maxSize) this.entries.delete(this.entries.keys().next().value!);
|
|
}
|
|
|
|
get(taskId: string): TaskMoveLanes | undefined {
|
|
const entry = this.entries.get(taskId);
|
|
if (!entry) return undefined;
|
|
if (this.now() - entry.at >= this.ttlMs) {
|
|
this.entries.delete(taskId);
|
|
return undefined;
|
|
}
|
|
// Refresh insertion order without changing the recorded cache time.
|
|
this.entries.delete(taskId);
|
|
this.entries.set(taskId, entry);
|
|
return entry.lanes;
|
|
}
|
|
|
|
invalidate(taskId: string): void {
|
|
this.entries.delete(taskId);
|
|
}
|
|
|
|
clear(): void {
|
|
this.entries.clear();
|
|
}
|
|
}
|