Files
fusion/packages/dashboard/app/utils/__tests__/releaseGate.test.ts
gsxdsm f6518b4275 FN-9029: keep release-gate verdicts transient
Keep release-gate verdicts restricted to fresh task-list responses.

- Strip release-gate data from SSE and non-board task paths.
- Reject stale initial verdicts before rendering promote controls.
- Cover server persistence, SSE payload, and client freshness boundaries.

Files changed:
 .../dashboard/app/hooks/__tests__/useTasks.test.ts | 115 +++++++++++++++++++++
 packages/dashboard/app/hooks/useTasks.ts           |  80 +++++++++-----
 .../app/utils/__tests__/releaseGate.test.ts        |   9 ++
 .../routes-tasks-release-gate-transient.test.ts    | 103 ++++++++++++++++++
 .../src/__tests__/sse-task-deleted-payload.test.ts |  10 +-
 packages/dashboard/src/sse.ts                      |  26 +++--
 6 files changed, 305 insertions(+), 38 deletions(-)

Fusion-Task-Id: FN-9029

Fusion-Task-Lineage: cf764f3e-c6f5-4fa1-b608-ebb8d547cfdd

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-08-13 15:43:16 -07:00

59 lines
2.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
RELEASE_GATE_VERDICT_MAX_AGE_MS,
isReleaseGateVerdictFresh,
releaseGateEvidenceFingerprint,
} from "../releaseGate";
const task = {
id: "FN-8987",
description: "test",
column: "todo",
status: null,
paused: false,
workflowIrPin: "builtin:coding",
enabledWorkflowSteps: undefined,
workflowStepResults: [],
steps: [{ name: "Plan", status: "pending" }],
updatedAt: "2026-08-11T20:00:00.000Z",
} as any;
const verdict = {
promoteBlocked: false,
unplannedForExecution: false,
blockedOnApproval: false,
reason: null,
readyAtCapacityBoundary: false,
evaluatedAt: "2026-08-11T20:00:00.000Z",
evaluatedForUpdatedAt: task.updatedAt,
} as const;
describe("release-gate freshness", () => {
it("fingerprints every browser-visible release input deterministically", () => {
const fingerprint = releaseGateEvidenceFingerprint(task);
expect(releaseGateEvidenceFingerprint({ ...task, enabledWorkflowSteps: ["code-review", "plan-review"] })).toBe(
releaseGateEvidenceFingerprint({ ...task, enabledWorkflowSteps: ["plan-review", "code-review"] }),
);
expect(releaseGateEvidenceFingerprint({ ...task, enabledWorkflowSteps: [] })).not.toBe(fingerprint);
expect(releaseGateEvidenceFingerprint({ ...task, pausedReason: "awaiting-approval" })).not.toBe(fingerprint);
expect(releaseGateEvidenceFingerprint({ ...task, workflowStepResults: [{ workflowStepId: "plan-review", status: "passed" }] })).not.toBe(fingerprint);
});
it("drops a verdict when its row clock, evidence, or bounded age becomes unsafe", () => {
const provenance = { fingerprint: releaseGateEvidenceFingerprint(task), capturedAt: 0 };
const now = Date.parse(verdict.evaluatedAt);
expect(isReleaseGateVerdictFresh(verdict, task, provenance, now)).toBe(true);
expect(isReleaseGateVerdictFresh(verdict, { ...task, status: "planning" }, provenance, now)).toBe(false);
expect(isReleaseGateVerdictFresh(verdict, { ...task, updatedAt: "2026-08-11T20:00:00.001Z" }, provenance, now)).toBe(false);
expect(isReleaseGateVerdictFresh(verdict, task, provenance, now + RELEASE_GATE_VERDICT_MAX_AGE_MS)).toBe(true);
expect(isReleaseGateVerdictFresh(verdict, task, provenance, now + RELEASE_GATE_VERDICT_MAX_AGE_MS + 1)).toBe(false);
});
it("requires local provenance, a parseable timestamp, and the evaluated row clock", () => {
const provenance = { fingerprint: releaseGateEvidenceFingerprint(task), capturedAt: 0 };
const now = Date.parse(verdict.evaluatedAt);
expect(isReleaseGateVerdictFresh(verdict, task, undefined, now)).toBe(false);
expect(isReleaseGateVerdictFresh({ ...verdict, evaluatedAt: "not-a-date" }, task, provenance, now)).toBe(false);
expect(isReleaseGateVerdictFresh({ ...verdict, evaluatedForUpdatedAt: undefined }, task, provenance, now)).toBe(false);
});
});