Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions test/e2e/live/rebuild-hermes-cron-restore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,22 @@ function parseCronJob(text: string, jobId: string, label: string): JsonObject {
);
}

function cronJobState(job: JsonObject, label: string): JsonObject {
return requireObject(job.state, `${label} state`);
}

function completedRuns(job: JsonObject, label: string): number {
const repeat = requireObject(cronJobState(job, label).repeat, `${label} repeat state`);
return typeof repeat.completed === "number"
? repeat.completed
: fail(`${label} completed run count is unavailable`);
export function hermesCronJobRuntimeState(job: JsonObject, label: string) {
const repeat = requireObject(job.repeat, `${label} repeat state`);
const state = typeof job.state === "string" ? job.state : fail(`${label} state is not a string`);
const completed =
typeof repeat.completed === "number" &&
Number.isSafeInteger(repeat.completed) &&
repeat.completed >= 0
? repeat.completed
: fail(`${label} completed run count is unavailable`);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return {
completed,
lastRunAt: job.last_run_at ?? null,
lastStatus: job.last_status ?? null,
nextRunAt: job.next_run_at,
state,
};
}

function assertFutureCronJob(job: JsonObject, seed: SeededCronJob): void {
Expand All @@ -141,12 +148,13 @@ function assertFutureCronJob(job: JsonObject, seed: SeededCronJob): void {
schedule: { kind: "interval" },
script: seed.scriptName,
});
const state = cronJobState(job, `cron job ${seed.id}`);
expect(state.last_run_at ?? null).toBeNull();
expect(state.last_status ?? null).toBeNull();
expect(completedRuns(job, `cron job ${seed.id}`)).toBe(0);
const runtime = hermesCronJobRuntimeState(job, `cron job ${seed.id}`);
expect(runtime.state).toBe("scheduled");
expect(runtime.lastRunAt).toBeNull();
expect(runtime.lastStatus).toBeNull();
expect(runtime.completed).toBe(0);
expect(
normalizeTimestampMs(state.next_run_at, `cron job ${seed.id} next run`),
normalizeTimestampMs(runtime.nextRunAt, `cron job ${seed.id} next run`),
"seeded recurring cron job must remain well in the future during rebuild",
).toBeGreaterThan(Date.now() + 60 * 60_000);
}
Expand Down Expand Up @@ -305,13 +313,13 @@ export function createRebuildHermesCronRestoreFixture({
for (let attempt = 1; attempt <= EXECUTION_POLL_ATTEMPTS; attempt += 1) {
const job = await readCronJob(seed.id, `${artifactPrefix}-job-attempt-${attempt}`);
const count = await executionCount(seed, `${artifactPrefix}-marker-attempt-${attempt}`);
const state = cronJobState(job, `cron job ${seed.id}`);
const completed = completedRuns(job, `cron job ${seed.id}`);
lastEvidence = JSON.stringify({ completed, count, last_status: state.last_status });
const runtime = hermesCronJobRuntimeState(job, `cron job ${seed.id}`);
const completed = runtime.completed;
lastEvidence = JSON.stringify({ completed, count, last_status: runtime.lastStatus });
if (completed > 1 || count > 1) {
fail(`Hermes cron job ${seed.id} executed more than once: ${lastEvidence}`);
}
if (completed === 1 && count === 1 && state.last_status === "ok") return;
if (completed === 1 && count === 1 && runtime.lastStatus === "ok") return;
await sleep(POLL_INTERVAL_MS);
}
fail(`Hermes cron job ${seed.id} did not complete exactly once: ${lastEvidence}`);
Expand Down
57 changes: 57 additions & 0 deletions test/e2e/support/rebuild-hermes-cron-restore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it } from "vitest";
import { hermesCronJobRuntimeState } from "../live/rebuild-hermes-cron-restore.ts";

describe("Hermes rebuild cron restore evidence", () => {
it("reads the flat runtime state emitted by Hermes", () => {
expect(
hermesCronJobRuntimeState(
{
last_run_at: null,
last_status: null,
next_run_at: "2026-08-06T19:41:01.000Z",
repeat: { completed: 0, times: null },
state: "scheduled",
},
"cron job fixture",
),
).toEqual({
completed: 0,
lastRunAt: null,
lastStatus: null,
nextRunAt: "2026-08-06T19:41:01.000Z",
state: "scheduled",
});
});

it("rejects the nested state shape that hid the live rebuild contract", () => {
expect(() =>
hermesCronJobRuntimeState(
{
state: {
last_run_at: null,
last_status: null,
next_run_at: "2026-08-06T19:41:01.000Z",
repeat: { completed: 0, times: null },
},
},
"cron job fixture",
),
).toThrow("cron job fixture repeat state is not an object");
});

it.each([-1, 0.5])("rejects invalid completed run count %s", (completed) => {
expect(() =>
hermesCronJobRuntimeState(
{
next_run_at: "2026-08-06T19:41:01.000Z",
repeat: { completed, times: null },
state: "scheduled",
},
"cron job fixture",
),
).toThrow("cron job fixture completed run count is unavailable");
});
});
Loading