FN-5742: add dual-observe merge-request parity seam

Add a dual-observe merge-request seam that preserves legacy authority while emitting parity signals.

- Add scheduler parity helpers for dependency satisfaction and shadow lease state, keeping legacy scheduling decisions authoritative.
- Extend ProjectEngine/run-audit merge dequeue flow with shadow-candidate selection and parity audit emission.
- Add reliability interaction coverage for dual-observe dependency parity, shadow dequeue parity, lease parity, and runnable guard behavior.
- Document FN-5742 reliability backstop coverage in architecture docs.

Files changed:
 docs/architecture.md                               |   2 +
 packages/engine/src/__tests__/reliability-interactions/dual-observe-merge-seam.test.ts                | 193 +++++++++++++++++++++
 packages/engine/src/__tests__/scheduler.test.ts    |  64 +++++++
 packages/engine/src/project-engine.ts              |  42 +++++
 packages/engine/src/run-audit.ts                   |   3 +
 packages/engine/src/scheduler.ts                   | 158 +++++++++++++++--
 6 files changed, 448 insertions(+), 14 deletions(-)

Fusion-Task-Id: FN-5742

Fusion-Task-Lineage: dc4c0212-fe75-4860-bf06-2765a0b18d38
This commit is contained in:
gsxdsm
2026-05-30 18:52:19 -07:00
parent c0b749cee9
commit 8609669a56
6 changed files with 448 additions and 14 deletions

View File

