FN-5768: add dual-observe workflow parity instrumentation

Add default-off workflow interpreter dual-observe parity instrumentation across core and engine.

- add workflow parity comparison primitives and exports in @fusion/core
- add engine parity observer seam with experimental flag and public exports
- add regression coverage for parity contracts and dual-observe reliability behavior
- document the dual-observe parity mode, events, and contract in workflow docs
- add a patch changeset for @runfusion/fusion release notes

Files changed:
 .changeset/fn-5768-workflow-parity-observer.md     |   9 +
 docs/workflow-steps.md                             |  18 ++
 .../core/src/__tests__/workflow-parity.test.ts     | 143 ++++++++++++++
 packages/core/src/index.ts                         |  18 ++
 packages/core/src/workflow-parity.ts               | 205 +++++++++++++++++++++
 .../workflow-interpreter-dual-observe.test.ts      | 144 +++++++++++++++
 packages/engine/src/index.ts                       |   7 +
 packages/engine/src/workflow-parity-observer.ts    | 116 ++++++++++++
 8 files changed, 660 insertions(+)

Fusion-Task-Id: FN-5768

Fusion-Task-Lineage: fb4f8111-f48d-482c-b1d9-9106c43c829a
This commit is contained in:
gsxdsm
2026-05-31 07:55:18 -07:00
parent 5c33ab133e
commit 5b4eecb5d4
8 changed files with 660 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
import { describe, expect, it, vi } from "vitest";
import {
observeWorkflowParity,
WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG,
} from "../../workflow-parity-observer.js";
import type { WorkflowRunObservation } from "@fusion/core";
const baseObservation: WorkflowRunObservation = {
stageTransitions: ["triage", "execute", "review", "merge"],
terminalColumn: "done",
terminalStatus: null,
reviewVerdict: "APPROVE",
mergeOutcome: "merged",
invariants: {
fileScopeGuardOutcome: "pass",
squashMergeContractOutcome: "pass",
autoMergeTerminalUntilMergedRespected: true,
moveTaskHardCancelRespected: true,
},
};
describe("FN-5768 workflow interpreter dual-observe", () => {
it("is strict no-op when flag is off", async () => {
const recordRunAuditEvent = vi.fn();
const runShadow = vi.fn();
await observeWorkflowParity({
settings: { experimentalFeatures: {} },
store: { recordRunAuditEvent },
agentId: "executor",
legacy: {
taskId: "FN-1",
observation: baseObservation,
auditEvents: [],
},
runShadow,
});
expect(runShadow).not.toHaveBeenCalled();
expect(recordRunAuditEvent).not.toHaveBeenCalled();
});
it("records parity-observed agree=true when observations match", async () => {
const recordRunAuditEvent = vi.fn().mockResolvedValue(undefined);
await observeWorkflowParity({
settings: { experimentalFeatures: { [WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG]: true } },
store: { recordRunAuditEvent },
agentId: "executor",
legacy: {
taskId: "FN-2",
observation: baseObservation,
auditEvents: [],
},
runShadow: async () => ({ observation: baseObservation, auditEvents: [] }),
});
expect(recordRunAuditEvent).toHaveBeenCalledTimes(1);
expect(recordRunAuditEvent).toHaveBeenCalledWith(
expect.objectContaining({
mutationType: "workflow:parity-observed",
metadata: expect.objectContaining({ agree: true }),
}),
);
});
it("records parity drift and keeps authoritative result unchanged", async () => {
const recordRunAuditEvent = vi.fn().mockResolvedValue(undefined);
const legacyResult = { authoritative: true };
await observeWorkflowParity({
settings: { experimentalFeatures: { [WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG]: true } },
store: { recordRunAuditEvent },
agentId: "executor",
legacy: {
taskId: "FN-3",
observation: baseObservation,
auditEvents: [],
},
runShadow: async () => ({
observation: {
...baseObservation,
terminalColumn: "in-review",
},
auditEvents: [],
}),
});
expect(legacyResult).toEqual({ authoritative: true });
expect(recordRunAuditEvent).toHaveBeenCalledTimes(2);
expect(recordRunAuditEvent).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ mutationType: "workflow:parity-observed" }),
);
expect(recordRunAuditEvent).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
mutationType: "workflow:parity-drift",
metadata: expect.objectContaining({
agree: false,
diffs: expect.arrayContaining([expect.objectContaining({ field: "terminalColumn" })]),
}),
}),
);
});
it("captures shadow errors fail-soft without rethrow", async () => {
const recordRunAuditEvent = vi.fn().mockResolvedValue(undefined);
await expect(
observeWorkflowParity({
settings: { experimentalFeatures: { [WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG]: true } },
store: { recordRunAuditEvent },
agentId: "executor",
legacy: {
taskId: "FN-4",
observation: baseObservation,
auditEvents: [],
},
runShadow: async () => {
throw new Error("shadow exploded");
},
}),
).resolves.toBeUndefined();
expect(recordRunAuditEvent).toHaveBeenCalledTimes(2);
expect(recordRunAuditEvent).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
mutationType: "workflow:parity-observed",
metadata: expect.objectContaining({ agree: false }),
}),
);
expect(recordRunAuditEvent).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
mutationType: "workflow:parity-drift",
metadata: expect.objectContaining({
diffs: expect.arrayContaining([expect.objectContaining({ field: "shadow.error" })]),
}),
}),
);
});
});

View File

@@ -84,6 +84,13 @@ export {
export {
generateSyntheticRunId,
} from "./run-audit.js";
export {
observeWorkflowParity,
WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG,
type WorkflowParityObserverInput,
type WorkflowParityObserverLegacyRunResult,
type WorkflowParityObserverShadowRunResult,
} from "./workflow-parity-observer.js";
export {
auditSquashMerge,
formatSquashAuditReport,

View File

@@ -0,0 +1,116 @@
import {
compareWorkflowRunAudits,
compareWorkflowRunObservations,
isExperimentalFeatureEnabled,
WORKFLOW_PARITY_DRIFT_MUTATION,
WORKFLOW_PARITY_OBSERVED_MUTATION,
type RunAuditEvent,
type Settings,
type TaskStore,
type WorkflowParityDiff,
type WorkflowRunObservation,
} from "@fusion/core";
import { generateSyntheticRunId } from "./run-audit.js";
export const WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG = "workflowInterpreterDualObserve" as const;
export interface WorkflowParityObserverLegacyRunResult {
taskId: string;
observation: WorkflowRunObservation;
auditEvents: RunAuditEvent[];
}
export interface WorkflowParityObserverShadowRunResult {
observation: WorkflowRunObservation;
auditEvents: RunAuditEvent[];
}
export interface WorkflowParityObserverInput {
settings: Pick<Settings, "experimentalFeatures"> | undefined;
store: Pick<TaskStore, "recordRunAuditEvent">;
agentId: string;
legacy: WorkflowParityObserverLegacyRunResult;
runShadow: () => Promise<WorkflowParityObserverShadowRunResult>;
}
function buildErrorDiff(error: unknown): WorkflowParityDiff {
return {
field: "shadow.error",
legacy: null,
interpreter: error instanceof Error ? error.message : String(error),
category: "audit",
severity: "error",
};
}
/**
* Observe-only parity seam. Never mutates/blocks authoritative legacy behavior.
*/
export async function observeWorkflowParity(input: WorkflowParityObserverInput): Promise<void> {
if (!isExperimentalFeatureEnabled(input.settings, WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG)) {
return;
}
const { store, agentId, legacy } = input;
const runId = generateSyntheticRunId("workflow-shadow", legacy.taskId);
try {
const shadow = await input.runShadow();
const observationReport = compareWorkflowRunObservations(legacy.observation, shadow.observation);
const auditReport = compareWorkflowRunAudits(legacy.auditEvents, shadow.auditEvents);
const diffs = [...observationReport.diffs, ...auditReport.diffs];
const agree = diffs.length === 0;
await store.recordRunAuditEvent?.({
taskId: legacy.taskId,
agentId,
runId,
domain: "database",
mutationType: WORKFLOW_PARITY_OBSERVED_MUTATION,
target: legacy.taskId,
metadata: {
agree,
},
});
if (!agree) {
await store.recordRunAuditEvent?.({
taskId: legacy.taskId,
agentId,
runId,
domain: "database",
mutationType: WORKFLOW_PARITY_DRIFT_MUTATION,
target: legacy.taskId,
metadata: {
agree,
diffs,
},
});
}
} catch (error) {
await store.recordRunAuditEvent?.({
taskId: legacy.taskId,
agentId,
runId,
domain: "database",
mutationType: WORKFLOW_PARITY_OBSERVED_MUTATION,
target: legacy.taskId,
metadata: {
agree: false,
},
});
await store.recordRunAuditEvent?.({
taskId: legacy.taskId,
agentId,
runId,
domain: "database",
mutationType: WORKFLOW_PARITY_DRIFT_MUTATION,
target: legacy.taskId,
metadata: {
agree: false,
diffs: [buildErrorDiff(error)],
},
});
}
}