Files
fusion/packages/engine/src/workflow-authoritative-driver.ts
2026-06-08 19:13:14 -07:00

211 lines
8.0 KiB
TypeScript

import {
BUILTIN_CODING_WORKFLOW_IR,
WORKFLOW_INTERPRETER_AUTHORITATIVE_FLAG,
evaluateInterpreterCutoverReadiness,
isExperimentalFeatureEnabled,
type Settings,
type Task,
type TaskDetail,
type WorkflowDefinition,
type WorkflowParitySummary,
} from "@fusion/core";
import type { TaskExecutor } from "./executor.js";
import { executorLog } from "./logger.js";
import { WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG } from "./workflow-parity-observer.js";
import { WorkflowGraphTaskRunner, type WorkflowGraphTaskRunResult } from "./workflow-graph-task-runner.js";
import type { WorkflowLegacySeams } from "./workflow-node-handlers.js";
import type { PreparedWorktree, WorkflowRuntimePrimitives } from "./runtime-primitives.js";
const AUTHORITATIVE_WORKFLOW_ID = "workflow-interpreter-authoritative";
export interface WorkflowAuthoritativeDriverStore {
getSettings(): Promise<Settings>;
getTask(taskId: string): Promise<TaskDetail>;
getTaskWorkflowSelection?(taskId: string): { workflowId: string; stepIds: string[] } | undefined;
getWorkflowParitySummary?(options?: { since?: string; limit?: number }): WorkflowParitySummary;
}
export interface WorkflowAuthoritativeDriverDeps {
store: WorkflowAuthoritativeDriverStore;
executor: Pick<TaskExecutor, "createAuthoritativeWorkflowSeams"> & Partial<Pick<TaskExecutor, "createAuthoritativeWorkflowPrimitives">>;
minimumObservedRuns?: number;
}
export interface WorkflowAuthoritativeDriverResult {
handled: boolean;
disposition: "completed" | "failed" | "fell-back";
reason?: string;
readinessReasons: string[];
graphResult?: WorkflowGraphTaskRunResult;
}
function buildAuthoritativeSettings(settings: Settings): Settings {
return {
...settings,
experimentalFeatures: {
...(settings.experimentalFeatures ?? {}),
workflowGraphExecutor: true,
[WORKFLOW_INTERPRETER_AUTHORITATIVE_FLAG]: true,
},
};
}
function primitivesFromLegacySeams(seams: WorkflowLegacySeams): WorkflowRuntimePrimitives {
const prepared: PreparedWorktree = { worktreePath: "" };
return {
prepareWorktree: async () => ({ outcome: "success", data: prepared }),
readArtifact: async () => undefined,
writeArtifact: async (_ctx, _task, key) => ({ outcome: "success", data: { key } }),
runPlanningSession: async (ctx, task) => {
const result = await seams.planning(task, ctx.node.context ?? {});
return { ...result, data: { approved: result.outcome === "success", artifactKeys: [] } };
},
runCodingSession: async (ctx, task) => {
const result = await seams.execute(task, ctx.node.context ?? {});
return {
...result,
data: { taskDone: result.outcome === "success", modifiedFiles: [] },
};
},
runTaskStep: async (ctx, task) => {
const result = await seams.stepExecute?.(task, ctx.node.context ?? {});
return { outcome: result?.outcome ?? "failure" };
},
resetTaskStep: async () => ({ ok: true }),
runReview: async (ctx, task, input) => {
if (typeof input.stepIndex === "number") {
const result = await seams.stepReview?.(task, ctx.node.context ?? {}, { type: input.type });
return {
outcome: "success",
value: result?.verdict === "APPROVE" ? "approve" : result?.verdict === "REVISE" ? "revise" : result?.verdict === "RETHINK" ? "rethink" : "unavailable",
data: result ?? { verdict: "UNAVAILABLE" },
};
}
const result = await seams.review(task, ctx.node.context ?? {});
return { ...result, data: { verdict: result.outcome === "success" ? "APPROVE" : "REVISE" } };
},
runVerification: async () => ({ outcome: "success", data: { verdict: "skipped" } }),
runWorkflowStep: async () => ({ outcome: "success", data: { allPassed: true } }),
updateSteps: async (_ctx, _task, steps) => ({ outcome: "success", data: { count: steps.length } }),
transitionTask: async (ctx, task) => seams.schedule(task, ctx.node.context ?? {}),
requestMerge: async (ctx, task) => {
const result = await seams.merge(task, ctx.node.context ?? {});
return {
...result,
data: result.outcome === "success"
? { status: "merged" as const }
: { status: "failed" as const, reason: result.value ?? "merge-failed" },
};
},
abortRun: async () => ({ outcome: "success" }),
audit: () => undefined,
};
}
export class WorkflowAuthoritativeDriver {
public constructor(private readonly deps: WorkflowAuthoritativeDriverDeps) {}
public async maybeRun(task: Task): Promise<WorkflowAuthoritativeDriverResult> {
let settings: Settings;
let paritySummary: WorkflowParitySummary | undefined;
try {
settings = await this.deps.store.getSettings();
paritySummary = this.deps.store.getWorkflowParitySummary?.();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
executorLog.warn(`[workflow-authoritative] ${task.id}: readiness probe failed — falling back to legacy (${message})`);
return {
handled: false,
disposition: "fell-back",
reason: `store-unavailable: ${message}`,
readinessReasons: ["workflow parity readiness probe unavailable"],
};
}
const authoritativeFlagEnabled = isExperimentalFeatureEnabled(
settings,
WORKFLOW_INTERPRETER_AUTHORITATIVE_FLAG,
);
const dualObserveEnabled = isExperimentalFeatureEnabled(
settings,
WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG,
);
const readiness = evaluateInterpreterCutoverReadiness({
authoritativeFlagEnabled,
dualObserveEnabled,
paritySummary,
minimumObservedRuns: this.deps.minimumObservedRuns,
});
if (!readiness.ready) {
return {
handled: false,
disposition: "fell-back",
reason: readiness.reasons.join("; "),
readinessReasons: readiness.reasons,
};
}
const existingSelection = this.deps.store.getTaskWorkflowSelection?.(task.id);
if (existingSelection) {
return {
handled: false,
disposition: "fell-back",
reason: `workflow selection already present (${existingSelection.workflowId})`,
readinessReasons: [],
};
}
let liveTask: TaskDetail;
try {
liveTask = await this.deps.store.getTask(task.id);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
executorLog.warn(`[workflow-authoritative] ${task.id}: failed to load live task — falling back to legacy (${message})`);
return {
handled: false,
disposition: "fell-back",
reason: `task-load-failed: ${message}`,
readinessReasons: [],
};
}
const seams = this.deps.executor.createAuthoritativeWorkflowSeams(settings);
const runner = new WorkflowGraphTaskRunner({
store: {
getTaskWorkflowSelection: () => ({ workflowId: AUTHORITATIVE_WORKFLOW_ID, stepIds: [] }),
getWorkflowDefinition: async () => ({
id: AUTHORITATIVE_WORKFLOW_ID,
name: "Workflow interpreter authoritative cutover",
ir: BUILTIN_CODING_WORKFLOW_IR,
} satisfies Pick<WorkflowDefinition, "id" | "name" | "ir"> as WorkflowDefinition),
},
primitives: this.deps.executor.createAuthoritativeWorkflowPrimitives?.(settings) ?? primitivesFromLegacySeams(seams),
seams,
runCustomNode: async (node) => {
throw new Error(`unexpected custom node in builtin authoritative workflow: ${node.id}`);
},
onEvent: (event) => {
executorLog.log(`[workflow-authoritative] ${event.type} ${event.taskId}: ${event.detail}`);
},
});
const graphResult = await runner.run(liveTask, buildAuthoritativeSettings(settings));
if (graphResult.disposition === "fell-back") {
return {
handled: false,
disposition: "fell-back",
reason: graphResult.reason,
readinessReasons: [],
graphResult,
};
}
return {
handled: true,
disposition: graphResult.disposition,
reason: graphResult.reason,
readinessReasons: [],
graphResult,
};
}
}