Skip to content

fix(scheduler): reject illegal calendar timestamps instead of rolling them - #8632

Merged
huangruiteng merged 4 commits into
mainfrom
cursor/scheduler-illegal-calendar-timestamps-0429
Nov 12, 2026
Merged

huangruiteng merged 4 commits into
mainfrom
cursor/scheduler-illegal-calendar-timestamps-0429

Conversation

@Duang777

@Duang777 Duang777 commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Goal And Delivered Outcome

  • Outcome basis / optional anchor: self-found scheduler defect. No public issue. The request in this PR is the basis.
  • Goal/source and gap: schedulerTimestampMilliseconds accepted impossible calendar dates via Date.parse. The calendar codec then rejected a legal lowercase RFC 3339 separator (2026-12-04t01:15:00z) that the monitor CLI already stored. An early updated_at check also blocked identity_reset.
  • Observable before → after: impossible dates no longer roll forward. A lowercase t separator is accepted and the caller's original text is stored. An obsolete illegal updated_at does not block a new identity's initial interval. Same-identity interval checks and current_time still reject an illegal calendar.
  • Issue/task and intended base: none. Base is main.

Symptom

Reproduced on scratch main 9ce3a46bfe525aa2167de6699fc676ce8ef60247. 2026-02-31T00:00:00Z became 2026-03-03T00:00:00.000Z (1772496000000). Monitor schedule, failure-cache TTL, and a null backoff parse followed that rolled or missing instant.

Input Before (main) After (this branch)
2026-02-31T00:00:00Z 2026-03-03T00:00:00.000Z null
monitor cadence from generated_at 2026-02-31T00:00:00Z accepted, due 2026-03-03T00:30:00Z throws generated_at must be an ISO timestamp
explicit due 2026-02-31T12:00:00Z stored as the due time throws explicit_next_due_at must be an ISO timestamp
failure failed_at 2026-02-31T00:00:00Z at reference 2026-03-03T00:00:00Z kept inside the 24h TTL dropped
same-identity updated_at 2026-01-32T12:00:00Z advance_after_interval throws updated_at must be an ISO timestamp
current_time 2026-02-31T12:10:00Z or 2026-11-31T12:10:00Z rolled, then advance_after_interval throws current_time must be an ISO timestamp
previous 30-minute receipt, illegal updated_at, new reset token head 80ed6f20 threw before reset identity_reset, 15 minutes, apply_required
explicit due 2026-12-04t01:15:00z main accepted and stored that text; head 0461def06 rejected it accepted, and next_due_at stays 2026-12-04t01:15:00z
explicit due 2026-11-31t10:04:00z rejected as an impossible calendar still rejected

Root cause

Date.parse overflows an impossible month/day. A null parse was treated as an elapsed backoff interval. The replacement codec matched only an uppercase T or a space before the calendar check, so RFC 3339's lowercase t separator failed closed. Validating every persisted updated_at before cadence selection also rejected an obsolete clock that identity_reset never reads.

Minimal fix

parseIsoTimestamp still round-trips calendar fields. Before that grammar it rewrites only a lowercase t between the date and HH:. The stored monitor due time remains the caller's original text. updated_at is read only when the same identity is comparing the applied interval. current_time, monitor generated_at / explicit_next_due_at, and a present failure-cache reference_time still fail closed when they are illegal. A missing or blank updated_at on the elapsed branch stays elapsed. An omitted failure-cache reference time still uses the wall clock.

Author Declaration

Implemented against

  • Specification and revision: no written specification; the request in this PR is the basis. Identity reset follows docs/status-data-contract.md: a changed reset_token returns to the current profile's initial interval. Lowercase t follows RFC 3339 §5.6. Reviews on 80ed6f20 and 0461def06.
  • Criteria:
Criterion Disposition Symbol / path Test
Impossible calendar dates must not roll forward implemented parseIsoTimestamp, schedulerTimestampMilliseconds scheduler_state_store.test.ts
A clock that informs a decision fails closed implemented monitor schedule, current_time, same-identity elapsed check scheduler_state_transition_rules.test.ts
An obsolete illegal updated_at must not block identity_reset implemented cadenceConsumesAppliedInterval identity-reset test in the same file
A legal lowercase t separator stays acceptable and is stored unchanged implemented parseIsoTimestamp codec test and explicit 2026-12-04t01:15:00z schedule test
Lowercase form of an impossible date stays rejected implemented same codec 2026-11-31t10:04:00z, 2026-02-31t00:00:00z
  • Self-check: scratch main reproduced the rollover. Later heads added the elapsed-branch check and the lowercase separator. Did not re-run the isolated SQLite quota monitor-poll --execute process. Did not merge.

