Exclude proven engine-process downtime from active task duration stats and badges. - Persist a throttled engineLastActiveAt heartbeat while the scheduler is running and unpaused. - Reconcile in-progress execution segment anchors on startup when downtime exceeds the poll threshold. - Emit run-audit events for shifted and no-action downtime reconciliation outcomes. - Cover core timing, dashboard badge behavior, scheduler heartbeat throttling, and self-healing audit metadata. - Add a patch changeset for the published CLI package. Files changed: .changeset/fn-7011-engine-downtime-timing.md | 7 +++ AGENTS.md | 1 + .../src/__tests__/store-execution-timing.test.ts | 51 ++++++++++++++++++++++ packages/core/src/settings-schema.ts | 1 + packages/core/src/store.ts | 28 ++++++++++++ packages/core/src/types.ts | 5 +++ .../app/utils/__tests__/taskTiming.test.ts | 16 +++++++ .../__tests__/scheduler-workflow-cutover.test.ts | 29 +++++++++++- packages/engine/src/__tests__/self-healing.test.ts | 23 ++++++++++ packages/engine/src/run-audit.ts | 4 ++ packages/engine/src/scheduler.ts | 11 +++++ packages/engine/src/self-healing.ts | 21 +++++++++ 12 files changed, 196 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7011 Fusion-Task-Lineage: 44225b0a-0fa1-40be-812d-33fd5e446944
60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { getActiveRuntimeMs, getWallClockSinceFirstExecutionMs } from "../taskTiming";
|
|
|
|
describe("taskTiming helpers", () => {
|
|
it("returns persisted plus live segment for in-progress tasks", () => {
|
|
const nowMs = Date.parse("2026-05-15T13:16:00.000Z");
|
|
const runtime = getActiveRuntimeMs(
|
|
{
|
|
column: "in-progress",
|
|
cumulativeActiveMs: 240_000,
|
|
executionStartedAt: "2026-05-15T13:15:00.000Z",
|
|
columnMovedAt: "2026-05-15T13:15:00.000Z",
|
|
},
|
|
nowMs,
|
|
);
|
|
|
|
expect(runtime).toBe(300_000);
|
|
});
|
|
|
|
it("returns null when there is no active-runtime signal", () => {
|
|
const runtime = getActiveRuntimeMs(
|
|
{
|
|
column: "todo",
|
|
cumulativeActiveMs: undefined,
|
|
executionStartedAt: undefined,
|
|
columnMovedAt: undefined,
|
|
},
|
|
Date.now(),
|
|
);
|
|
|
|
expect(runtime).toBeNull();
|
|
});
|
|
|
|
it("uses shifted executionStartedAt so the active badge excludes engine-down time", () => {
|
|
const t0 = Date.parse("2026-06-25T00:00:00.000Z");
|
|
const runtime = getActiveRuntimeMs(
|
|
{
|
|
column: "in-progress",
|
|
cumulativeActiveMs: undefined,
|
|
executionStartedAt: new Date(t0 + 60 * 60_000).toISOString(),
|
|
columnMovedAt: new Date(t0).toISOString(),
|
|
},
|
|
t0 + 65 * 60_000,
|
|
);
|
|
|
|
expect(runtime).toBe(5 * 60_000);
|
|
expect(getActiveRuntimeMs({ column: "in-progress", cumulativeActiveMs: undefined, executionStartedAt: undefined, columnMovedAt: undefined }, t0)).toBeNull();
|
|
});
|
|
|
|
it("returns wall-clock runtime since first execution", () => {
|
|
const wallClock = getWallClockSinceFirstExecutionMs(
|
|
"2026-05-15T08:42:00.000Z",
|
|
"2026-05-15T13:17:00.000Z",
|
|
Date.parse("2026-05-15T13:20:00.000Z"),
|
|
);
|
|
|
|
expect(wallClock).toBe(16_500_000);
|
|
});
|
|
});
|