Files
fusion/packages/core/src/workflow-cutover.ts
gsxdsm eb3833a542 FN-6971: retire dual-observe cutover gating
Remove stale dual-observe prerequisites from workflow-authoritative readiness while preserving parity-summary safeguards.

- Require the authoritative flag plus clean populated parity summaries for interpreter cutover readiness.
- Keep persisted workflowInterpreterDualObserve values inert in runtime tests and documentation.
- Update cutover, parity, and graph-executor tests to reflect retired shadow observation behavior.
- Add a formatted patch changeset for the operator-facing cutover readiness fix.

Files changed:
 .changeset/fn-6971-workflow-cutover-readiness.md   |  7 ++
 docs/architecture.md                               |  2 +-
 docs/settings-reference.md                         |  4 +-
 docs/workflow-steps.md                             | 19 +++--
 .../core/src/__tests__/workflow-cutover.test.ts    | 19 +++--
 packages/core/src/workflow-cutover.ts              |  9 +--
 .../workflow-interpreter-cutover.test.ts           | 87 +++++++++++++++++++---
 .../workflow-interpreter-dual-observe.test.ts      | 76 ++++++-------------
 .../src/__tests__/stepwise-workflow-parity.test.ts | 25 ++++---
 .../engine/src/workflow-authoritative-driver.ts    | 10 +--
 10 files changed, 152 insertions(+), 106 deletions(-)

Fusion-Task-Id: FN-6971
Fusion-Task-Lineage: 363a441d-62e5-403c-9389-75fcf788352a
2026-06-24 23:32:52 -07:00

79 lines
3.1 KiB
TypeScript

import type { WorkflowParityDriftReport, WorkflowParitySummary } from "./workflow-parity.js";
/**
* Opt-in authoritative cutover flag for routing the coding lifecycle through the
* workflow interpreter. The cutover is guarded by rollout-readiness checks and
* remains reversible by disabling the flag.
*/
export const WORKFLOW_INTERPRETER_AUTHORITATIVE_FLAG = "workflowInterpreterAuthoritative" as const;
export interface InterpreterCutoverReadinessInput {
/** Explicit operator opt-in; default runtime remains legacy when false. */
authoritativeFlagEnabled: boolean;
/** Aggregated parity signal from the audit trail (for example `store.getWorkflowParitySummary()`). */
paritySummary?: Pick<WorkflowParitySummary, "observed" | "drift" | "recentDrift"> | null;
/** Optional unresolved drift reports surfaced directly by the caller. */
unresolvedDriftReports?: readonly Pick<WorkflowParityDriftReport, "agree" | "diffs">[] | null;
/** Minimum observed parity runs required before cutover may proceed. Default: 1. */
minimumObservedRuns?: number;
}
export interface InterpreterCutoverReadinessResult {
ready: boolean;
reasons: string[];
}
function normalizeMinimumObservedRuns(value: number | undefined): number {
if (!Number.isFinite(value)) return 1;
return Math.max(1, Math.floor(value!));
}
function countUnresolvedDriftReports(
reports: readonly Pick<WorkflowParityDriftReport, "agree" | "diffs">[] | null | undefined,
): number {
if (!reports || reports.length === 0) return 0;
return reports.filter((report) => report.agree === false || report.diffs.length > 0).length;
}
/**
* Pure rollout-readiness guard for the interpreter-authoritative cutover.
* Callers supply explicit parity evidence; this function performs no I/O.
*
* FNXC:WorkflowInterpreterCutover 2026-06-23-21:58:
* workflowInterpreterDualObserve is retired and inert. Authoritative cutover must use the explicit authoritative flag plus clean populated parity summaries as evidence, without reactivating hidden shadow observation.
*/
export function evaluateInterpreterCutoverReadiness(
input: InterpreterCutoverReadinessInput,
): InterpreterCutoverReadinessResult {
const reasons: string[] = [];
const minimumObservedRuns = normalizeMinimumObservedRuns(input.minimumObservedRuns);
if (!input.authoritativeFlagEnabled) {
reasons.push("experimentalFeatures.workflowInterpreterAuthoritative is disabled");
}
const paritySummary = input.paritySummary;
if (!paritySummary) {
reasons.push("workflow parity summary unavailable");
} else {
if (paritySummary.observed < minimumObservedRuns) {
reasons.push(
`workflow parity observation window too small (${paritySummary.observed}/${minimumObservedRuns} observed)`,
);
}
if (paritySummary.drift > 0) {
reasons.push(`workflow parity drift above zero (${paritySummary.drift} drift events)`);
}
}
const unresolvedDriftCount = countUnresolvedDriftReports(input.unresolvedDriftReports);
if (unresolvedDriftCount > 0) {
reasons.push(`workflow parity has unresolved drift reports (${unresolvedDriftCount})`);
}
return {
ready: reasons.length === 0,
reasons,
};
}