@@ -0,0 +1,193 @@
import { describe, expect, it, vi } from "vitest";
import {
computeShadowLeaseParityState,
getUnmetSchedulingDependencies,
isRunnableQueuedOverlapCandidate,
} from "../../scheduler.js";
import { ProjectEngine } from "../../project-engine.js";
import { classifyTransientMergeError } from "../../transient-merge-error-classifier.js";
describe("FN-5742 dual-observe merge seam", () => {
it("emits no dependency parity diff when legacy and marker agree", () => {
const task = { id: "FN-T", dependencies: ["FN-DEP"] } as any;
const dep = { id: "FN-DEP", column: "done" } as any;
const onParityDiff = vi.fn();
const unmet = getUnmetSchedulingDependencies(task, [task, dep], {
markerAcceptedByTaskId: new Map([["FN-DEP", true]]),
onParityDiff,
});
expect(unmet).toEqual([]);
expect(onParityDiff).not.toHaveBeenCalled();
});
it("keeps legacy dependency satisfaction authoritative while emitting parity diff", () => {
const task = { id: "FN-T", dependencies: ["FN-DEP"] } as any;
const dep = { id: "FN-DEP", column: "in-review" } as any;
const onParityDiff = vi.fn();
const unmet = getUnmetSchedulingDependencies(task, [task, dep], {
markerAcceptedByTaskId: new Map([["FN-DEP", false]]),
onParityDiff,
});
expect(unmet).toEqual([]);
expect(onParityDiff).toHaveBeenCalledWith(
expect.objectContaining({
taskId: "FN-T",
dependencyId: "FN-DEP",
legacySatisfied: true,
markerSatisfied: false,
}),
);
});
it("shadow dequeue selector skips manual-required records", () => {
const fakeEngine = {
mergeQueue: ["FN-A", "FN-B", "FN-C"],
runtime: {
getTaskStore: () => ({
getMergeRequestRecord: (taskId: string) => {
if (taskId === "FN-A") return { state: "manual-required" };
if (taskId === "FN-B") return { state: "queued" };
return null;
},
}),
},
};
const candidate = (ProjectEngine.prototype as any).getShadowMergeRequestCandidateId.call(fakeEngine);
expect(candidate).toBe("FN-B");
});
it("emits shadow dequeue parity audit with agree metadata", () => {
const recordRunAuditEvent = vi.fn();
const fakeEngine = {
runtime: {
getTaskStore: () => ({ recordRunAuditEvent }),
},
};
(ProjectEngine.prototype as any).emitMergeRequestShadowDequeueParity.call(fakeEngine, "FN-LEGACY", "FN-SHADOW");
expect(recordRunAuditEvent).toHaveBeenCalledWith(
expect.objectContaining({
taskId: "FN-LEGACY",
mutationType: "merge:request-dequeued-shadow",
metadata: expect.objectContaining({
legacyTaskId: "FN-LEGACY",
shadowTaskId: "FN-SHADOW",
agree: false,
}),
}),
);
});
it("marks dequeue parity as agree when legacy and shadow match", () => {
const recordRunAuditEvent = vi.fn();
const fakeEngine = {
runtime: {
getTaskStore: () => ({ recordRunAuditEvent }),
},
};
(ProjectEngine.prototype as any).emitMergeRequestShadowDequeueParity.call(fakeEngine, "FN-LEGACY", "FN-LEGACY");
expect(recordRunAuditEvent).toHaveBeenCalledWith(
expect.objectContaining({
mutationType: "merge:request-dequeued-shadow",
metadata: expect.objectContaining({
agree: true,
}),
}),
);
});
it("is a no-op when shadow dequeue API is unavailable", () => {
const fakeEngine = {
mergeQueue: ["FN-A"],
runtime: {
getTaskStore: () => ({}),
},
};
const candidate = (ProjectEngine.prototype as any).getShadowMergeRequestCandidateId.call(fakeEngine);
expect(candidate).toBeNull();
});
it("computes lease parity so manual-required does not create a merge lock", () => {
const parity = computeShadowLeaseParityState("manual-required");
expect(parity.shadowExecutorLeaseApplied).toBe(false);
expect(parity.shadowMergeLockApplied).toBe(false);
expect(parity.shadowLeaseApplied).toBe(false);
});
it("computes lease parity so queued requests create a merge lock", () => {
const parity = computeShadowLeaseParityState("queued");
expect(parity.shadowExecutorLeaseApplied).toBe(false);
expect(parity.shadowMergeLockApplied).toBe(true);
expect(parity.shadowLeaseApplied).toBe(true);
});
it("keeps dependency checks unchanged when parity observer options are absent", () => {
const task = { id: "FN-T", dependencies: ["FN-DEP"] } as any;
const dep = { id: "FN-DEP", column: "in-review" } as any;
const unmet = getUnmetSchedulingDependencies(task, [task, dep]);
expect(unmet).toEqual([]);
});
it("does not block unrelated executor dispatch when merge lane is busy", () => {
const now = Date.now();
const activeScopes = new Map<string, string[]>([["FN-MERGE", ["packages/engine/src/merger.ts"]]]);
const todo = {
id: "FN-UNRELATED",
column: "todo",
status: "queued",
paused: false,
userPaused: false,
dependencies: [],
nextRecoveryAt: undefined,
} as any;
const runnable = isRunnableQueuedOverlapCandidate(todo, [todo], now, activeScopes, ["docs/architecture.md"]);
expect(runnable).toBe(true);
});
it("classifies transient merge errors without changing scheduler runnable decisions", () => {
const transient = classifyTransientMergeError(
"lease-handoff-failed: target-not-queued while attempting merge handoff",
);
expect(transient).toBe("lease-handoff-target-not-queued");
const todo = {
id: "FN-RUN",
column: "todo",
status: "queued",
paused: false,
userPaused: false,
dependencies: [],
nextRecoveryAt: undefined,
} as any;
expect(isRunnableQueuedOverlapCandidate(todo, [todo], Date.now())).toBe(true);
});
it("shadow dequeue helpers do not touch limbo recovery counters", () => {
const updateTask = vi.fn();
const fakeEngine = {
runtime: {
getTaskStore: () => ({
recordRunAuditEvent: vi.fn(),
updateTask,
}),
},
mergeQueue: ["FN-A"],
};
(ProjectEngine.prototype as any).emitMergeRequestShadowDequeueParity.call(fakeEngine, "FN-A", "FN-A");
(ProjectEngine.prototype as any).getShadowMergeRequestCandidateId.call(fakeEngine);
expect(updateTask).not.toHaveBeenCalled();
});
});