FN-8760: publish workflow lanes for new tasks

Publish durable workflow lane metadata when new tasks wake triage.

- Defer task-created events until selected workflow lanes are durable.
- Cache and emit resolved lifecycle lanes while preserving listener compatibility.
- Prevent proposal-claim replays from issuing duplicate triage wakes and cover custom lanes.

Files changed:
 .changeset/fn-8760-planning-wake.md                |  7 ++++
 .../postgres/task-proposal-claim.pg.test.ts        | 48 +++++++++++++++++++++-
 .../__tests__/task-updated-lanes-payload.test.ts   | 20 ++++++++-
 packages/core/src/store.ts                         | 12 ++++--
 packages/core/src/task-store/task-creation.ts      | 44 ++++++++++++++++----
 packages/engine/src/__tests__/triage.test.ts       | 34 ++++++++++++++-
 6 files changed, 152 insertions(+), 13 deletions(-)

Fusion-Task-Id: FN-8760

Fusion-Task-Lineage: 06275d4a-3fc0-405e-baa5-3cdc8d195695

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-08-03 03:01:24 -07:00
parent b269bff4d4
commit 71ba437cfe
6 changed files with 152 additions and 13 deletions

View File

@@ -882,7 +882,7 @@ describe("canonical triage policy prompt", () => {
describe("FN-5893 invariant regression wording", () => {
const corePromptSource = readFileSync(
fileURLToPath(new URL("../../../core/src/agent-prompts.ts", import.meta.url)),
fileURLToPath(new URL("../../../core/src/agents/agent-prompts.ts", import.meta.url)),
"utf8",
);
@@ -1759,6 +1759,38 @@ Planner rewrote mission without the raw request.
// Should not throw
});
/*
FNXC:PlanningModeScheduling 2026-08-03-09:44:
Planning Mode may create into a selected workflow whose intake and hold lanes are renamed.
The creation event must carry that durable lane answer so the normal wake reaches triage without
a route-specific scheduler call; paused work remains gated before the poll is requested.
*/
it("wakes normal planning admission for a created task in a custom workflow lane", () => {
const listeners = new Map<string, (...args: unknown[]) => void>();
const triageStore = createMockStore({
on: vi.fn((event: string, listener: (...args: unknown[]) => void) => {
listeners.set(event, listener);
return triageStore;
}),
off: vi.fn(),
});
const triageProcessor = trackProcessor(new TriageProcessor(triageStore, rootDir));
const requestImmediatePoll = vi.spyOn(triageProcessor, "requestImmediatePoll").mockReturnValue(true);
triageProcessor.start();
listeners.get("task:created")?.(
createTriageTask({ id: "FN-PLANNING-CREATED", column: "planning-inbox" }),
{ lanes: { intake: "planning-inbox", hold: "ready-to-plan" } },
);
listeners.get("task:created")?.(
createTriageTask({ id: "FN-PLANNING-PAUSED", column: "planning-inbox", paused: true }),
{ lanes: { intake: "planning-inbox", hold: "ready-to-plan" } },
);
expect(requestImmediatePoll).toHaveBeenCalledTimes(1);
triageProcessor.stop();
});
it("handles settings:updated event for globalPause", () => {
const handler = vi.fn();
(store.on as ReturnType<typeof vi.fn>).mockImplementation(