fix(FN-7952): finish async workflow selection cutover
Use PostgreSQL workflow selections in the dashboard TUI, authoritative driver, and graph-runner adapter so migrated tasks cannot silently fall back to the coding workflow.
This commit is contained in:
@@ -1031,7 +1031,11 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
): Promise<ResolvedColumnInfo> {
|
||||
if (!flagOn) return {};
|
||||
try {
|
||||
const selection = projectStore.getTaskWorkflowSelection(task.id);
|
||||
/*
|
||||
FNXC:WorkflowSelection 2026-07-14-17:06:
|
||||
The dashboard TUI must resolve task workflow selections through the asynchronous store API so PostgreSQL-backed projects retain custom workflow column names and trait flags. The synchronous compatibility method has no backend result and is reserved for legacy test doubles.
|
||||
*/
|
||||
const selection = await projectStore.getTaskWorkflowSelectionAsync(task.id);
|
||||
const workflowId = selection?.workflowId;
|
||||
let columns = workflowIrCache.get(workflowId);
|
||||
if (columns === undefined) {
|
||||
@@ -3175,7 +3179,11 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
try {
|
||||
const values = (t as { customFields?: Record<string, unknown> }).customFields;
|
||||
if (values && Object.keys(values).length > 0) {
|
||||
const selection = projectStore.getTaskWorkflowSelection(t.id);
|
||||
/*
|
||||
FNXC:WorkflowSelection 2026-07-14-17:06:
|
||||
Task-detail custom-field chips must use the asynchronous workflow-selection read so PostgreSQL tasks render fields declared by their selected workflow.
|
||||
*/
|
||||
const selection = await projectStore.getTaskWorkflowSelectionAsync(t.id);
|
||||
const def = selection?.workflowId
|
||||
? await projectStore.getWorkflowDefinition(selection.workflowId)
|
||||
: undefined;
|
||||
|
||||
@@ -5135,7 +5135,10 @@ export class TaskExecutor {
|
||||
*/
|
||||
settings = { ...settings };
|
||||
let selection: { workflowId: string; stepIds: string[] } | undefined;
|
||||
if (typeof this.store.getTaskWorkflowSelection !== "function") {
|
||||
if (
|
||||
typeof this.store.getTaskWorkflowSelectionAsync !== "function"
|
||||
&& typeof this.store.getTaskWorkflowSelection !== "function"
|
||||
) {
|
||||
/*
|
||||
FNXC:WorkflowExecution 2026-06-23-22:01:
|
||||
Graph execution is the default for production TaskStore implementations, which expose workflow-selection APIs. Minimal test stores and older embedded adapters can lack that API; fall back to the legacy executor instead of half-entering graph routing with no workflow persistence surface.
|
||||
@@ -5159,7 +5162,7 @@ export class TaskExecutor {
|
||||
disposition: "failed",
|
||||
outcome: "failure",
|
||||
reason:
|
||||
"workflow-selection-api-unavailable: store lacks getTaskWorkflowSelection so the workflow graph cannot run "
|
||||
"workflow-selection-api-unavailable: store lacks a workflow-selection reader so the workflow graph cannot run "
|
||||
+ `${gateTask.enabledWorkflowSteps?.length ?? 0} enabled pre-merge workflow step(s); the legacy runWorkflowSteps path was removed (U4). Failing closed rather than skipping gates (KTD-5).`,
|
||||
visitedNodeIds: [],
|
||||
});
|
||||
@@ -5249,8 +5252,12 @@ export class TaskExecutor {
|
||||
const runner = new WorkflowGraphTaskRunner({
|
||||
store: {
|
||||
...this.store,
|
||||
getTaskWorkflowSelection: (taskId: string) =>
|
||||
this.store.getTaskWorkflowSelection?.(taskId) ?? { workflowId: "builtin:coding", stepIds: [] },
|
||||
/*
|
||||
FNXC:WorkflowSelection 2026-07-14-17:06:
|
||||
Graph execution must reuse the asynchronously resolved selection. A PostgreSQL TaskStore cannot provide that selection through the synchronous compatibility method, and substituting builtin:coding here would silently execute the wrong graph.
|
||||
*/
|
||||
getTaskWorkflowSelection: () => selection,
|
||||
getTaskWorkflowSelectionAsync: async () => selection,
|
||||
getWorkflowDefinition: async (id: string) =>
|
||||
(await this.store.getWorkflowDefinition?.(id))
|
||||
?? (id === "builtin:coding" ? getBuiltinWorkflow("builtin:coding") : undefined),
|
||||
@@ -5940,7 +5947,10 @@ export class TaskExecutor {
|
||||
*/
|
||||
private async maybeObserveWorkflowParity(taskId: string, settings: Settings): Promise<void> {
|
||||
if (!isExperimentalFeatureEnabled(settings, WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG)) return;
|
||||
if (typeof this.store.getTaskWorkflowSelection !== "function") return;
|
||||
if (
|
||||
typeof this.store.getTaskWorkflowSelectionAsync !== "function"
|
||||
&& typeof this.store.getTaskWorkflowSelection !== "function"
|
||||
) return;
|
||||
try {
|
||||
const selection = typeof this.store.getTaskWorkflowSelectionAsync === "function"
|
||||
? await this.store.getTaskWorkflowSelectionAsync(taskId)
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface WorkflowAuthoritativeDriverStore {
|
||||
getSettings(): Promise<Settings>;
|
||||
getTask(taskId: string): Promise<TaskDetail>;
|
||||
getTaskWorkflowSelection?(taskId: string): { workflowId: string; stepIds: string[] } | undefined;
|
||||
getTaskWorkflowSelectionAsync?(taskId: string): Promise<{ workflowId: string; stepIds: string[] } | undefined>;
|
||||
getWorkflowParitySummary?(options?: { since?: string; limit?: number }): WorkflowParitySummary;
|
||||
}
|
||||
|
||||
@@ -160,7 +161,13 @@ export class WorkflowAuthoritativeDriver {
|
||||
};
|
||||
}
|
||||
|
||||
const existingSelection = this.deps.store.getTaskWorkflowSelection?.(task.id);
|
||||
/*
|
||||
FNXC:WorkflowSelection 2026-07-14-17:06:
|
||||
The authoritative cutover guard must await PostgreSQL workflow-selection storage before deciding a task is unselected. Keep the synchronous branch only for narrow legacy/in-memory driver doubles.
|
||||
*/
|
||||
const existingSelection = this.deps.store.getTaskWorkflowSelectionAsync
|
||||
? await this.deps.store.getTaskWorkflowSelectionAsync(task.id)
|
||||
: this.deps.store.getTaskWorkflowSelection?.(task.id);
|
||||
if (existingSelection) {
|
||||
return {
|
||||
handled: false,
|
||||
|
||||
Reference in New Issue
Block a user