Files
openclaw/src/cron/persisted-shape.ts
T
Peter Steinberger e2ec8283c4 refactor(deadcode): trim mid-size src exports (#105888)
* refactor(deadcode): trim auto-reply and CLI exports

* refactor(deadcode): trim cron and task exports

* refactor(deadcode): trim fleet and process exports

* test(deadcode): exercise live task and process seams

* test(fleet): cover stream redaction through owner module

* refactor(security): trim dead internal exports

* refactor(secrets): trim dead internal exports

* refactor(deadcode): trim remaining src exports

* refactor(deadcode): remove test-only runtime exports

* refactor(deadcode): trim pairing test exports

* refactor(deadcode): reconcile refreshed baseline

* test(auto-reply): deduplicate queue state imports
2026-07-13 00:42:56 -07:00

115 lines
3.5 KiB
TypeScript

/** Validates persisted cron job records before loading them from disk/state. */
import { parseAbsoluteTimeMs } from "./parse.js";
/** Structural rejection code for persisted cron jobs that cannot be loaded safely. */
type InvalidPersistedCronJobReason =
| "missing-id"
| "missing-schedule"
| "invalid-schedule"
| "invalid-trigger"
| "missing-payload"
| "invalid-payload";
/** Returns the first structural reason a persisted cron job cannot be loaded safely. */
export function getInvalidPersistedCronJobReason(
candidate: Record<string, unknown>,
): InvalidPersistedCronJobReason | null {
const id = candidate.id;
if (typeof id !== "string" || !id.trim()) {
return "missing-id";
}
const schedule = candidate.schedule;
if (!schedule || Array.isArray(schedule)) {
return "missing-schedule";
}
if (typeof schedule === "string") {
// Legacy shorthand schedules are normalized later by the full cron parser;
// this guard only rejects shapes that cannot be persisted or quarantined.
return null;
}
if (typeof schedule !== "object") {
return "missing-schedule";
}
const scheduleRecord = schedule as Record<string, unknown>;
const scheduleKind = scheduleRecord.kind;
if (
scheduleKind !== "at" &&
scheduleKind !== "every" &&
scheduleKind !== "cron" &&
scheduleKind !== "on-exit"
) {
return "invalid-schedule";
}
if (scheduleKind === "at") {
const at = scheduleRecord.at;
if (typeof at !== "string" || parseAbsoluteTimeMs(at) === null) {
return "invalid-schedule";
}
}
if (scheduleKind === "every") {
const everyMs = scheduleRecord.everyMs;
if (typeof everyMs !== "number" || !Number.isFinite(everyMs) || everyMs <= 0) {
return "invalid-schedule";
}
}
if (scheduleKind === "cron") {
const expr = scheduleRecord.expr;
if (typeof expr !== "string" || expr.trim().length === 0) {
return "invalid-schedule";
}
}
if (scheduleKind === "on-exit") {
const command = scheduleRecord.command;
if (typeof command !== "string" || command.trim().length === 0) {
return "invalid-schedule";
}
}
if ("trigger" in candidate) {
const trigger = candidate.trigger;
if (!trigger || typeof trigger !== "object" || Array.isArray(trigger)) {
return "invalid-trigger";
}
const script = (trigger as Record<string, unknown>).script;
if (
typeof script !== "string" ||
script.trim().length === 0 ||
scheduleKind === "at" ||
scheduleKind === "on-exit"
) {
return "invalid-trigger";
}
}
const payload = candidate.payload;
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
return "missing-payload";
}
const payloadRecord = payload as Record<string, unknown>;
const payloadKind = payloadRecord.kind;
if (payloadKind !== "systemEvent" && payloadKind !== "agentTurn" && payloadKind !== "command") {
return "invalid-payload";
}
if (payloadKind === "systemEvent") {
const text = payloadRecord.text;
if (typeof text !== "string") {
return "invalid-payload";
}
}
if (payloadKind === "agentTurn") {
const message = payloadRecord.message;
if (typeof message !== "string" || message.trim().length === 0) {
return "invalid-payload";
}
}
if (payloadKind === "command") {
const argv = payloadRecord.argv;
if (
!Array.isArray(argv) ||
argv.length === 0 ||
argv.some((value) => typeof value !== "string" || value.length === 0)
) {
return "invalid-payload";
}
}
return null;
}