Scope And Continuation

  • Completed scope: calendar checks, identity-reset recovery, and the lowercase RFC 3339 separator used by monitor due times.
  • Slice boundary: hint.ts notification cooldown still uses Date.parse on an ISO string already taken from request.timestamps.

Validation

  • Tested revision: 451246f2a02b32b70cd85b330cb7b1197c46d13c
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
regression_parity failed on main for the original illegal-calendar tests Scratch main 9ce3a46bf: the two scheduler files, 29 tests, 25 pass, 4 fail, exit 1.
unit passed On this head: those two files, 30 pass, 0 fail. tests/control_plane_ts/scheduler_*.test.ts, 124 pass, 0 fail. tests/control_plane_ts/monitor_metadata.test.ts included in a combined 42-pass run with the two scheduler files. Node v22.23.4.
static passed npm run typecheck:control-plane, exit 0. CI pins Node 22.22.3; this run used v22.23.4 (engines.node >=22.22.3).
integration not_run No isolated SQLite quota monitor-poll --execute and no live host heartbeat on this head.
real_backend not_applicable No authority-store change.
  • Coverage and gaps: uppercase and lowercase impossible dates are rejected. The legal lowercase due time is preserved. Identity reset, unacknowledged retry, and hold_active_initial do not read an obsolete updated_at. The SQLite CLI writeback named in review was not re-executed here.

See validation disclosure guidance.

Frontend / Visual Evidence

  • UI impact: none
  • Before: N/A
  • After: N/A
  • First viewport and preview: N/A
  • States and viewports shown: N/A
  • Source data: none
  • Attention review: N/A
  • Craft review: N/A

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

  • Direction / acceptance reference, when applicable: Core control-plane hardening. No roadmap id.

Shared-authority RFC fixture impact

N/A. This reuses parseIsoTimestamp and does not claim migration or shared Goal Authority progress.

  • Production-scale fixture schema: N/A
  • Semantic dimensions changed, or reviewed no-impact rationale: N/A
  • Provider conformance arms run: N/A
  • Read-only legacy/file/PostgreSQL three-arm rehearsal: N/A

Residual risk

  • hint.ts notificationCooldown still calls Date.parse on failed.iso from request.timestamps.
  • current_time is still rejected when it is present and illegal, including during identity reset, because failure-cache retention uses it.
  • An illegal failed_at on a cached failure is dropped by the TTL filter.
  • Omitting reference_time still uses Date.now().
  • This head was not re-checked with the isolated SQLite quota monitor-poll --execute command.

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • Existing English/Chinese RFC pairs (including ledger entries) both have a diff, and the local premerge check python3 scripts/check_rfc_bilingual.py --base origin/main --head HEAD passes, or no paired RFC changed.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).
Open in Web Open in Cursor 

… them

schedulerTimestampMilliseconds parsed ISO text with Date.parse. Impossible
calendar dates such as 2026-02-31 and 2026-11-31 became a later real instant,
so monitor schedules and failure-cache TTL accepted them. Backoff also treated
a null parse as an elapsed interval, which advanced cadence from a clock that
is not a real ISO date.

Use parseIsoTimestamp for this codec. Present illegal clocks fail closed.
A missing or blank updated_at still counts as elapsed, and an omitted
failure-cache reference time still uses the wall clock.

Signed-off-by: Duang777 <duangjl007@gmail.com>
Co-authored-by: DDL <duangjl007@gmail.com>
Bring the illegal calendar timestamp scheduler fix up to date with current main.

Signed-off-by: Duang777 <duangjl007@gmail.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max

评审提交:80ed6f20a8d5b5af30d1a6638ad53087db0253fd。
[P2] 无效的旧时钟会阻断新的调度重置。 提前计算历史间隔 · 新增旧时钟校验

动机 / Motivation

