feat(FN-4394): complete Step 3 — add heartbeat scope resolver helpers

Fusion-Task-Id: FN-4394
Fusion-Task-Lineage: 1e7ad660-2cd2-40ba-b7b8-7ef552c6fa13
This commit is contained in:
Fusion
2026-05-14 02:44:48 -07:00
committed by gsxdsm
parent 97777ea12a
commit 99f06020d6
2 changed files with 82 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
import type { Agent, ProjectSettings } from "@fusion/core";
import { describe, expect, it } from "vitest";
import { resolveHeartbeatScopeDisciplineMode, selectHeartbeatProcedure } from "../heartbeat-procedure-resolver.js";
const project = (mode?: ProjectSettings["heartbeatScopeDiscipline"]) => ({ heartbeatScopeDiscipline: mode });
const agent = (mode?: unknown) => ({ runtimeConfig: mode === undefined ? {} : { heartbeatScopeDiscipline: mode } }) as Pick<Agent, "runtimeConfig">;
describe("resolveHeartbeatScopeDisciplineMode", () => {
it.each(["strict", "lite", "off"] as const)("prefers agent override: %s", (mode) => {
expect(resolveHeartbeatScopeDisciplineMode(project("strict"), agent(mode))).toBe(mode);
});
it.each(["strict", "lite", "off"] as const)("uses project mode when agent unset: %s", (mode) => {
expect(resolveHeartbeatScopeDisciplineMode(project(mode), agent(undefined))).toBe(mode);
});
it("falls through invalid agent mode to project mode", () => {
expect(resolveHeartbeatScopeDisciplineMode(project("lite"), agent("invalid"))).toBe("lite");
});
it("falls through invalid project mode to strict default", () => {
expect(resolveHeartbeatScopeDisciplineMode(project("invalid" as never), agent(undefined))).toBe("strict");
});
it("defaults to strict when unset", () => {
expect(resolveHeartbeatScopeDisciplineMode(undefined, undefined)).toBe("strict");
});
});
describe("selectHeartbeatProcedure", () => {
const procedures = {
task: { strict: "task-strict", lite: "task-lite", off: "task-off" },
noTask: { strict: "no-task-strict", lite: "no-task-lite", off: "no-task-off" },
} as const;
it.each([
["strict", false, "task-strict"],
["lite", false, "task-lite"],
["off", false, "task-off"],
["strict", true, "no-task-strict"],
["lite", true, "no-task-lite"],
["off", true, "no-task-off"],
] as const)("maps mode=%s isNoTask=%s", (mode, isNoTaskRun, expected) => {
expect(selectHeartbeatProcedure(mode, isNoTaskRun, procedures)).toBe(expected);
});
});

View File

@@ -0,0 +1,36 @@
import type { Agent, HeartbeatScopeDisciplineMode, ProjectSettings } from "@fusion/core";
const VALID_MODES: readonly HeartbeatScopeDisciplineMode[] = ["strict", "lite", "off"] as const;
function isHeartbeatScopeDisciplineMode(value: unknown): value is HeartbeatScopeDisciplineMode {
return typeof value === "string" && (VALID_MODES as readonly string[]).includes(value);
}
export function resolveHeartbeatScopeDisciplineMode(
projectSettings: Pick<ProjectSettings, "heartbeatScopeDiscipline"> | undefined,
agent: Pick<Agent, "runtimeConfig"> | undefined,
): HeartbeatScopeDisciplineMode {
const runtimeConfig = agent?.runtimeConfig as Record<string, unknown> | undefined;
const agentMode = runtimeConfig?.heartbeatScopeDiscipline;
if (isHeartbeatScopeDisciplineMode(agentMode)) {
return agentMode;
}
const projectMode = projectSettings?.heartbeatScopeDiscipline;
if (isHeartbeatScopeDisciplineMode(projectMode)) {
return projectMode;
}
return "strict";
}
export function selectHeartbeatProcedure<T>(
mode: HeartbeatScopeDisciplineMode,
isNoTaskRun: boolean,
procedures: {
task: Record<HeartbeatScopeDisciplineMode, T>;
noTask: Record<HeartbeatScopeDisciplineMode, T>;
},
): T {
return isNoTaskRun ? procedures.noTask[mode] : procedures.task[mode];
}