mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(commitments): reject calendar-invalid due timestamps (#106236)
* fix(commitments): reject calendar-invalid due timestamps * test(commitments): fix calendar validation clock * fix(commitments): preserve timestamp interpretation * fix(commitments): preserve valid RFC 3339 dates * chore(commitments): remove contributor changelog edit --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
co-authored by
Peter Steinberger
parent
bdd060a40c
commit
78305b65b1
@@ -172,6 +172,55 @@ describe("commitment extraction", () => {
|
||||
expect(valid.map((entry) => entry.candidate.dedupeKey)).toEqual(["interview:2026-04-30"]);
|
||||
});
|
||||
|
||||
it("rejects calendar-invalid due timestamps", () => {
|
||||
const valid = validateCommitmentCandidates({
|
||||
items: [item()],
|
||||
result: {
|
||||
candidates: [
|
||||
candidate({
|
||||
dedupeKey: "invalid-earliest",
|
||||
dueWindow: { earliest: "2026-04-31T17:00:00.000Z" },
|
||||
}),
|
||||
candidate({
|
||||
dedupeKey: "invalid-latest",
|
||||
dueWindow: {
|
||||
earliest: "2026-04-30T17:00:00.000Z",
|
||||
latest: "2026-04-31T23:00:00.000Z",
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
nowMs,
|
||||
});
|
||||
|
||||
const validCandidate = expectSingleValidCandidate(valid);
|
||||
expect(validCandidate.candidate.dedupeKey).toBe("invalid-latest");
|
||||
expect(validCandidate.earliestMs).toBe(Date.parse("2026-04-30T17:00:00.000Z"));
|
||||
expect(validCandidate.latestMs).toBe(validCandidate.earliestMs + 12 * 60 * 60 * 1000);
|
||||
});
|
||||
|
||||
it("accepts calendar-valid leap-day, offset, and lowercase RFC 3339 timestamps", () => {
|
||||
const valid = validateCommitmentCandidates({
|
||||
items: [item()],
|
||||
result: {
|
||||
candidates: [
|
||||
candidate({
|
||||
dedupeKey: "leap-day-offset",
|
||||
dueWindow: {
|
||||
earliest: "2028-02-29t09:00:00-08:00",
|
||||
latest: "2028-02-29t20:00:00z",
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
nowMs,
|
||||
});
|
||||
|
||||
const validCandidate = expectSingleValidCandidate(valid);
|
||||
expect(validCandidate.earliestMs).toBe(Date.parse("2028-02-29t09:00:00-08:00"));
|
||||
expect(validCandidate.latestMs).toBe(Date.parse("2028-02-29t20:00:00z"));
|
||||
});
|
||||
|
||||
it("clamps inferred due time to at least one heartbeat interval after write time", () => {
|
||||
const writeMs = nowMs + 5_000;
|
||||
const valid = validateCommitmentCandidates({
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { normalizeOptionalString as asString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { resolveAgentConfig } from "../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { parseAbsoluteTimeMs } from "../cron/parse.js";
|
||||
import { resolveHeartbeatIntervalMs } from "../infra/heartbeat-summary.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import { resolveCommitmentsConfig } from "./config.js";
|
||||
@@ -252,7 +253,11 @@ function parseDueMs(raw: string | undefined): number | undefined {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Date.parse(raw);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
if (!Number.isFinite(parsed) || parseAbsoluteTimeMs(raw) === null) {
|
||||
return undefined;
|
||||
}
|
||||
// The cron parser validates the ISO shape and calendar; preserve Date.parse's existing interpretation.
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function resolveMinimumDueMs(params: {
|
||||
|
||||
@@ -55,6 +55,11 @@ describe("parseAbsoluteTimeMs", () => {
|
||||
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00z")).toBe(expected);
|
||||
});
|
||||
|
||||
it("parses RFC 3339 lowercase t and z separators", () => {
|
||||
const expected = Date.parse("2024-01-15T10:30:00Z");
|
||||
expect(parseAbsoluteTimeMs("2024-01-15t10:30:00z")).toBe(expected);
|
||||
});
|
||||
|
||||
it("parses datetime with milliseconds and Z", () => {
|
||||
const expected = Date.parse("2024-01-15T10:30:45.123Z");
|
||||
expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.123Z")).toBe(expected);
|
||||
|
||||
+2
-2
@@ -3,9 +3,9 @@ import { parseStrictPositiveInteger } from "../infra/parse-finite-number.js";
|
||||
|
||||
const ISO_TZ_RE = /(Z|[+-]\d{2}:?\d{2})$/i;
|
||||
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
||||
const ISO_DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}T/;
|
||||
const ISO_DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}[Tt]/;
|
||||
const ISO_ABSOLUTE_RE =
|
||||
/^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(\.\d+)?)?(?:[Zz]|[+-]\d{2}:?\d{2})?)?$/;
|
||||
/^(\d{4})-(\d{2})-(\d{2})(?:[Tt](\d{2}):(\d{2})(?::(\d{2})(\.\d+)?)?(?:[Zz]|[+-]\d{2}:?\d{2})?)?$/;
|
||||
|
||||
function normalizeUtcIso(raw: string) {
|
||||
if (ISO_TZ_RE.test(raw)) {
|
||||
|
||||
Reference in New Issue
Block a user