feat(compliance): make last_status/last_assessed_at a live nested-write roll-up (#84)#90
Merged
Merged
Conversation
…te roll-up (#84) `compliance_control.last_status` / `last_assessed_at` were hand-maintained stored fields: the seed set them per control and nothing recomputed them when an assessment landed. The intended design (the `last_assessed_at` field description already said "set by hook when a related assessment moves to passed/failed/partial") was blocked because a nested cross-object write from a hook crashed the QuickJS sandbox — framework#1867. That is fixed, so the roll-up is now live. This is a NON-aggregate roll-up ("most recent completed assessment", not a sum/count), so a native `summary` field cannot express it — the right tool is a hook that writes the parent control via `ctx.api`. Add `assessmentRollupHook` (afterInsert/afterUpdate on compliance_assessment): it re-derives the control from ALL of its assessments and copies the latest completed (passed/partial/failed) one's status + assessed_at onto the control (or `not_tested` / null when none is completed). Notes for the body-only sandbox (learned by boot-testing against the pinned @objectstack 15.1.1 runtime): - The hook sandbox exposes the engine-repo FACADE, whose single-record update is `update(data, opts)` with the id in `data` — there is no `updateById`. The hook mirrors the engine's own recipe (id in payload + `where: { id }`). - `last_assessed_at` is a datetime field: it wants an ISO-8601 string, not an epoch number, so the hook normalises the (date-granular) assessed_at through `new Date(...).toISOString()`. Seed: - drop the hand-set last_status/last_assessed_at from the 6 assessed controls (now derived by the hook as the assessments load) - add two historical passed assessments (GDPR Art.32 ~200d ago, HIPAA §164.308(a)(1) ~40d ago) so those controls' rolled-up status reproduces the previous seed exactly - the two never-assessed controls (A.8.16, 164.312(a)(1)) seed an explicit `not_tested` initial state (the hook never fires for them) Boot-verified against a fresh dev server (@objectstack 15.1.1, 0 errors, no sandbox crash): - all 8 controls' last_status / last_assessed_at match the previous seeded baseline exactly - runtime afterInsert: POSTing a passed assessment for a never-assessed control flips it to passed@today live - runtime afterUpdate: transitioning that assessment out of a completed state reverts the control to not_tested hooks/index.ts, compliance_assessment.hook.ts, CHARTER.md: replace the stale "STORED field / sandbox crashes on nested writes" notes with the live roll-up description. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZguyAaQbyUpwMZ2gMLaAP
Fix the CI `format:check` (prettier --check) failure on #90 — apply the repo's Prettier style to the two changed files (no logic change): - src/objects/compliance_assessment.hook.ts - src/data/index.ts Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZguyAaQbyUpwMZ2gMLaAP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
延续 #84 的最后一项、也是唯一一个非聚合跨对象 rollup。
compliance_control.last_status/last_assessed_at之前是手工维护的存储字段:seed 里逐个 control 写死,评估(assessment)落地时没有任何自动重算。字段本来就是为 hook 设计的(last_assessed_at的 description 一直写着 "set by hook when a related assessment moves to passed/failed/partial"),只是因为 hook 里的嵌套跨对象写入会把 QuickJS 沙箱写崩(framework#1867)才降级成存储字段。该 bug 已修复。因为这是「取最近一次已完成评估」的非聚合 rollup(不是 sum/count),原生
Field.summary表达不了 —— 正确工具就是 #84 item 2 说的嵌套写钩子。改动
新增
assessmentRollupHook(compliance_assessment的 afterInsert/afterUpdate):从该 control 的全部评估里重新推导,把最近一次已完成(passed/partial/failed)评估的status+assessed_at写到 control 上(没有已完成评估则写not_tested/ null)。body-only 沙箱的两个坑(靠对 pinned
@objectstack15.1.1 运行时 boot 实测发现)update(data, opts)、id 放在data里 —— 没有updateById。钩子照搬引擎自己的写法(id 进 payload +where: { id })。last_assessed_at是 datetime 字段,要 ISO-8601 字符串、不是 epoch 数字,所以钩子用new Date(...).toISOString()归一化(assessed_at 是 date 粒度)。seed
not_tested(钩子对它们不会触发)验证(fresh dev server,@objectstack 15.1.1,0 错误,无沙箱崩溃)
passed@todaynot_tested说明
is_overdue_for_review对「日期陈旧」控件仍返回 None —— 这是模板既有的 formula 行为(origin/main 用 seed 的 cel 值也一样,last_assessed_at存的是同样的 ISO 串),本 PR 未触及,不是回归。影响
纯模板改动,不改 framework。至此 #84 五个字段全部处理完:expense/procurement/content 走
summary,compliance 走嵌套写钩子,project 单独说明(见后续)。Generated by Claude Code