有现有调度收据、等待边界变化后需要恢复及时唤醒的 Agent 和维护者。 既有 30 分钟调度收据带无效 updated_at,而监控到期界限将 reset token 改为新身份,需重置到 15 分钟。 真实 store 写入并读回旧收据后,基点返回 reset_required、15 分钟且需要宿主更新和 ACK;当前直接报 updated_at must be an ISO timestamp,整个调度提示无法返回。只把旧时间换为合法值的同头对照正常重置。 独立验证 Python→真实 TS worker 与隔离 File state、实际测试套件。未改动宿主自动化或查询远端 CI。

保留旧身份失效后的自动恢复。 调度状态合同 规定 token 变化回到当前 profile 的初始 RRULE,只有同身份的后续轮次才比较旧间隔。 核验项:calendar_dates_do_not_roll, obsolete_clock_cannot_block_identity_reset。

改动思路 / Approach

沿用 runtime_timestamp 和现有 TS cadence owner。把时间检查放到真正消费旧时间的分支。 合法 calendar codec 适用于当次输入和确实用到的历史时间;失效身份只用于辨认是否重置。 复用现有 calendar codec 很合适。校验当前时间与同身份旧时间应区分;旧身份在重置分支只用来确定发生了变化。

具体改动 / Changes

  • 统一日期解析:实际 native 路径:2 月 31 日在基点滚到 3 月 3 日,当前 monitor 计划拒绝,失败缓存由 1 条降为 0。合法闰日 +08:00 同样得 UTC 16:40;有效旧收据保持重置。TS 基点 46/当前 51,Python 两边各 40,类型检查通过。
  • backoff 提前校验:既有 30 分钟调度收据带无效 updated_at,而监控到期界限将 reset token 改为新身份,需重置到 15 分钟。 真实 store 写入并读回旧收据后,基点返回 reset_required、15 分钟且需要宿主更新和 ACK;当前直接报 updated_at must be an ISO timestamp,整个调度提示无法返回。只把旧时间换为合法值的同头对照正常重置。

对主干的风险 / Risks

错误作用于现有状态读取,不只是手工传参。现有 store 会接收和返回这个旧 updated_at,投影失败时状态字节保持原样,因此下次继续命中,无法产生新 RRULE 和 ACK。合法收据对照正常。这里无需放松当前时间或同身份时间校验。类型、签署、差异和合入模拟通过;未查询远端 CI。

我的整体评价 / Verdict

请求修改。严格校验解决月日溢出,但旧身份的坏时间不应使新身份重置失败。 按当前 cadence 分支惰性验证旧 updated_at:只有同一身份需要判断是否到间隔时才读取。新身份的初始间隔独立计算;现用非法时间仍显式拒绝。

English verdict: REQUEST_CHANGES. Head 80ed6f2. Calendar checks fix rollover and TTL retention. An obsolete persisted acknowledgement with a bad timestamp now aborts the scheduler hint before identity_reset can propose the new initial interval. Validate the old clock only in the elapsed-time branch; keep rejecting clocks that actually inform a decision.

