feat(FN-4608): complete Step 2 — handle empty-sample reliability days

Fusion-Task-Id: FN-4608
Fusion-Task-Lineage: 6cabc509-38a1-4cb8-be6f-51c53d5753ff
This commit is contained in:
Fusion
2026-05-15 09:32:10 -07:00
committed by gsxdsm
parent 18115553d6
commit fc5061afdd
3 changed files with 80 additions and 5 deletions

View File

@@ -4,6 +4,7 @@ import type { ActivityLogEntry, RunAuditEvent } from "@fusion/core";
import {
bucketByDay,
dayHasSamples,
fileScopeInvariantFailuresPerDay,
inReviewDurationMetrics,
inReviewFailureRate7d,
@@ -136,4 +137,52 @@ describe("reliability-metrics", () => {
expect(inReviewFailureRate7d({ "2026-05-13": 10 }, { "2026-05-13": 2 }, endMs)).toEqual({ value: 0.2 });
expect(inReviewFailureRate7d({}, {}, endMs)).toEqual({ value: null, reason: "no-in-review-entries" });
});
it("returns no-in-review-entries when all seven days are empty", () => {
const endMs = Date.parse("2026-05-13T00:00:00.000Z");
expect(inReviewFailureRate7d({ "2026-05-13": 0, "2026-05-12": 0 }, { "2026-05-13": 0 }, endMs)).toEqual({
value: null,
reason: "no-in-review-entries",
});
});
it("filters task movement counts by start/end window", () => {
const activity: ActivityLogEntry[] = [
moved("2026-05-10T23:59:59.000Z", "FN-1", "todo", "in-review"),
moved("2026-05-11T00:00:00.000Z", "FN-2", "todo", "in-review"),
moved("2026-05-12T00:00:00.000Z", "FN-3", "in-review", "in-progress"),
];
const start = Date.parse("2026-05-11T00:00:00.000Z");
const end = Date.parse("2026-05-12T00:00:00.000Z");
expect(tasksEnteredInReviewPerDay(activity, start, end)).toEqual({ "2026-05-11": 1 });
expect(tasksBouncedToInProgressPerDay(activity, start, end)).toEqual({ "2026-05-12": 1 });
});
it("reports hasSamples semantics for per-day rows", () => {
expect(dayHasSamples({
tasksEnteredInReview: 0,
tasksBouncedToInProgress: 0,
postMergeAuditFailures: null,
fileScopeInvariantFailures: null,
recoverAlreadyMergedReviewTasksRecoveries: null,
})).toBe(false);
expect(dayHasSamples({
tasksEnteredInReview: 0,
tasksBouncedToInProgress: 1,
postMergeAuditFailures: null,
fileScopeInvariantFailures: null,
recoverAlreadyMergedReviewTasksRecoveries: null,
})).toBe(true);
expect(dayHasSamples({
tasksEnteredInReview: 0,
tasksBouncedToInProgress: 0,
postMergeAuditFailures: { block: 0, warn: 1, off: 0 },
fileScopeInvariantFailures: 0,
recoverAlreadyMergedReviewTasksRecoveries: 0,
})).toBe(true);
});
});

View File

@@ -29,6 +29,14 @@ export interface InReviewDurationMetric {
reason?: NullMetricReason;
}
export interface ReliabilityPerDayCounts {
tasksEnteredInReview: number;
tasksBouncedToInProgress: number;
postMergeAuditFailures: { block: number; warn: number; off: number } | null;
fileScopeInvariantFailures: number | null;
recoverAlreadyMergedReviewTasksRecoveries: number | null;
}
const DAY_MS = 86_400_000;
export function bucketByDay(timestamp: string): string {
@@ -183,6 +191,21 @@ export function mergeAttemptsPerMergedTask(events: RunAuditEvent[], activity: Ac
};
}
export function dayHasSamples(counts: ReliabilityPerDayCounts): boolean {
if (counts.tasksEnteredInReview > 0 || counts.tasksBouncedToInProgress > 0) {
return true;
}
if (counts.postMergeAuditFailures) {
const { block, warn, off } = counts.postMergeAuditFailures;
if (block + warn + off > 0) {
return true;
}
}
return (counts.fileScopeInvariantFailures ?? 0) > 0 || (counts.recoverAlreadyMergedReviewTasksRecoveries ?? 0) > 0;
}
export function inReviewFailureRate7d(enteredByDay: Record<string, number>, bouncedByDay: Record<string, number>, endMs: number): NullableMetric<number> {
let entered = 0;
let bounced = 0;

View File

@@ -61,6 +61,7 @@ import { createAuthMiddleware, authenticateUpgradeRequest, getDaemonToken } from
import { validateRemoteAuthToken } from "./remote-auth.js";
import { getCliPackageVersion } from "./cli-package-version.js";
import {
dayHasSamples,
fileScopeInvariantFailuresPerDay,
inReviewDurationMetrics,
inReviewFailureRate7d,
@@ -1167,11 +1168,13 @@ export function createServer(store: TaskStore, options?: ServerOptions): ReturnT
const postMergeAuditFailures = postMergeByDay.value ? (postMergeByDay.value[day] ?? { block: 0, warn: 0, off: 0 }) : null;
const fileScopeInvariantFailures = fileScopeByDay.value ? (fileScopeByDay.value[day] ?? 0) : null;
const recoverAlreadyMergedReviewTasksRecoveries = recoveriesByDay.value ? (recoveriesByDay.value[day] ?? 0) : null;
const hasSamples = tasksEnteredInReview > 0
|| tasksBouncedToInProgress > 0
|| (postMergeAuditFailures ? postMergeAuditFailures.block + postMergeAuditFailures.warn + postMergeAuditFailures.off > 0 : false)
|| (typeof fileScopeInvariantFailures === "number" && fileScopeInvariantFailures > 0)
|| (typeof recoverAlreadyMergedReviewTasksRecoveries === "number" && recoverAlreadyMergedReviewTasksRecoveries > 0);
const hasSamples = dayHasSamples({
tasksEnteredInReview,
tasksBouncedToInProgress,
postMergeAuditFailures,
fileScopeInvariantFailures,
recoverAlreadyMergedReviewTasksRecoveries,
});
perDay.push({
date: day,