Skip to content

fix(compliance): correct is_overdue_for_review date math — CEL has no Timestamp+int#92

Merged
os-zhuang merged 1 commit into
mainfrom
claude/fix-compliance-overdue-review
Jul 19, 2026
Merged

fix(compliance): correct is_overdue_for_review date math — CEL has no Timestamp+int#92
os-zhuang merged 1 commit into
mainfrom
claude/fix-compliance-overdue-review

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Bug

compliance_control.is_overdue_for_review 之前是:

daysFromNow(0) > record.last_assessed_at + record.review_frequency_days * 86400000

formula 引擎是 CEL,日期是 Timestamp,而 CEL 没有 Timestamp + int 运算符 —— 所以只要 control 有 last_assessed_at,整个右边就求值为 null。后果:只有「从未评估」(last_assessed_at == null 分支)的 control 会被标为逾期;评估过但早已超过复评周期的 control 永远不会被标出来* 86400000(把天数当成 epoch 毫秒)也是错的 —— CEL 的日期运算是按天、走 builtin 的,不是毫秒 epoch。

这个字段是「复评逾期」列表视图(is_overdue_for_review = true)的过滤条件,而 seed 里的 GDPR Art.32(约 200 天前评估、周期 180 天,description 明说 "Past review-frequency window")正是用来演示它的 —— 之前一直没被标出来。

修复

改用 CEL 的 addDays(date, n) builtin + Timestamp 比较:

addDays(record.last_assessed_at, record.review_frequency_days) < today()

即:当「下次复评日期(上次评估 + 周期天数)」已经过去时,判为逾期。

验证(fresh dev server,@objectstack 15.1.1,0 错误)

8 个 seed control 全部正确:

control last_assessed freq overdue
GDPR Art.32 ~200d ago 180 true(修复前 false)
HIPAA §164.308(a)(1) ~40d 365 false
CC6.1 / CC7.1 / CC8.1 <90d 90 false
A.5.1 100d 180 false
A.8.16 / 164.312(a)(1) 从未评估 true

说明


Generated by Claude Code

… Timestamp+int (#84)

`is_overdue_for_review` was:

    daysFromNow(0) > record.last_assessed_at + record.review_frequency_days * 86400000

The formula engine is CEL, where dates are Timestamps and there is NO
`Timestamp + int` operator — so `last_assessed_at + N` (and the whole RHS)
evaluated to null for every control that HAS a last_assessed_at. The result:
only never-assessed controls (the `last_assessed_at == null` branch) ever
flagged as overdue; a control assessed long ago but past its review cadence
never did. The `* 86400000` (a "days → epoch ms" conversion) was doubly wrong:
CEL date arithmetic is day-granular via builtins, not millisecond epochs.

Fix — use the CEL `addDays(date, n)` builtin and a Timestamp comparison:

    addDays(record.last_assessed_at, record.review_frequency_days) < today()

i.e. overdue when the next-review date (last assessment + cadence days) is
already in the past.

Boot-verified against a fresh dev server (@objectstack 15.1.1, 0 errors) — all
eight seeded controls now compute correctly:
- GDPR Art.32 (assessed ~200d ago, 180d cadence) → overdue = true  [was false]
- HIPAA §164.308(a)(1) (~40d ago, 365d), CC6.1/CC7.1/CC8.1 (<90d), A.5.1
  (100d/180d) → overdue = false
- A.8.16 / 164.312(a)(1) (never assessed) → overdue = true

The "Overdue for Review" list view (filter is_overdue_for_review = true) now
surfaces stale-but-assessed controls, not just never-assessed ones.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BZguyAaQbyUpwMZ2gMLaAP
@os-zhuang
os-zhuang merged commit 93a5477 into main Jul 19, 2026
3 checks passed
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.

2 participants