Skip to content

[Decision] §09 要「各段时长」和「超 SLA」,但语义层算不出时间差——两问共用一个补法 #31

Open

Description

@hotlong

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)照着做时发现平台这一版给不了:

  1. 平均周转 / 各段时长——需要「两个时间戳相减」。语义层不收 SQL 也不收表达式(ADR-0021),没有地方做减法。
  2. 超 SLA——需要拿合同的 review_started_at 去比另一个对象的列(clm_contract_type.review_sla_days,九种类型分别是 2/3/5/10 天)。分析层既不能按公式过滤(§12 缺口 [Decision] Two states in DESIGN.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

  1. added a commit that references this issue on Sep 8, 2026
  2. zhuangjianguo commented on Sep 9, 2026

    @zhuangjianguo
    Collaborator

    前提刷新 —— 平台侧根因已有卡,且在等维护者动作。 ⛔ 不是裁决,不动标签。

    本卡第一问的实测(avg(submitted_at) 返回 2025.9166666666667,derived: { op: 'difference' } 渲染出 -0.849999999999909)在上游已经立案:

    objectstack-ai/objectstack#16737 — service-analytics: AVG() over a Field.datetime measure returns SQLite's text→numeric coercion (an average YEAR) with no error, and derived: { op: 'difference' } renders the difference of two of them as a clean plausible number —— open,且带 pm:awaiting-maintainer(决定已做,只剩一次 GitHub 之外的人工动作)。

    同族另有两张 open:#16099(无任何层拒绝不自洽的聚合/字段类型配对,avg over 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

  3. zhuangjianguo commented on Sep 9, 2026

    @zhuangjianguo
    Collaborator

    裁定:1C + 2B —— 去平台修;本仓维持替代物并把措辞改成如实

    维护者裁决,2026-09-09,常设决裁批第 1 批,逐字:

    第 1 批 平台的问题去平台修,业务的问题按照你的意见。

    本卡的阻塞源是平台缺陷(语义层不做日期减法;AVG() 打在 datetime 上返回平均年份)⇒ 走「去平台修」。这也与决策分析写法的应用仓推荐序特例一致:阻塞源是平台缺陷 ⇒ 恒荐等待;⛔ 不荐绕行(形状容错、复刻平台规则)、不荐劈半落地。⇒ 1C(等平台)+ 2B(保留固定阈值,措辞如实)。

    ⛔ 因此 1A / 2A 明确不采纳:用每日任务在应用里补出分析层该有的日期运算,正是该特例点名的「复刻平台规则」。

    ⚠️ 公开更正:我呈报时对 1C 的描述被一个更晚的读数证伪

    呈报时我把 1C 写成「一次修好所有应用」。录裁前重读平台侧,这句话不成立:

    • 平台卡 objectstack-ai/objectstack#16737 — open,bug · priority:p2 · domain:services · pm:awaiting-maintainer,assignee os-trump。
    • 它已经有一个 open PR:objectstack-ai/objectstack#16778,标题逐字:

      feat(service-analytics)!: refuse an aggregate a datetime measure's field type cannot carry, and reconcile the storage-form annotations to one measured statement

    ⇒ 平台选定的补法是拒绝,不是提供。#16778 合并后,本仓依然算不出各段时长 —— 变化的是:那条「能跑通、能渲染、结果错」的路径会开始响亮报错,而不是给出 -0.849999999999909 这种可信的假数。

    洞被堵上了,能力没拿到。 这对本卡的意义:

    • 防错轴的紧急项(静默假数)由 #16778 解决 —— 那是本卡真正危险的部分,它有归宿了。
    • 第 9 章要的「各段时长」在可预见的将来仍然给不出,除非平台另有一张卡提供日期差度量原语。#16737 的验收清单第 3 条明说「Either it errors, or it returns something a reader cannot mistake for a duration」—— 两条路都不产出时长。

    ⛔ 我不静默重裁。裁定按字面执行为 1C + 2B。但若你看了上面这段觉得「等一个只会报错的修复」不是你要的意思,说一句,本卡回决策箱重开选项(那时真正的选项是:向平台立一张日期差度量原语的能力卡,还是接受第 9 章永久少这两个数)。

    裁后执行

    1. 状态转换:needs-user-decision → pm:blocked,正文加机器可读行 Blocked-by: objectstack-ai/objectstack#16737(同一笔标签写入)。
    2. §09 措辞如实(随折叠的设计文档 PR,链首 [Decision] DESIGN.md §03 requires a termination reason but declares no field for it #6,本卡为 Refs #31 的搭车项,⛔ 非 Fixes):第 9 章不再无条件承诺「各段时长」与「超 SLA」,改为记明现状 —— 交付的是阶段覆盖数与审批吞吐,两个原指标等平台的日期能力,并点名 #16737。沉默地留着一个兑现不了的承诺,正是本卡自己反对的形状。
    3. 卡 09 的 F3(法务 SLA 巡检)按 2B 写:固定阈值,且砖的标题必须自带阈值(「审查超 30 天」而不是「超 SLA」),因为九种合同类型的真实 SLA 是 2/3/5/10 天,30 天高于全部 —— 标题自陈阈值,读者就不会误读成按类型的违约。
    4. 解锁判据:#16737 关闭时本卡回队重验,⛔ 判据不是「PR 合并」而是「本仓升到含该修复的版本后,那条错路确实报错」。跨仓解锁判据是消费方可安装。
    5. 已在平台卡上留下本仓作为具名下游消费者的记录。

    Generated by Claude Code

  4. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    Collaborator

    席位裁定 —— 解锁判据未满足,pm:blocked 保持

    #80 在本仓实际安装的版本上重跑了这张卡的探针。判据没有满足,而且这张卡的措辞刚好证明了它自己的价值。

    实测(17.4.0,54 个 @objectstack/* 包全部 17.4.0,better-sqlite3 13.0.3,从 node_modules 读出)

    那条错路三层都不拦:

    层 结果
    作者期 validate / lint / typecheck 全部退出 0,derived 原样编进 dist/objectstack.json
    查询构建 POST /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 白纸黑字写着这个洞是开的(我自己拉的包,逐字核过):

      sum / avg over a temporal column are refused by no layer and answered by the backend (an epoch mean on SQLite, an error on Postgres) … a derived measure is numeric by construction, because computeDerived coerces its operands with Number().

    这正是本卡当初把判据写成「消费方可安装」而不是「上游 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

  5. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    Contributor

    Seat note · 2026-10-09T14:41Z — waiting state, written on the card.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    Contributor

    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 build exit 1 (measure-aggregate-field-type-refused ×2), PUT /api/v1/meta/dataset/probe31_cycle → 422 INVALID_METADATA with nothing stored, POST /api/v1/analytics/query avg(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 (main is 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

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    解锁判据已成立——请决定本卡怎么收尾 · 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 继续有效。

    你要做的:回一个字母。

    做法 代价 / 收益
    A(推荐) 关单(完成):1C 的等待已经结束,危险的错数已被平台拒绝,替代物按 1C + 2B 保留 零工作量;§09 不再承诺算不出来的指标
    B 改选 1A:日任务把各段时长 / 超 SLA 盖成合同上的持久字段 §09 字面兑现;需要你定字段名与语义(公开契约),并动日任务与 §03

    四棱分析

    • 实际业务需求:周转时长与超 SLA 是法务负责人真实会问的问题;但目前没有任何用户或部署在等这两个数,拉动来自设计文档本身。
    • 项目长远合理性:A 停在平台能兑现的范围内;B 等于在应用里补平台不提供的度量——1C 当初的取向正是不在应用里绕路。
    • 防 AI 写错:平台现在会响亮拒绝错误写法,A 下不会再有 agent 造出错数;B 会引入新的盖戳字段与日任务,需要新的正确性保证。
    • 创业阶段不扩散:A 不扩散;B 新增字段与任务逻辑。

    Generated by Claude Code

  8. 6 remaining items

  9. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    Unblocked — 2026-10-27

    upstream state installable here
    objectstack-ai/objectstack#25778 closed fix commit d1663ba is an ancestor of tag @objectstack/spec@18.2.0

    main now pins @objectstack/* 18.2.0 (PR #155, merge 21f2319, push CI Validate success), so the fix is installable in this repo. The ruling recorded in the body stands; this card returns to the queue for dispatch. The dev re-measures the premise on main before writing code.

    State: pm:blocked → pm:queue.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 17
    Ruling-ref: 6396322580
    Session: session_017nzWFRUo2U7evMkPdVniAn
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-31-cycle-time-measures
    Worktree: hotclm-issue-31
    Domain: repo:hotclm (single-lane sister repo — no domain:*)
    Seat: repo:hotclm#1
    File surface: src/datasets/** · src/dashboards/** · their translations — the two §09 measures (stage durations, over-SLA) on the 18.2.0 datetime-difference primitive; governed §09 text rides the seat's design-doc PR #120 — stop on breach; explain in the report
    Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; hand-derived for a sister repo), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: this app's analytics (how long a contract takes, which ones broke SLA) | objectstack-ai/objectstack#25778 shipped in 18.2.0 | every legal head and admin reading the CLM dashboards
    Thread-read: 6437400862
    Premise: re-measured by the dev on origin/main bc5c3e8 (@objectstack 18.2.0) with README personas first
    Serial constraints cleared: Unblocked 2026-10-27 (18.2.0 on main); in flight #127 (profiles), #158 (lifecycle actions) — disjoint files.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    Written 2026-10-27T09:49Z · PR #168 · head cfb92e3 · branch claude/issue-31-cycle-time-measures

    {
      "issue": 31,
      "status": "done",
      "branch": "claude/issue-31-cycle-time-measures",
      "pr": "https://github.com/objectstack-ai/hotclm/pull/168",
      "head": "cfb92e3",
      "session": "session_017nzWFRUo2U7evMkPdVniAn (parent seat's; this run is an os-dev subagent)",
      "premise_still_valid": true,
      "summary": "Half delivered, half re-measured as still blocked; body line 1 is 'Part of #31'. (1) Stage durations: 18.2.0's `duration` measure (objectstack#25778, d1663ba, an ancestor of tag @objectstack/spec@18.2.0, exit 0) now powers contract_cycle_time. It carries 5 avg-days measures: submitted→review started, review started→approved, approved→signed, signed→active, submitted→approved. They replace the 5 stage-coverage counts. On the legal workbench, approval_throughput (a count) becomes approval_turnaround (avg days submitted→approved, this month, compareTo previousPeriod), and a new row of 4 per-stage tiles is added. Per 1C, the stand-ins are replaced once the measure exists. (2) Per-type over-SLA: still NOT expressible. 18.2.0's $now anchor (objectstack#25808, ruling A) reads its addDays offset from a SAME-record column only. `addDays: { $field: 'contract_type.review_sla_days' }` answers 400 INVALID_FILTER at both doors, while `os validate` passes it. The fixed 30-day tile stays, per 2B. No app-side SLA column was stamped or mirrored, because the ruling forbids it. Also: the executive-board comment that said durations are inexpressible is corrected; the turnaround tile's description says 'equal-length period just before it' (measured: October was compared with [08-31, 09-30]), not 'last month'; and its success colour is dropped.",
      "tests": "Independent SQL on the throwaway DB (SQLite julianday(to)-julianday(from), rows with both stamps) against Chromium zh-CN dashboard reads, signed in as README personas created on 18.2.0: Legal Head (create-user + sys_user_position clm_legal_head + change-password) and Business Requester 1 (create-user + set-user-manager + change-password; pnpm demo:zh re-run hands over 43 contracts). Legal Head: SQL 2.0/49.0/8.0/3.0 (n=35/17/24/72) matches rendered 2.0/49.0/8.0/3.0; turnaround SQL 65.14 (n=7) vs compare 53.17 (n=6) matches rendered 65.1 ↑23%. Requester 1: SQL 2.0/59.5/8.0/3.0 matches rendered 2.0/59.5/8.0/3.0; turnaround 76.0 vs 47.0 matches rendered 76.0 ↑62%. Units are days: the service SQL is AVG(((ROUND((julianday(b)-julianday(a))*86400000.0)/1000.0)/86400.0)). No -0.85 artefact: the old avg-stamp + derived-difference spelling answers 400 DATASET_INVALID (raw SQL on this DB would have given -0.786). Native-SQL strategy served both personas, with the sharing scope in the same statement. category=nda filter: 75.5 = SQL 75.5 (n=4). By contract type: every row equals SQL. Monthly bucket (dimension approved_month): 400 INVALID_FIELD naming the measure. Baseline on origin/main bc5c3e8, as Legal Head: 6/12/6/2, 本月已批准 7 ↑17%. Console before and after: the same one 404 resource and one 401 get-session pre-login. Boot: no degraded/no-such-table/failed plugin.",
      "gates": "Head cfb92e3, each exit captured before any pipe: pnpm install EXIT=0; pnpm validate EXIT=0 (✓ Validation passed); pnpm lint EXIT=0 (6 suggestions, 0 warnings; origin/main bc5c3e8 measured identically, 6 suggestions and 0 warnings, so the #64 warning common.md expects is no longer on main and no warning was added); 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. CI check-runs on PR #168: not read (report delivered at local verification end; CI convergence is the seat's).",
      "dogfood_battery": "n/a — app repo, no `!` title, no Clause-② line",
      "mcp_calls": "0",
      "api_writes": "2 — fleet-write relay: pr_create objectstack-ai/hotclm#168 (read-back identical, 8871 bytes, assignee zhuangjianguo); this os-dev-report comment on hotclm#31 via post-stamped. git push x2 (not REST).",
      "open_questions": [
        {
          "question": "The per-type over-SLA half of #31 has no platform card that will deliver it. objectstack#25808 closed with ruling A (same-record offset only) and explicitly did not take B′ (a one-hop path offset). What should #31 be blocked by now?",
          "options": [
            "A: the seat files an objectstack card for a now-anchored comparand whose addDays offset reads a declared to-one path (B′). This amends the 2026-08-06 or 2026-08-12 platform ruling, so it is a platform maintainer decision. #31 stays open, Blocked-by that card, with the 30-day tile kept (2B).",
            "B: rule 2B permanent. §09 states that the per-type over-SLA is not in the product, and #31 closes when PR #168 merges (Fixes on a follow-up body edit by the seat).",
            "C: a per-contract negative SLA-days column stamped by the app, so the same-record anchor can read it. ⛔ Listed only to reject: §09 and the 2026-09-09 ruling forbid app-side stamping, and ruling 6396322580 reaffirmed it."
          ],
          "recommendation": "A, because ruling 6396322580 (C) chose to keep the goal and file the capability rather than make a platform gap a permanent product boundary, and the same reasoning applies to the half #25808 did not cover. The card's cost line must say it reopens B′."
        }
      ],
      "out_of_scope_findings": [
        "class: c · reach: `pnpm validate` (os validate 18.2.0) exit 0 for a dataset measure filter `review_started_at: { $lt: { $now: true, addDays: { $field: 'contract_type.review_sla_days' } } }` on a dataset with include ['contract_type']. At runtime the same spelling answers 400 INVALID_FILTER at POST /api/v1/analytics/dataset/query as a runtime filter (\"the addDays offset … is a dotted path, and SQL push-down compiles same-table column references only\") and as a measure filter (reason withheld). An AI that writes the per-type SLA count gets green gates and a tile that always errors. Seam: spec:NowAnchorSchema.addDays.$field (packages/spec/src/data/filter.zod.ts) → runtime: driver-sql crossFieldOffsetExpr same-table rule; lint: no rule judges the offset path at author time · dedupe words: \"$now addDays dotted path validate\", \"now anchor offset relationship path lint\", \"measure filter cross-object offset author time\", \"INVALID_FILTER addDays dotted\"",
        "class: a · reach: browser, Chromium zh-CN as Legal Head on the hotclm legal workbench: the turnaround tile reads \"↑ 23% 较上期\" drawn emerald for a turnaround that got 23% LONGER (worse). objectui packages/plugin-dashboard/src/DatasetWidget.tsx:1325 colours delta.direction 'up' emerald for every measure, and the spec has no key to declare lower-is-better on a metric tile's compareTo. Seam: spec:DashboardWidget.compareTo (no polarity) → renderer: objectui DatasetWidget compare-trend span · dedupe words: \"compareTo delta colour lower is better\", \"metric trend arrow polarity duration\", \"previousPeriod delta direction good bad\"",
        "carrier: #156 (src/flows this round) · noted, not filed: src/flows/legal-review-sla.flow.ts and renewal-notice.flow.ts still name objectstack#16737 as the tracker for the per-type SLA and durations. The durations now exist, and the SLA's tracker is the open question above.",
        "carrier: none · noted, not filed: one 404 resource appears in the browser console on the legal workbench both before and after this change, and was not identified (not captured by the page's response listener)."
      ],
      "governed_text_proposal": {
        "DESIGN.md §02 table row Insights (line 80)": "| Insights | 周转分析 | 阶段时间戳 → dataset(平台 `duration` 度量)→ 看板:各段平均时长与平均审批周转;按类型的「超 SLA」等平台跨对象比较(§09) |",
        "DESIGN.md §09 dataset bullet": "- `contract_cycle_time` — `submitted_at → review_started_at → approved_at → signed_at → activated_at` 各段平均时长,外加提交 → 批准(平均审批周转);单位天,平台 `duration` 度量(`objectstack-ai/objectstack#25778`,18.2.0);按类型与法务经办。每个平均值只计入两个时间戳都已记录的合同;不出按月趋势(平台拒绝带粒度分桶的时长度量)",
        "DESIGN.md §09 法务工作台 bullet": "- **法务工作台** — 待受理数 · 审查中 · 审查超 30 天(固定阈值,写进砖的标题)· 谈判停滞 · 平均审批周转(提交 → 批准的平均天数,本月对比紧挨在前的等长时段,用平台 `compareTo: previousPeriod`)· 各阶段合同数(横向条形图,按合同数从多到少排;只画在办的六个阶段)· 各段时长(四块砖,每段一块,单位天)",
        "DESIGN.md §09 paragraph replacing line 308's first half (the 16737 / 25778 pointer)": "**各段时长已交付;按类型的「超 SLA」仍不在交付面内**(维护者裁定 2026-09-09 1C + 2B,2026-10-25 C)。平台 18.2.0 提供两个 datetime 列之差的聚合度量(`duration: { from, to, unit }`,`objectstack-ai/objectstack#25778`),各段时长与平均审批周转都建在它上面;「`AVG()` 打在 datetime 上再相减」那条错路自 17.7.0 起在作者期与查询期都被拒绝(`objectstack-ai/objectstack#16737`)。「超 SLA」要拿 `review_started_at` 比「现在减去该合同类型的 `review_sla_days`」:18.2.0 的 `$now` 锚点(`objectstack-ai/objectstack#25808`,裁定 A)只读同一条记录上的数值列,经 `contract_type` 的点路径在查询期被拒绝(`400 INVALID_FILTER`),跟踪于 〔open question 的新平台卡号〕。在那之前 SLA 以固定 30 天阈值呈现且阈值写进砖的标题(九种类型的真实 SLA 是 2/3/5/10 天,30 天高于全部)。〔「审批瓶颈」一段原样保留〕⛔ 不得用应用侧日任务盖戳字段,也不得在合同上镜像一列 SLA 天数,把「超 SLA」补出来 —— 那是在应用里复刻平台规则。",
        "DESIGN.md §12 table, new row": "| 按类型超 SLA | objectstack#25808(同记录已交付;跨对象偏移待新卡) | 固定 30 天阈值,阈值写进砖的标题 | 按 `clm_contract_type.review_sla_days` 计数 |"
      }
    }

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    Ruling-ref: 6396322580

    Landed (part) — PR #168 · 2026-10-27, and re-blocked

    reading value
    PR #168 MERGED (squash on main) at its ACCEPT head cfb92e3; Part of #31
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」
    delivered 各段时长 on the legal workbench (four tiles, in days) and 平均审批周转 in place of the 审批吞吐 count, both on 18.2.0's duration measure. The numbers match independent SQL
    still blocked per-type 「超 SLA」: the $now anchor reads its offset from the same record only (objectstack#25808 ruling A). The fixed 30-day tile stays (2B), and nothing is stamped in the app

    Next: objectstack-ai/objectstack#26601 asks the platform to let the offset follow contract_type (reopening B′). If the platform says no for good, that answer comes back to the maintainer, since ruling C chose to keep the goal. Also filed: objectstack-ai/objectstack#26602 (a rising turnaround is drawn green). §09 text rides seat PR #120. State: pm:dispatched → pm:blocked, assignee cleared; the body's Blocked-by: now names #26601.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions