Files
fusion/packages/engine/src/runtime-primitives.ts
gsxdsm f987470e6d refactor(FN-7039): delete legacy runWorkflowSteps execution path; graph is sole executor
Removes the legacy workflow-step EXECUTION path now that the graph records results
(U2): delete runWorkflowSteps(), the workflow-step seam + runWorkflowStep primitive
(runtime-primitives, workflow-node-handlers, authoritative-driver), and the legacy
execute() step blocks. Keeps task.workflowStepResults + its store write path (the
graph's sink) and executeWorkflowStep/executeScriptWorkflowStep (reused by the graph).

- Watchdog recoverCompletedTask now re-enters via maybeExecuteWorkflowGraph so the
  graph re-runs pending gates, records results, and owns the in-review/back-for-fix
  transition (KTD-2).
- maybeExecuteWorkflowGraph fails CLOSED (parks) when a store lacks
  getTaskWorkflowSelection AND the task has enabled pre-merge steps — closing the
  FN-7039 silent-skip class without changing minimal-store implementation runs (KTD-5).

KNOWN GAP (follow-up): the FN-4343 per-step workflowStepScopeEnforcement leak check
lived only in runWorkflowSteps and is NOT yet replicated on the graph path. Merge-time
File Scope enforcement (FileScopeViolationError, squash overlap) is unaffected.

Plan U4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 23:25:23 -07:00

225 lines
5.5 KiB
TypeScript

import type { TaskDetail, TaskStep, WorkflowIrNode } from "@fusion/core";
import type { PrMergeCallResult } from "./pr-nodes.js";
import type { RunTaskStepResult, ResetStepResult } from "./step-runner.js";
import type { WorkflowNodeOutcome } from "./workflow-graph-executor.js";
export type RuntimePrimitiveName =
| "prepare-worktree"
| "read-artifact"
| "write-artifact"
| "planning-session"
| "coding-session"
| "step-session"
| "reset-step"
| "review"
| "verification"
| "transition"
| "merge"
| "abort"
| "audit";
export interface WorkflowRuntimeRunContext {
runId: string;
taskId: string;
workflowId: string;
/** True after any primitive with task/git/session side effects starts. */
sideEffectsStarted?: boolean;
recoveryEventId?: string;
}
export interface WorkflowRuntimeNodeContext {
node: Pick<WorkflowIrNode, "id" | "kind" | "column" | "config">;
effectivePrincipalId?: string;
attempt?: number;
context?: Record<string, unknown>;
}
export interface WorkflowPrimitiveContext {
run: WorkflowRuntimeRunContext;
node: WorkflowRuntimeNodeContext;
}
export interface RuntimePrimitiveResult<TValue = unknown> {
outcome: WorkflowNodeOutcome;
value?: string;
data?: TValue;
contextPatch?: Record<string, unknown>;
}
export interface PreparedWorktree {
worktreePath: string;
branchName?: string;
baseCommitSha?: string;
modifiedFiles?: string[];
}
export interface PlanningSessionResult {
approved: boolean;
artifactKeys: string[];
createdTaskIds?: string[];
feedback?: string;
}
export interface CodingSessionResult {
taskDone: boolean;
modifiedFiles: string[];
summary?: string;
}
export interface ReviewPrimitiveResult {
verdict: "APPROVE" | "REVISE" | "RETHINK" | "UNAVAILABLE";
review?: string;
summary?: string;
}
export interface VerificationPrimitiveResult {
verdict: "approve" | "revise" | "failed" | "advisory-failed" | "skipped";
feedback?: string;
stepName?: string;
}
// FNXC:WorkflowExecution 2026-06-25-00:00: U4 (KTD-2) — the `runWorkflowStep`
// primitive and its `WorkflowStepPrimitiveInput`/`WorkflowStepPrimitiveResult`
// shapes were removed. Workflow quality gates run as the graph's own
// optional-group / gate nodes which record into `task.workflowStepResults` (U2);
// there is no dedicated workflow-step runtime primitive.
export interface TransitionPrimitiveInput {
column?: string;
status?: string | null;
reason: string;
preserveProgress?: boolean;
}
export interface MergePrimitiveInput {
expectedHeadOid?: string;
manualAllowed?: boolean;
}
export type MergePrimitiveResult =
| { status: "merged"; noOp?: boolean }
| { status: "manual-required"; reason?: string }
| { status: "failed"; reason: string }
| { status: "timeout" }
| PrMergeCallResult;
export interface AbortPrimitiveInput {
reason: string;
hardCancel?: boolean;
}
export interface AuditPrimitiveInput {
type: string;
message: string;
metadata?: Record<string, unknown>;
}
export interface WorkflowRuntimePrimitives {
prepareWorktree(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
): Promise<RuntimePrimitiveResult<PreparedWorktree>>;
readArtifact(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
key: string,
): Promise<string | undefined>;
writeArtifact(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
key: string,
content: string,
): Promise<RuntimePrimitiveResult<{ key: string }>>;
runPlanningSession(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
): Promise<RuntimePrimitiveResult<PlanningSessionResult>>;
runCodingSession(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
prepared: PreparedWorktree,
): Promise<RuntimePrimitiveResult<CodingSessionResult>>;
runTaskStep(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
stepIndex: number,
): Promise<RunTaskStepResult>;
resetTaskStep(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
stepIndex: number,
baselineSha?: string,
checkpointId?: string,
): Promise<ResetStepResult>;
runReview(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
input: { type: "plan" | "code"; stepIndex?: number; baselineSha?: string },
): Promise<RuntimePrimitiveResult<ReviewPrimitiveResult>>;
runVerification(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
prepared: PreparedWorktree,
): Promise<RuntimePrimitiveResult<VerificationPrimitiveResult>>;
updateSteps(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
steps: TaskStep[],
): Promise<RuntimePrimitiveResult<{ count: number }>>;
transitionTask(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
input: TransitionPrimitiveInput,
): Promise<RuntimePrimitiveResult>;
requestMerge(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
input?: MergePrimitiveInput,
): Promise<RuntimePrimitiveResult<MergePrimitiveResult>>;
abortRun(
ctx: WorkflowPrimitiveContext,
task: TaskDetail,
input: AbortPrimitiveInput,
): Promise<RuntimePrimitiveResult>;
audit(ctx: WorkflowPrimitiveContext, input: AuditPrimitiveInput): Promise<void> | void;
}
export function markSideEffectsStarted(ctx: WorkflowPrimitiveContext): WorkflowPrimitiveContext {
return {
...ctx,
run: {
...ctx.run,
sideEffectsStarted: true,
},
};
}
export function primitiveNodeContext(
run: WorkflowRuntimeRunContext,
node: WorkflowRuntimeNodeContext["node"],
extras: Omit<WorkflowRuntimeNodeContext, "node"> = {},
): WorkflowPrimitiveContext {
return {
run,
node: {
...extras,
node,
},
};
}