Repository navigation
Description
Ruled: 6396322580 · letter C · 2026-10-25T10:07Z (C = keep the goal, platform card for a datetime-difference measure; 1C + 2B of 2026-09-09 stand)
Blocked-by: objectstack-ai/objectstack#26601 (per-type over-SLA: the $now offset across contract_type; #25778 shipped in 18.2.0 and the durations landed as PR #168)
⬆️ 2026-10-25 第 3 批裁定 C:等待对象从 objectstack#16737(已关闭,修法是拒绝而非提供)换成 objectstack#25778(分析层提供「两个 datetime 列之差」的度量原语)。解锁判据不变:消费方可安装,且本仓能写出 §09 的那两个度量。
⬆️ 裁定 1C(维护者,2026-09-09,第 1 批):阻塞源是平台缺陷 ⇒ 去平台修。解锁判据是消费方可安装(本仓升到含该修复的版本后那条错路确实报错),⛔ 不是「上游 PR 已合并」。详见本卡 2026-09-09 的裁定评论,含一条对 1C 的公开更正:平台在飞的 PR #16778 选的补法是拒绝而非提供,所以修完之后各段时长仍然算不出来。
维护者速读
事情:设计方案第 9 章要两样东西,卡 10(#29 / PR #30)照着做时发现平台这一版给不了:
- 平均周转 / 各段时长——需要「两个时间戳相减」。语义层不收 SQL 也不收表达式(ADR-0021),没有地方做减法。
- 超 SLA——需要拿合同的
review_started_at去比另一个对象的列(clm_contract_type.review_sla_days,九种类型分别是 2/3/5/10 天)。分析层既不能按公式过滤(§12 缺口 [Decision] Two states inDESIGN.md§03 are dead ends: a started obligation cannot go overdue, a partly paid instalment cannot be completed #10),跨对象过滤也被直接拒绝。
两问的补法是同一个:让日任务把结果盖成合同上的持久字段,之后分析层直接读那个数就行。这正是 §12 缺口 #7 已经为另一个日期运算问题选定的形状(「到期、逾期由日任务盖戳字段」),所以不是新机制。
为什么必须你拍板:字段叫什么、语义是什么,是公开契约;而且它落在卡 09(日任务那张卡)的地盘上,不是卡 10 能自己加的。
你要做的:回两个字母,比如「1A 2A」。
第一问 —— 各段时长
这里最值得你看一眼的,是它差点怎么错
开发 agent 先按直觉写了 derived: { op: 'difference', of: [avg_activated, avg_submitted] }。它不报错。它解析通过、运行成功、渲染出一个数:-0.849999999999909。
原因实测如下:
sqlite> select typeof(submitted_at), submitted_at from clm_contract limit 1;
text|2026-05-19T00:00:00.000Z
sqlite> select avg(submitted_at) from clm_contract;
2025.9166666666667 ← 平均「年份」,不是平均日期
Field.datetime 在 SQLite 上存的是 ISO 文本,AVG() 触发 SQLite 的文本→数字强制转换,取到开头的年。于是那个 -0.85 是两个平均年份之差——一个干净、合理、可以直接进仪表盘的错数。
这就是本来会发布出去的形态。 卡 10 因此改为交付「各阶段覆盖数」(已提交 60 · 已开始审查 41 · 已批准 34 · 已签署 72 · 已生效 72)和一块标明为「审批吞吐」的砖,§09 要的「本月 vs 上月」对比则用平台自己的 compareTo: { kind: 'previousPeriod' } 原语实现,没有硬编码差值。
选项
| 做法 | 代价 / 收益 | |
|---|---|---|
| 1A(推荐) | 在 clm_contract 上加持久的时长字段,由日任务盖戳——与 §12 缺口 #7 同形 |
需要 §03 加字段 + 卡 09 加一次盖戳;字段名与语义要你定。收益:§09 字面兑现,且时长可被分析层过滤与排序 |
| 1B | 接受卡 10 的替代物,改 §09 的措辞为「阶段覆盖与吞吐」 | 零工作量,今天的板子如实。但让出一项真实的分析能力,且要改 §09 |
| 1C | 向平台提需求(datediff 类度量,或 Field.datetime 改存数值时间戳),在它落地前维持替代物 |
一次修好所有应用;但时间不可控,期间两块砖一直是近似 |
| 1D | 砍掉 contract_cycle_time 数据集 |
⛔ §09 点名四个数据集,静默砍掉一个正是卡片明令禁止的。列出仅为明确否决 |
第二问 —— 超 SLA
卡 10 交付的是固定 30 天阈值,并把砖的标题写成「审查超 30 天」而不是「超 SLA」——因为九种类型的 SLA 是 2/3/5/10 天,30 天高于全部,且标题自己说明了阈值,读者不会误以为它是按类型的违约。
| 做法 | 代价 / 收益 | |
|---|---|---|
| 2A(推荐) | 并入 1A:同一个日任务顺手按类型 SLA 盖一个 review_due_at(或违约布尔),砖改为过滤持久列 |
一个任务、一次写入,两块砖同时不再是近似 |
| 2B | 保留固定阈值,改 §09 措辞 | 零工作量,但「超 SLA」这项能力就没有了 |
| 2C | 固定阈值作为过渡,§09 保持目标,等卡 09 落地再回头 | 拖延,且过渡期无期限 |
四棱分析(两问共用)
实际业务需求 — 偏 A。「合同平均要走多久」和「哪些审查压过了 SLA」是 CLM 买家评估流程效率时最先问的两个问题,也是对标 Ironclad/Agiloft 时销售会被追问的两个数。1B/2B 等于承认这个产品答不出来。
项目长远合理性 — 明确偏 A,理由是一致性:这个设计对完全相同的问题(日期运算算不了)已经在 §12 缺口 #7 选过一次答案,就是「日任务盖戳字段」。同一个问题给两种答案,是下一个作者最容易踩空的地方。而且持久数值列可过滤、可排序、可聚合——这正是 clm_contract 那五个汇总字段被做成 summary 而不是公式的同一个理由。
防 AI 写错 — 这一棱最偏 A,而且差距最大。今天的状态是:语义层里存在一条能跑通、能渲染、结果错的路径(op: 'difference' 那个 -0.85)。只要这条路还开着,下一个作者——人或 AI——照直觉写就会写出它,而且没有任何闸门会拦。1A 把正确答案变成一个现成的持久字段,等于把那条错路的诱因移走。1B 反而更危险:它把「这里算不出时长」写进文档,却不改变「随手一写就能得到一个假时长」这个事实。
创业阶段不扩散 — 略偏 B。1A 要加字段、改 §03、给卡 09 加活。但卡 09 本来就要建一个跑在这些对象上的日任务,所以边际成本是「一个字段加一次盖戳」,不是一套新机制。2A 更是搭同一趟车。
结论:三棱同向 A,第四棱的成本因为卡 09 已有日任务而被大幅摊薄。推荐 1A 2A。 若你更想省,1B 2B 也自洽,但必须写进 §09——沉默不可接受,理由同 #10:把矛盾留给下游卡片,就是把它推给一个更没权限、也更有压力悄悄编一个答案的位置。
⛔ 字段名与语义我没有替你定,那是公开契约。
影响
不阻塞。PR #30 本身不声称任何时长,可以按现状合并(正在做第一轮 rework,原因与本卡无关)。这两问的落地在卡 09——那张卡同时还等着 #6、#10、#14 三张决策卡,所以一并回答可以让卡 09 一次派发到位。
关联
#29 / PR #30(来源)· 卡 09(落地处,尚未派发)· #6 #10 #14(同样卡住卡 09 的三张)· DESIGN.md §09、§12 缺口 #7 与 #10。
Activity
Four datasets and three dashboards (#30)
zhuangjianguo commentedon Sep 9, 2026
前提刷新 —— 平台侧根因已有卡,且在等维护者动作。 ⛔ 不是裁决,不动标签。
本卡第一问的实测(
avg(submitted_at)返回2025.9166666666667,derived: { op: 'difference' }渲染出-0.849999999999909)在上游已经立案:objectstack-ai/objectstack#16737— service-analytics:AVG()over aField.datetimemeasure returns SQLite's text→numeric coercion (an average YEAR) with no error, andderived: { op: 'difference' }renders the difference of two of them as a clean plausible number —— open,且带pm:awaiting-maintainer(决定已做,只剩一次 GitHub 之外的人工动作)。同族另有两张 open:#16099(无任何层拒绝不自洽的聚合/字段类型配对,
avgover datetime 在 SQLite 能跑、在 Postgres 报错)与 #15768(datetime 度量在分析响应里被定型为number)。这对本卡的意义:选项 1C(向平台提需求,等它落地前维持替代物)在写这张卡时是「时间不可控」的假设面,现在它有具体载体和状态了。⇒ 裁本卡之前应先读 #16737 的现状 —— 如果平台给出日期差度量或修掉静默强制转换,1A 的「日任务盖戳字段」就不是唯一出路,1C 的成本估计也要重算。
另:本卡与 #11 #19 #20 #28 同属前提取自
@objectstack/*17.3.0 的一批。#35 已把本仓推到 17.4.0 并于今日合并(PR #37,origin/main@5d5a254,锁文件零 17.3.0 残留)。⇒ 这一批的平台能力断言现在都该在 17.4 上重跑再定,本卡的语义层限制(ADR-0021 不收 SQL/表达式、跨对象过滤被拒)尤其需要重验。取数时刻 2026-09-09T14:5xZ。
Generated by Claude Code
zhuangjianguo commentedon Sep 9, 2026
裁定:1C + 2B —— 去平台修;本仓维持替代物并把措辞改成如实
维护者裁决,2026-09-09,常设决裁批第 1 批,逐字:
本卡的阻塞源是平台缺陷(语义层不做日期减法;
AVG()打在 datetime 上返回平均年份)⇒ 走「去平台修」。这也与决策分析写法的应用仓推荐序特例一致:阻塞源是平台缺陷 ⇒ 恒荐等待;⛔ 不荐绕行(形状容错、复刻平台规则)、不荐劈半落地。⇒ 1C(等平台)+ 2B(保留固定阈值,措辞如实)。⛔ 因此 1A / 2A 明确不采纳:用每日任务在应用里补出分析层该有的日期运算,正是该特例点名的「复刻平台规则」。
呈报时我把 1C 写成「一次修好所有应用」。录裁前重读平台侧,这句话不成立:
objectstack-ai/objectstack#16737— open,bug·priority:p2·domain:services·pm:awaiting-maintainer,assigneeos-trump。objectstack-ai/objectstack#16778,标题逐字:⇒ 平台选定的补法是拒绝,不是提供。#16778 合并后,本仓依然算不出各段时长 —— 变化的是:那条「能跑通、能渲染、结果错」的路径会开始响亮报错,而不是给出
-0.849999999999909这种可信的假数。洞被堵上了,能力没拿到。 这对本卡的意义:
⛔ 我不静默重裁。裁定按字面执行为 1C + 2B。但若你看了上面这段觉得「等一个只会报错的修复」不是你要的意思,说一句,本卡回决策箱重开选项(那时真正的选项是:向平台立一张日期差度量原语的能力卡,还是接受第 9 章永久少这两个数)。
裁后执行
needs-user-decision→pm:blocked,正文加机器可读行Blocked-by: objectstack-ai/objectstack#16737(同一笔标签写入)。DESIGN.md§03 requires a termination reason but declares no field for it #6,本卡为Refs #31的搭车项,⛔ 非Fixes):第 9 章不再无条件承诺「各段时长」与「超 SLA」,改为记明现状 —— 交付的是阶段覆盖数与审批吞吐,两个原指标等平台的日期能力,并点名 #16737。沉默地留着一个兑现不了的承诺,正是本卡自己反对的形状。Generated by Claude Code
docs(design): record four maintainer rulings in DESIGN.md §03, §06 an…
docs(design): make §09 self-consistent and stop §02 promising turnaround
WIP: card 09 scheduled jobs — INCOMPLETE AND UNREVIEWED
Card 09 — F9 activation · F3/F4/F10–F14 scheduled jobs · F16 executed…
docs: reframe five DESIGN.md 09 comments as agreement, not shortfall
zhuangjianguo commentedon Sep 10, 2026
席位裁定 —— 解锁判据未满足,
pm:blocked保持#80 在本仓实际安装的版本上重跑了这张卡的探针。判据没有满足,而且这张卡的措辞刚好证明了它自己的价值。
实测(17.4.0,54 个
@objectstack/*包全部 17.4.0,better-sqlite313.0.3,从node_modules读出)那条错路三层都不拦:
validate/lint/typecheck全部退出 0,derived原样编进dist/objectstack.jsonPOST /api/v1/analytics/dataset/query→ HTTP 200,回显AVG(submitted_at)cycle_days = -0.8555555555556111,avg_submitted = 2025.8833333333334操作员看到的是两块普通指标砖:
-0.86 / Avg Cycle (days?)和2,025.88 / Avg Submitted—— 没有报错横幅、没有空态、没有校验提示、控制台无错。这是本卡列出的三种结局里的第三种,也是本卡认定最危险的那种:它照样撒谎。 数值与本卡原记录的
-0.849999999999909不同,只因演示数据的日期随启动日漂移;机制与行数(120 行 / 60 已提交 / 72 已生效)完全一致。⇒ 本卡「⛔ 不得用应用侧日任务盖戳」与 §09 已记录的交付面全部不变。
pm:blocked保持,⛔ 不进决策箱,⛔ 不需要维护者现在做任何事。这是本轮最该记下的一件事:
objectstack-ai/objectstack#16737已于 2026-09-10 06:13Z 关闭(completed)。@objectstack/service-analytics的latest就是 17.4.0 —— npm 上 17.x 之后没有更新的发布版。而 17.4.0 自己的
CHANGELOG.md白纸黑字写着这个洞是开的(我自己拉的包,逐字核过):这正是本卡当初把判据写成「消费方可安装」而不是「上游 PR 已合并」的理由。 那句 ⛔ 今天兑现了:上游卡关了,本仓装的版本行为一个字节没变。判据措辞是对的,⛔ 不要改它。
一条上游发版说明里的新事实,比本卡原有的更细
洞是按后端分岔的:SQLite 上是 epoch 均值(静默假数),Postgres 上是报错。 也就是说这个陷阱是演示环境(SQLite)专属的 —— 真正跑在 Postgres 上的部署会直接报错。本卡原文只写了「⛔ 未在第二种方言上复核」,现在平台自己的发版说明把答案写出来了。这对将来落地 1A/2A 时怎么描述这个字段有用,⛔ 但不改变今天的裁定。
17.4.0 确实发了这一族的两个兄弟修复(min/max 的类型描述),但没发「拒绝」那一半:装好的包里搜不到
16737也搜不到16778。skim 一眼 temporal 相关的发版说明,很容易把兄弟当成本体。 下一次读 #16737 状态的人请先看latest的实际行为,不要只看 issue 是不是 closed。已做
上游 #16737 上补了一条消费方读数(
latest上行为未变 + 它自己 CHANGELOG 的原话),⛔ 措辞是「请问哪个发布版会带上」,不是「你关错了」—— 修复很可能已在main上,只是还没发版。附:本卡引用的 17.3.0 测量已刷新
src/datasets/cycle-time.dataset.ts注释里那段标着 17.3.0 的测量,现在有了 17.4.0 的同形读数:形状相同、数字更新、结论不变。⛔ 那条注释本身不改(#80 明令禁止),刷新后的读数落在 #80 的报告里。顺带一提:正因为那条注释老老实实标了版本号,这个缺口才看得见 —— 一条不标版本的测量会一直被当成当前事实。Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Seat note · 2026-10-09T14:41Z — waiting state, written on the card.
-0.86with no refusal at any layer.objectstack-ai/objectstack#16737closed completed 2026-09-10, after that reading. Closure is not the criterion; this card's own criterion is that the wrong path errors in this repo, on the version it has installed.@objectstack/*dependencies from 17.4.0 to 17.7.0, walking the official upgrade checklists of 17.5, 17.6 and 17.7 #82 (@objectstack/*17.4.0 → 17.7.0, in flight since 2026-10-09). Its dispatch carries Re-run #31's unlock probe on 17.4.0 — its criterion may already be met and nobody has looked #80's probe, re-run on 17.7.0, as a report-only increment. When that reading lands, the seat decides this card's state from it.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Seat note · 2026-10-09T16:08Z — the 17.7.0 reading exists.
#82's dev re-ran #80's probe on 17.7.0 (PR #85, section #31's unlock probe on 17.7.0): the wrong path now errors at every door —
os validate/os buildexit 1 (measure-aggregate-field-type-refused×2),PUT /api/v1/meta/dataset/probe31_cycle→422 INVALID_METADATAwith nothing stored,POST /api/v1/analytics/queryavg(submitted_at)→400 INVALID_FIELD(query was NOT run). Probe never committed; tree restored and proved by state.This card's criterion is 「本仓升到含该修复的版本后那条错路确实报错」 — it is met on the branch, and becomes this repo's state when PR #85 merges (
mainis still on 17.4.0 until then). At that merge the seat brings this card back to the maintainer: 1C's wait is over, and as this card's own correction recorded, the platform fixed by refusing, not by providing — the per-stage durations remain uncomputable on 17.7.0.Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
解锁判据已成立——请决定本卡怎么收尾 · 2026-10-10T00:17Z
Governing text: 本卡裁定 1C(维护者,2026-09-09,第 1 批)「阻塞源是平台缺陷 ⇒ 去平台修。解锁判据是消费方可安装(本仓升到含该修复的版本后那条错路确实报错)」·
DESIGN.md§09维护者速读
事情:PR #85 已合并(
c31c7e2),本仓main现在是 17.7.0。上一条评论里的探针在 17.7.0 上测得:那条错路(对Field.datetime求avg再相减)在每一道门都报错——作者时os validate拒绝、保存门422、查询门400。本卡自己的解锁判据成立了,「渲染出一个干净的错数」这个风险已经消失。但要知道:平台的修法是拒绝而不是提供(本卡 2026-09-09 的更正里已说明),所以 §09 原本想要的「各段时长 / 超 SLA」在 17.7.0 上仍然算不出来。现在的替代物(阶段覆盖数、审批吞吐、固定 30 天阈值且标题写明阈值)照 1C + 2B 继续有效。
你要做的:回一个字母。
四棱分析
Generated by Claude Code
19 remaining items
DESIGN §02/§09/§12: stage durations delivered on the duration measure…
objectstack-fleet commentedon Nov 3, 2026
Unblocked — 2026-11-03
objectstack#26601 closed: B′ (a to-one hop for the
addDays$fieldoffset) was declined (ruling 6457437597 on objectstack#26753). The director's pointer for this card (objectstack#26601, 2026-10-28T00:44Z) gives the platform-native answer under ruling 6396322580: a stored expression field onclm_contract(review_sla_days, read throughcontract_type), with the over-SLA dashboard filter on that column. The dev verifies the one-hop stored expression on one record first.mainis on@objectstack/*18.3.0 since PR #198 (fc68eb6, push CI green). State:pm:blocked→pm:queue.Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
Claim: PM loop round 18
Ruling-ref: 6396322580
Session:
session_017nzWFRUo2U7evMkPdVniAnAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-31-review-slaWorktree:
hotclm-issue-31Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/objects/contract.object.ts(stored expressionreview_sla_days) ·src/datasets/**·src/dashboards/**·src/translations/{en,zh-CN}/(the new field and dashboard strings) — stop on breach; explain in the reportContainer & model:
S,mode:subagent,model: opus(TIER_DEFAULT; hand-derived for a sister repo),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
this app's legal dashboard (over-SLA reviews per contract type) | objectstack-ai/objectstack#26601 closed: B′ declined, platform-native answer is a stored expression field | the legal headThread-read: none
Premise: re-measured by the dev on
origin/mainfc68eb6(@objectstack 18.3.0) with the declared demo staffSerial constraints cleared: 18.3.0 landed (PR #198). Dispatched together on disjoint files: #195 (lifecycle actions), #197 (demoStaff, README, demo script), #157 (child-name mirror), #31 (SLA field, datasets, dashboards); #195 and #31 both add translation keys to objects.contract.ts and rebase before their PRs.
Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
os-dev-report
Written 2026-11-03 · PR #201 · head 4e58df8 · branch claude/issue-31-review-sla
{ "issue": 31, "status": "done", "branch": "claude/issue-31-review-sla", "pr": "https://github.com/objectstack-ai/hotclm/pull/201", "head": "4e58df8", "session": "session_017nzWFRUo2U7evMkPdVniAn (parent seat's; this run is an os-dev subagent)", "premise_still_valid": true, "summary": "The per-type over-SLA half of §09 is delivered on 18.3.0, and the PR body opens with 'Fixes #31' because the durations half landed in PR #168. Following the director's pointer (objectstack#26601, 2026-10-28T00:44Z), clm_contract gets a stored expression field read one hop through contract_type. It was first verified on one record with the pointer's exact spelling (positive review_sla_days): insert stored 3, re-picking the type stored 10, and a sent 99 did not stand. ONE DEVIATION, measured: 18.3's $now anchor ADDS its addDays column and has no subDays or negation, so 'now minus SLA' needs a column holding the negative number (the platform's own documented example names it review_sla_days_offset). The field is therefore review_sla_days_offset = 0 - record.contract_type.review_sla_days, readonly, hidden, in the identity group. The name is raised as open question 1. contract_metrics gains review_over_sla_count (count, status in_review AND review_started_at < {$now, addDays: {$field: review_sla_days_offset}}). On the legal workbench the fixed 30-day tile becomes 审查超时限, and a new per-type horizontal bar is added. No hook, job or flow writes the column.", "tests": "Throwaway DB on port 3167, pnpm demo:zh, @objectstack 18.3.0, every reading signed in as legal.head@example.com (password admin123, demo staff, no hand-made account). BEFORE (origin/main fc68eb6, fresh DB, Chromium zh-CN): 待受理 6 · 审查中 12 · 审查超过 30 天 6 · 等待对方 2. AFTER (fresh DB): one record as requester1, final field: POST NDA gives offset -3; PATCH contract_type to Lease gives -10; PATCH offset 5 returns 200 and the value stays -10. Edit of the type's own SLA is NOT MEASURED: clm.admin@ got PATCH clm_contract_type 403, and no other route was taken. Independent SQLite (julianday(review_started_at) < julianday('now') - type.review_sla_days), API (POST /api/v1/analytics/dataset/query as legal.head) and the rendered dashboard agree. Tile: 12 = 12 = 12. Per type: MSA 6, NDA 2, SUP 1, DPA 1, LSE 1, ICA 1, identical in all three. Under SLA: as legal.head, MSA-2026-0016 was moved submitted → in_review (legal_owner counsel1, offset -5, started now). 审查中 went 12 → 13 while 审查超时限 stayed 12, and the MSA bar stayed 6 of 7 in review. Over SLA: the DPA contract (17.7 days in review, SLA 5) is counted, and it was NOT in the old 30-day tile. runtimeFilter category=nda gives 2 = SQL 2. Selected alone, the measure's filter narrows the rows, so a type with none over SLA is not drawn (rows: [] measured). The chart description says so. The record page for MSA-2026-0016 does not show the hidden field. Second pnpm demo:zh run on the same DB: Seeds 1130 rows; no degraded, no-such-table or failed plugin; 0 of 121 offsets NULL; 0 disagree with -type SLA. Console before and after: the same one 404 resource and one pre-login 401 get-session. The browser readings were on fc68eb6 plus the change. The rebase onto 56ebd41 brought #199 and #200, which touch no dataset, dashboard or this field.", "gates": "Head 4e58df8 (rebased on origin/main 56ebd41), each exit captured before any pipe: pnpm install EXIT=0; pnpm validate EXIT=0 (✓ Validation passed); pnpm lint EXIT=0 (18 warnings = 17 security-superuser-field-mask-inert #196 + 1 obligation_requester_own #64, 6 suggestions: identical to main, none on a changed key); pnpm typecheck EXIT=0; pnpm lint:i18n-gate EXIT=0 (COVERAGE: 0 missing keys across 2 locales); CLM_DEMO_SEED=1 pnpm validate EXIT=0. Measured while placing the field: in the 'lifecycle' group it added a 19th warning, because CONTRACT_STAMPED_FIELDS derives a clm_admin FLS mask from that group. The field therefore sits in 'identity', and a stored expression needs no lock. CI check-runs on PR #201: not read (the report is delivered at the end of local verification; CI convergence is the seat's).", "dogfood_battery": "n/a — app repo, no `!` title, Clause-② no", "mcp_calls": "0", "api_writes": "2 — fleet-write relay: pr_create objectstack-ai/hotclm#201 (read-back identical, 8868 bytes, assignee zhuangjianguo); this os-dev-report comment on hotclm#31 via post-stamped. git push x1 (not REST).", "open_questions": [ { "question": "The pointer spelled the field review_sla_days (positive copy of the type's SLA). 18.3's $now anchor can only ADD a same-record column, so the PR ships review_sla_days_offset (negative). Keep that name?", "options": [ "A: keep review_sla_days_offset, one hidden column holding -SLA (as shipped; the platform's own documented spelling for this count in filter.zod.ts).", "B: two columns. A visible positive review_sla_days (the pointer's spelling, shown on the contract) plus the hidden review_sla_days_offset that the filter reads. One more stored column and two translation keys.", "C: name the column review_sla_days but store the negative value. ⛔ Listed only to reject: a column named for the SLA that holds -5 misleads every later reader." ], "recommendation": "A, because it is one column, its sign is forced by the platform and named the way the platform documents it, and nothing in the app needs a positive copy (the type already shows it; the cycle-time dataset reads it through the join). Choose B only if legal should see the SLA on the contract page." } ], "out_of_scope_findings": [ "class: a · reach: boot of this change over a DB created by origin/main (pnpm demo:zh, 18.3.0) left clm_contract.review_sla_days_offset NULL on 121/121 rows. A later demo seed pass rewrote only 41/120 rows (79 left NULL). The $now anchor reads a NULL offset as 0 days, so every started review counts as over SLA until its contract is next written. Adding a stored expression field to an object with rows has no backfill: the engine computes it only on a write, and `os migrate` has summary-nulls for roll-ups but nothing for stored expressions. Seam: spec:Field.expression (stored, field.zod.ts) → runtime: objectql computeStoredExpressions (write path only); migrate: no stored-expression backfill · dedupe words: \"stored expression backfill existing rows\", \"expression field added NULL existing records\", \"migrate stored computed field\", \"recompute stored expression on schema change\"", "class: none (platform contract, noted) · a stored one-hop expression does not follow an edit of the related record: changing clm_contract_type.review_sla_days leaves every filed contract's offset as it was until that contract is written. Not measured live (clm.admin@ was refused 403 on PATCH clm_contract_type), and stated from the field.zod.ts contract · carrier: none · noted in the PR acceptance notes, not filed", "carrier: none · noted, not filed: src/flows/legal-review-sla.flow.ts (F3 reminder) still fires at a fixed 30 days. The help text on clm_contract_type.review_sla_days says so and is still accurate. Both are outside this claim's surface. The reminder could now read review_sla_days_offset, so that the tile and the reminder tell one story again." ], "deviations": [ "Field name review_sla_days_offset (negative), not review_sla_days, as measured above; see open question 1. The file is the claim's src/objects/contract.object.ts, so the surface is not breached.", "Browser and API readings were taken on fc68eb6 plus the change. The gates ran on the rebased head 4e58df8." ], "governed_text_proposal": { "DESIGN.md §03 字段清单, clm_contract row — insert after 'direction(同上)'": "· review_sla_days_offset number(合同类型 `review_sla_days` 的负数:平台存储表达式 `0 - record.contract_type.review_sla_days`,每次写入由引擎计算,客户端传值不生效;只读、隐藏;只供 §09「审查超时限」计数读取)", "DESIGN.md §09 法务工作台 bullet (on top of PR #168's proposal)": "- **法务工作台** — 待受理数 · 审查中 · 审查超时限(各合同按自己类型的 `review_sla_days` 判定)· 谈判停滞 · 平均审批周转(提交 → 批准的平均天数,本月对比紧挨在前的等长时段,用平台 `compareTo: previousPeriod`)· 各阶段合同数(横向条形图,按合同数从多到少排;只画在办的六个阶段)· 各段时长(四块砖,每段一块,单位天)· 审查超时限按合同类型(横向条形图,按超时限数从多到少排;没有超时限合同的类型不画)", "DESIGN.md §09 paragraph on 各段时长 / 超 SLA (replaces the 2B paragraph and PR #168's proposed replacement)": "**各段时长与按类型的「超 SLA」均已交付**(维护者裁定 2026-09-09 1C + 2B,2026-10-25 C:保留目标、等平台)。各段时长建在平台 18.2.0 的 `duration` 度量上(`objectstack-ai/objectstack#25778`)。「审查超时限」拿 `review_started_at` 比「现在减去该合同类型的审查时限」:平台 `$now` 锚点(`objectstack-ai/objectstack#25808`,裁定 A)的偏移只读同一条记录上的数值列,经 `contract_type` 的一跳路径不被采纳(`#26601`、`#26753` 拒绝 B′)。因此合同上存一列 `review_sla_days_offset`,是平台存储表达式(18.3.0),由引擎在每次写入时从合同类型算出。`addDays` 只做加法,所以存的是负数。⛔ 不得用应用侧 hook、日任务或流程盖戳字段来补时长或超时限 —— 那是在应用里复刻平台规则;`review_sla_days_offset` 由平台计算,不在此列。〔「审批瓶颈」一段原样保留〕", "DESIGN.md §02 table, Insights row": "| Insights | 周转分析 | 阶段时间戳 → dataset(平台 `duration` 度量)→ 看板:各段平均时长、平均审批周转、按类型的审查超时限(§09) |", "DESIGN.md §12": "Do NOT add the row PR #168 proposed ('按类型超 SLA … 跨对象偏移待新卡'). It is resolved by this PR. If the seat's PR #120 already carries PR #168's ⛔ sentence 「也不得在合同上镜像一列 SLA 天数」, that sentence must give way to the paragraph above, because it would forbid this PR's platform-computed column." } }Generated by Claude Code
DESIGN.md §02/§03/§09/§12: per-type review SLA via a stored expression (
objectstack-fleet commentedon Nov 3, 2026
Ruling-ref: 6396322580
Landed — PR #201 · 2026-11-03
MERGED(squash onmain), merged at its ACCEPT head4e58df8;Fixes #31closes this cardRelease: the legal workbench counts reviews over their own type's SLA (审查超时限, plus a per-type bar), replacing the fixed 30-day tile. The per-stage durations landed earlier as PR #168, so both halves of §09 are now delivered. The governed §02/§03/§09/§12 text is on PR #120, which the maintainer merges. Follow-ups: the F3 reminder still uses 30 days (new hotclm card), and an upgraded database is not backfilled (new objectstack card). State:
pm:dispatchedremoved, assignee cleared.Generated by Claude Code