// Missing interval or blank updated_at stays elapsed. A present illegal
// calendar is rejected instead of rolled or treated as elapsed.
if (appliedInterval === null) return true;
const updatedAt = requireSchedulerTimestampMilliseconds(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 旧身份的时间不要阻断重置。 既有 30 分钟调度收据带无效 updated_at,而监控到期界限将 reset token 改为新身份,需重置到 15 分钟。 真实 store 写入并读回旧收据后,基点返回 reset_required、15 分钟且需要宿主更新和 ACK;当前直接报 updated_at must be an ISO timestamp,整个调度提示无法返回。只把旧时间换为合法值的同头对照正常重置。 按当前 cadence 分支惰性验证旧 updated_at:只有同一身份需要判断是否到间隔时才读取。新身份的初始间隔独立计算;现用非法时间仍显式拒绝。

An illegal updated_at on a previous identity was validated before cadence
decided whether that clock was an input. A reset-token change then failed
closed instead of returning the new profile's initial interval.

Read the old clock only on the same-identity elapsed branch. Current time
and a same-identity interval comparison still reject an illegal calendar.

Signed-off-by: Duang777 <duangjl007@gmail.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max

本次评审对应提交 0461def065a18d4afcf649062d8e8458377d94c7。

[P2] 保留监控 CLI 已支持的合法小写时间。 对已到期的隔离 SQLite Monitor 执行 quota monitor-poll --next-due-at 2026-12-04t01:15:00z --execute。base 退出 0,实际保存该到期时间;本提交退出 1(monitor_poll_rejected),到期时间留在原值。同一时刻的大写写法两版都能保存,11 月 31 日两版均拒绝。新 parser 的日期时间分隔符只允许大写 T 或空格;RFC 3339 §5.6 明确允许小写。在已有校验前接受/归一化这个分隔符,不放宽日历校验。

动机 / Motivation

用 --next-due-at 回写监控的用户、现有 shell/Agent 调用,以及读回调度提示的 App 宿主。基线调度时钟会把不可能的日期滚成下一月;同一 CLI 能保存合法的小写 RFC 3339 时间。上个 PR 头在旧状态验证时拦住了新调度身份重置。新头拒绝新的非法日历,且两次原生唤醒读入相同的旧坏记录,恢复 reset_required 及 ACK 提示。合法大写、有偏移和闰日对照通过。隔离 SQLite 与真实本地 CLI/TS runtime;未调用实际宿主更新、已安装环境或托管 CI。隔离 SQLite 的真实 monitor-poll 执行:合法的 2026-12-04t01:15:00z,base 退出 0 并写回原时间,head 退出 1(monitor_poll_rejected)、到期日不变;只改大小写,大写两版都写回。

改动思路 / Approach

有界修复可复用共享 codec 接受合法大小写,保持调度 owner 的原规则。不扩到通知冷却、真实宿主或更广泛的时间格式;可保留现有 reset 分支。 保留严格日历检查和已修好的身份重置,在已有 ISO codec 上接纳合法的小写分隔符;不引入另一套日期判定。

具体改动 / Changes

关键代码讲解

基线 9ce3a46bfe525aa2167de6699fc676ce8ef60247 与新提交 0461def065a18d4afcf649062d8e8458377d94c7 的真实调用对照。上个提交会在旧记录的 updated_at 校验时堵住身份重置;当前先判断是否会消费旧间隔,原生 should-run 两次都重新给出 reset_required 与 ACK。仍有独立退化:monitor_metadata.schedule 调用已换 codec 的 schedulerTimestampMilliseconds,该解析只识别大写 T 或空格。实际 CLI 小写时刻退出 1、SQLite 到期未更新;大写仅改大小写即可在两版写回;非法 11 月 31 日两版拒绝。

依据 docs/status-data-contract.md:reject_impossible_calendar、obsolete_state_identity_reset、preserve_public_monitor_timestamp_format。

对主干的风险 / Risks

长程重置已恢复,但重复调用合法小写时间的监控会丢掉下一次写回。用户体验从原本正常的 ISO 选项变成笼统拒绝。有限的修复是接纳该合法分隔符,并保持非法月日和实际历史状态的负例。

语义与 CI 对齐

标准 premerge、124 项 TS、41 项 Python 和 typecheck 通过。实际 SQLite 监控写回与旧状态 guard 正反对照已执行;未查托管 CI、未操作真实宿主调度。

我的整体评价 / Verdict

请求修改。旧坏状态的新身份重置已验证修复;请保持合法小写时间的原生监控写回,回归测同时覆盖等价大写和不可能日期。本次评审覆盖现有宿主与 CLI 行为。隔离 SQLite 与真实本地 CLI/TS runtime;未调用实际宿主更新、已安装环境或托管 CI。

English verdict: REQUEST_CHANGES. Request changes because the new scheduler codec rejects a valid lowercase RFC 3339 timestamp accepted by the existing monitor CLI, although identity reset now recovers.

: `${text}Z`;
const parsed = Date.parse(timezoneAware);
return Number.isNaN(parsed) ? null : parsed;
const parsed = parseIsoTimestamp(text);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

对已到期的隔离 SQLite Monitor 执行 quota monitor-poll --next-due-at 2026-12-04t01:15:00z --execute。base 退出 0,实际保存该到期时间;本提交退出 1(monitor_poll_rejected),到期时间留在原值。同一时刻的大写写法两版都能保存,11 月 31 日两版均拒绝。新 parser 的日期时间分隔符只允许大写 T 或空格;RFC 3339 §5.6 明确允许小写。在已有校验前接受/归一化这个分隔符,不放宽日历校验。

The calendar codec only matched an uppercase T or a space, so a monitor
poll that already accepted 2026-12-04t01:15:00z was rejected. Normalize
that separator before the existing calendar check. Impossible dates stay
rejected, and the caller's original text is still what gets stored.

Signed-off-by: Duang777 <duangjl007@gmail.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max

本次评审对应提交 451246f2a02b32b70cd85b330cb7b1197c46d13c。

动机 / Motivation

使用显式监控到期日的 CLI 调用方,以及消费调度建议的 App 宿主。基线将一部分不可能的调度日历滚入下一月。候选 80ed6f2 会在旧记录损坏时阻断新身份;0461def 修复了重置,但拒绝原本可写回的合法小写 ISO 时间。合法 UTC 大写、小写和偏移均原样写回;非法月日与重复时区不写回。两次原生唤醒都恢复新身份的 reset_required 和 ACK 提示。真实源码 CLI、TypeScript 运行时与隔离 SQLite;未调用实际宿主更新、已安装环境或托管 CI。通知冷却未改。

改动思路 / Approach

在现有 owner 恢复 RFC 合法表示;严格检查只使用实际参与策略判断的事实。调度、监控与共享 ISO 语法;通知冷却、实际宿主 ACK 与历史记录迁移不属于本补丁。 复用共享 ISO 解析器做日历验证,并按现有 cadence 分支决定是否需要读旧时钟。

具体改动 / Changes

关键代码讲解

对照 9ce3a46bfe525aa2167de6699fc676ce8ef60247 与当前提交 451246f2a02b32b70cd85b330cb7b1197c46d13c。在隔离 SQLite 上走真实 quota monitor-poll --next-due-at --execute,大写 UTC、小写 t/z 和带偏移的小写时间都逐字写回,退出码都是 0;11 月 31 日的大小写写法和重复时区后缀都退出 1,权威行保持原值。此前 0461def 会拒绝同一个合法小写输入。修复仅在 RFC 3339 §5.6 允许的位置归一化分隔符,日期继续按原日历字段验证。

quota should-run --codex-app 两次读入同一条不再适用的坏记录,均返回 reset_required 和 ACK 提示,原记录未变;这覆盖了 80ed6f2 上的重置失败。仍参与同身份间隔判断的坏 updated_at 与坏 current_time 会明确拒绝,坏的缓存时间被剔除。

依据 docs/status-data-contract.md 和现有公开 ISO 参数:reject_impossible_calendar、obsolete_state_identity_reset、preserve_public_monitor_timestamp_format 已达到。

对主干的风险 / Risks

共享 codec 同时服务租约和交接。固定时钟对照确认,合法小写、等价大写与偏移得到同一到期时刻;错误 actor、lease key 或版本仍被拒绝或返回冲突,过期及非法到期也未放行。没有新增状态、配置或存储迁移。实际参与间隔判断的历史坏时间仍会显式失败;这保留了错误信号。宿主执行与通知冷却不在本次改动范围。

语义与 CI 对齐

标准 premerge 14 项选中检查和 4 项直接检查通过;124 项调度 TypeScript、基线和新头各 54 项共享时间调用测试、60 项 Python 调度测试及 typecheck 通过。复查了实际 CLI 与 SQLite 读回、连续两次原生 guard 及共享租约反例。未查询托管 CI,也未调用真实宿主更新。

我的整体评价 / Verdict

同意当前精确提交。两个历史回归都有实际路径对照,非法输入和租约边界保持预期行为。本次评审覆盖现有宿主与 CLI 行为。真实源码 CLI、TypeScript 运行时与隔离 SQLite;未调用实际宿主更新、已安装环境或托管 CI。通知冷却未改。

English verdict: APPROVE. Approve the exact head. The real monitor path preserves legal lowercase and offset inputs, rejects impossible dates, and the scheduler can reset stale identity state without weakening lease checks.

@huangruiteng
huangruiteng merged commit 9df7fb5 into main Nov 12, 2026
34 of 46 checks passed
@huangruiteng
huangruiteng deleted the cursor/scheduler-illegal-calendar-timestamps-0429 branch November 12, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants