Files
fusion/packages/engine/src/workflow-parity-observer.ts
gsxdsm 5b4eecb5d4 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
2026-05-31 08:32:01 -07:00

117 lines
3.2 KiB
TypeScript

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)],
},
});
}
}