Skip to content

pnpm demo stops being re-runnable once the daily jobs have run: the seed re-asserts statuses the state machines refuse to go back to #41

Closed

Description

@zhuangjianguo

Ruled: 6395830052 · letter A · 2026-10-25T09:14Z (A via a platform card: seed-dataset applyOn: 'create'; interim README truth)

Blocked-by: objectstack-ai/objectstack#25761

Found while browser-verifying card 09 (#39). Not a defect in the jobs — a consequence of them existing that the seed does not account for. Filed unassigned, out of scope for #39.

The documented workflow that now breaks

README.md tells the evaluator to re-run the demo:

Every dataset is an upsert, so create the accounts and run pnpm demo again and their contracts are handed over.

That works on a freshly seeded database. It stops working once card 09's daily jobs have run once, because those jobs are the declared writer of two statuses the fixture also declares, and the state machines refuse the way back.

Reproduction, measured on claude/issue-39-post-signature-jobs @ 83eda0a, 17.4.0, better-sqlite3

  1. pnpm demo on an empty database — 820 ok / 0 errors.
  2. Run the sweeps once each: POST /api/v1/automation/expiration_sweep/trigger, POST /api/v1/automation/payment_overdue/trigger.
  3. pnpm demo again.

Boot banner on step 3:

⚠ Seeds:   app.objectstack.hotclm 818 ok / 2 errors ⚠
  ✗ Failed to write clm_contract record #99 (title=Data Processing Agreement — Fernway Cleaning):
    hook 'contract_state_machine' threw: A expired contract is closed; its status cannot
    change to active. Start a renewal or an amendment instead.
  ✗ Failed to write clm_payment_plan record #2 (contract+seq=["…","3"]):
    hook 'payment_plan_state_machine' threw: Payment instalment status cannot go from
    overdue to planned. Allowed from overdue: partial, paid.

Two rows are dropped and every other row still loads, so the run looks almost clean — the same "the summary reads like a clean load" shape #39's acceptance criteria warn about.

Why it happens

The fixture writes status as a literal on every upsert. Once a job has moved a row on:

  • expiration_sweep (F13) moves an active contract past its end date to expired. §03 makes expired terminal, so re-asserting the fixture's active is refused.
  • payment_overdue (F11) moves a due or partial instalment past its planned date to overdue. §03 gives overdue only → partial / → paid, so re-asserting planned is refused.

Both refusals are correct: the state machines are doing exactly what DESIGN.md §03 asks. It is the fixture's unconditional status that has no answer for "this row has since moved on".

The two rows are the ones whose dates the jobs act on, so the count grows as the demo database ages: any contract that expires and any instalment that falls into arrears joins the set.

What a fix has to decide (hence: not a rider on #39)

Whichever way it goes it is a change to card 08's seed, and it needs a product answer first:

  • A — leave status out of the upsert on re-seed. The fixture stops fighting the jobs, and a demo database shows the lifecycle actually moving. Costs the guarantee that pnpm demo restores a known corpus.
  • B — re-seed onto a clean database only, and say so in the README instead of promising re-runnability. Cheapest, and honest, but it takes away the account-handover workflow the README currently recommends.
  • C — keep the promise and make the seed reset the row first (overdue → paid → … is not reachable either, so this means a system-context reset, not a transition). Most machinery, and it puts a second writer on the columns Card 09 — F9 activation · F3/F4/F10–F14 scheduled jobs · F16 executed upload (M3) #39 gave exactly one.

I have no view worth acting on between them; A looks smallest but it changes what "the demo dataset" means, which is a §10 question.

Activity

  1. zhuangjianguo commented on Sep 9, 2026

    @zhuangjianguo
    CollaboratorAuthor

    维护者速读

    分诊:本席把这张卡收进决策箱(needs-user-decision),并补上入箱必带的四棱块与速读。⛔ 不裁 —— 选项之间的差别是「demo 数据集意味着什么」,那是 §10 的产品问题。

    事情:README 现在告诉试用者「建好账号后再跑一次 pnpm demo,合同就交接过来了」。卡 09 的定时任务上线后,这条路只在全新数据库上还成立。任务跑过一次之后再跑 demo,会有行被拒绝。

    为什么:种子把 status 当字面值无条件写入。而定时任务是这两个状态的唯一声明写手,状态机不许回头:

    • 到期扫描把过了终止日的 active 合同推到 expired,而 expired 是终态 —— 种子再写 active 被拒。
    • 逾期任务把过了计划日的 due / partial 期次推到 overdue,而 overdue 只准去 partial / paid —— 种子再写 planned 被拒。

    两条拒绝都是对的,状态机在照 §03 干活。是种子那个无条件的 status 没有「这行已经往前走了」的答案。

    ⚠️ 最坏的部分不是掉两行,是它看起来几乎是干净的:818 ok / 2 errors,其余全部照常加载。这正是卡 09 验收标准反复警告的「摘要读起来像成功」那个形状。而且随着演示库变老,掉的行会变多 —— 任何到期的合同、任何进入逾期的期次都会加入这个集合。

    你要做的:回一个字母,A、B 或 C。

    选项 × 真实代价

    做法 客户感知到的后果
    A 重跑时不写 status(只在首次种入时写) 种子不再跟任务打架,演示库能真实展现生命周期在动。代价:失去「pnpm demo 能把库恢复到已知状态」这个保证
    B 只支持在干净数据库上重种,README 如实改写 最省,且诚实。代价:拿掉 README 现在推荐的「建账号→重跑→合同交接」这条动线,而那条动线正是 #28 在讨论的开箱体验
    C 保住承诺,让种子先把行重置回去 保留一切。代价:overdue → planned 走不通,所以这意味着系统上下文强改,等于给卡 09 刚刚确立单一写手的那两列再加一个写手

    业务含义直译

    A = 演示库像一个真实运行了半年的系统,你重跑只补数据不倒转时钟;B = 演示库是一次性快照,要重来就重装;C = 演示库有一个「时光倒流」按钮,代价是有两个人能改同一块表。

    四棱分析

    os-decision-facets

    ① 项目长远合理性 — 偏 A,明确反对 C。卡 09 刚刚把「谁写 overdue / expired」收敛成唯一写手(overdue is written by these jobs only,写在卡里也写在代码里)。C 立刻再开一个写手,把刚建立的不变量拆掉,方向反了。A 是让种子退出这两列的争夺。

    ② 实际业务拉动 — 偏 B,判据实测。今天谁撞上?试用者,在 #28 描述的那条动线上 —— 建三个账号、重跑 demo 拿合同归属。那条动线是真的(README 写着,卡 09 的浏览器验证也照做了),但它只在首次种入后立刻执行,那时任务还没跑过,所以今天没有人真的被这个 bug 挡住。拉动是潜在的,不是现行的。

    ③ 防 AI 犯错 — 这一棱最要紧,且三个选项都要配一件事。出错时谁看到什么:今天是 818 ok / 2 errors 混在启动日志里,没有任何人会注意到,而缺的恰恰是演示库里最有意思的两行。⚠️ 无论选哪个,都应要求「种子写入被状态机拒绝时,启动横幅必须响亮」—— 否则演示库会静悄悄地烂掉。C 在这一棱最差:系统上下文强改会让「状态机拒绝」这个信号本身消失。

    ④ 创业阶段不扩散 — 偏 B,其次 A。B 零新增(改一段 README)。A 是种子逻辑的一处收窄,也不算扩张。C 要新增一套重置机制,是 declare-and-maintain,最重。

    推荐:A,回退 B。 ①③④ 都反对 C,②在 A/B 之间偏 B 但拉动是潜在的。选 A 的理由是它同时满足①和④,且保住了 #28 那条动线;选 B 的理由是最省且诚实。⛔ 不建议 C。

    本分析看不见什么:你是否把「pnpm demo 幂等地恢复一个已知语料」当成一条产品承诺。如果是,A 就动了那条承诺,B 是把承诺改小,而这两者的差别只有你能定。

    关联

    #39 / PR #42(发现处,已合并 a7b7db5)· #28(README 那条开箱动线,决策箱中)· 卡 08 的种子 · DESIGN.md §10


    Generated by Claude Code

  2. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    CollaboratorAuthor

    ⚠️ 前提刷新:本卡的复现被推翻了,而替代它的观察更糟

    ⛔ 不是裁决,不动标签。这条是把只存在于会话里的结论落到卡上 —— 结论只在 chat 就是半状态。

    本卡的正文说:任务跑过之后重跑 pnpm demo 会报错并掉两行(818 ok / 2 errors,两条状态机拒绝)。dogfood 走查(#45,main @ a7b7db5,17.4.0)测到的不是这个。

    读数 本卡正文 #45 实测
    重跑 pnpm demo 818 ok / 2 errors,两行被拒 三轮全部干净,0 报错
    付款期次 overdue → planned 被状态机拒绝 payment_overdue 跑完后那一轮把 18 期已逾期的款静默改回去了(overdue 30 → 12)
    到期合同 expired → active 被拒 ⛔ 未测 —— 那次 expiration_sweep 选中 0 行,这条路径从未被走到

    这为什么比原来的缺陷更严重

    原缺陷是响亮的:种子撞上状态机、被拒、启动横幅上有两行错。难看,但可见,而且数据是对的。

    新观察是静默的:重跑把一个每日任务刚做完的工作撤销了,0 errors,横幅干净。演示库里「这 18 笔款逾期了」这个事实消失了,没有任何人会知道。一个安静的错误结果和一个正确结果无法区分 —— 这正是这个仓反复在防的形状。

    ⚠️ 而且它与卡 09 的实测直接矛盾,这一点牵连到已合并的代码

    卡 09(#39 / PR #42,已合并 a7b7db5)的验收明确测到 payment_plan_state_machine 拒绝 overdue → planned:

    Payment instalment status cannot go from overdue to planned. Allowed from overdue: partial, paid.

    而 #45 在同一棵树上测到同一转换被静默接受。两份读数不可能都对。 三种可能,⛔ 都还没被排除:

    1. 状态机在种子路径上没有被强制(种子以系统上下文写入,可能绕过了 hook)—— 若是这样,卡 09 交付的那台状态机在它最需要生效的地方是空的。
    2. 两次测的不是同一个转换(例如 Dogfood pass on M3-complete main: drive the whole contract lifecycle in a browser and report what a real user hits #45 那轮里那些行当时并非 overdue)。
    3. 其中一次的读数取错了(例如从日志而非数据库读)。

    第 1 种成立的话,这不只是 #41 的问题 —— 它意味着我以复核人身份验收 PR #42 时,接受了一个在种子路径上不成立的不变量。

    本卡状态与下一步

    ⛔ 不关、不改选项、不撤卡。 但正文的复现步骤现在是未经证实的,读者不要照抄。

    需要按序做两件事,然后本卡的 A/B/C 才可判:

    1. 测 expiration 那半边 —— 需要一个足够"老"的数据库让 expiration_sweep 真的选中行。Dogfood pass on M3-complete main: drive the whole contract lifecycle in a browser and report what a real user hits #45 没走到这条路径,所以本卡关于 expired → active 的那一半既未证实也未推翻。
    2. 查清与卡 09 的矛盾 —— 一个具名判据:以种子路径把一个 overdue 期次写成 planned,从数据库读回它的状态,并同时读 hook 是否被调用。答案决定这是 pnpm demo stops being re-runnable once the daily jobs have run: the seed re-asserts statuses the state machines refuse to go back to #41 的选项问题,还是一张关于状态机强制面的新缺陷卡。

    ⇒ 若第 2 项证实是路径绕过,那是一张独立的缺陷卡,且优先于本卡:本卡在问「种子该怎么和任务共处」,而那张在问「状态机到底有没有生效」。

    出处:#45 的走查报告与其席位复核;读数时刻 2026-09-10T01:5xZ,树 a7b7db5。


    Generated by Claude Code

  3. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    CollaboratorAuthor

    ⚠️ 更正我自己上一条评论:那个「更糟」的判断是错的,而本卡现在可判了

    上一条我说 dogfood 观察到的「静默改回 18 期」比原报的响亮拒绝更糟,并列了三种可能。#50 的调查把它settle了:候选 (b)。⛔ 没有绕过,状态机是好的,而我那个「更糟」的定性不成立 —— 我把一条正常工作的合法边说成了缺陷。

    真正在发生的事

    未改动的种子语料里,payment_overdue 只能动那 18 行 partial:arrears 级选 due/partial 且过了 planned_date,而 150 行 fixture planned 的日期全在 +35 天以后,planned→due 那一级选中 0。那 18 行的 fixture 目标值就是 partial,所以重跑把它们从 overdue 写回 partial —— 而 overdue → partial 是你裁的 #10 → 2A 明确声明的合法边。

    ⇒ 「18 行被改回、0 报错」是一台正常状态机的正确输出。

    而卡 09 那次拒绝来自它自己造的探针行(fixture 值为 planned、被回拨日期推到 overdue),overdue → planned 确实被拒。#50 重造了那个探针,拿到卡 09 的报错原文与 818 ok / 2 errors。两次都对,测的是不同的行。

    逐行证据(全部 300 行按 id 快照、逐行 diff 而非看总数):A→B 恰好 18 行变,全是 partial → overdue;B→C 同一批 18 个 id,全是 overdue → partial,断言了集合相等。且 upsert 会跳过已与 fixture 相等的行(inserted 0, updated 18, skipped 802),所以 820 行里只有那 18 行被真正写过。

    这对本卡的三个后果

    ① 正文的复现步骤仍然未经证实,但原因变了。 它描述的 818 ok / 2 errors 需要一行语料里不存在的行 —— 卡 09 造的那种。在未改动的演示库上,重跑 demo 是干净的,且只会沿合法边把那 18 行写回。⇒ 正文该改写成这个形状。

    ② 新增一条抬高代价的事实,属 A/B/C 权衡的输入:被拒的种子记录是被整行丢弃的。 #50 的探针 A 里,被回拨的 planned_date 连同状态一起没写进去。⚠️ 也就是说:fixture 行里一个非法状态,会静默连带损失那一行的其它列。选项 C(让种子先重置行)本来看着最保守,这条事实让它更贵;而选项 A(重跑不写 status)因此更有吸引力 —— 它把整类整行丢弃的风险一起移走。

    ③ 「无声」这个论点要从 A 的理由里撤掉。 我上一条把静默无报错当成 A 的论据。那是错的:静默是合法边的正常表现,不是隐患。A 的理由现在只剩两条 —— 卡 09 刚把 overdue/expired 收敛成单一写手(C 会再开一个写手,方向相反),以及整行丢弃的代价。

    仍然缺的那一半,⛔ 不要当成已测

    expiration 那半边(expired → active)依然未被走到。 #50 只做了付款侧;dogfood 那次 expiration_sweep 选中 0 行。⛔ 本卡关于到期合同的那一半既未证实也未推翻,别照抄。

    ⇒ 本卡出决策箱之前该先把那半边测掉,否则 A/B/C 是在半份事实上选。

    一条与本卡相邻、影响读者判断的事实

    #50 顺带测出:面向人的种子横幅本身是不可靠的那一面,不是 loader 的计数器。 第一次启动时内联 seed 超了 8000ms 预算转入后台,Seeds: … 820 rows 那一行从未打印,而 seed 在 Server is ready 之后还写了 3.5 分钟。权威的是后来那条 WARN [Seeder] Seed loading completed JSON。任何基于「重跑后横幅说了什么」的论证都要改用 loader 的 JSON。

    (也因此顺带解释了 #49 的 dev 为何只看到 0 条 ERROR [SeedLoader] 而 dogfood 看到 120 条 —— 同一个脚本、不同的预算结局。)

    出处:#50(已关闭,调查完成)· 读数时刻 2026-09-10T07:1xZ · 树 a7b7db5


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Evidence from the 17.7.0 browser pass · 2026-10-10T02:47Z — #87, suspect 6. The failure has changed shape on 17.7.0:

    • Re-running pnpm demo after the daily jobs ran: seed summary {"inserted":0,"updated":18,"skipped":801,"errored":1}.
    • The one error: Failed to write clm_contract record #104 (Lease Agreement — Granite Facilities): A terminated contract is closed; its status cannot change to active — a contract legal terminated during the run.
    • Worse, silently: the 18 "updates" reverse payment_overdue's work — 18 part-paid instalments go from overdue back to partial, because overdue → partial is an allowed transition. Overdue drops from 30 to 12 and nothing says so.
    • The overdue → planned error this card quoted does not appear on 17.7.0.

    So the re-run no longer fails loudly on the jobs' instalments; it quietly undoes them. Relevant to the options here, and to #82 (whether pnpm demo arms the daily jobs at all).


    Generated by Claude Code

  5. added a commit that references this issue on Oct 10, 2026
  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Two more measured additions to this card's set · 2026-10-10T04:32Z — from #97 (PR #102, 17.7.0):

    1. With PR The renewal-notice and expiration-sweep jobs produce nothing although 11 contracts carry is_expiring, and five of the six daily flows report acted: 0 while they notify #102's placed rows, re-running pnpm demo after expiration_sweep has run prints 819 ok / 1 error — contract_state_machine refuses to move NDA-2026-0006 from expired back to active. Loud, same class.
    2. A re-seed resets ICA-2026-0002's is_expiring to 0, so the next F12 run sends that renewal notice again. Silent — the fixture re-asserting a column a job wrote, like the 18 overdue → partial reversals measured in Full browser test on 17.7.0 main: drive the whole contract lifecycle as every audience, in en and zh-CN, and report what a real user hits #87.

    Generated by Claude Code

  7. zhuangjianguo commented on Oct 25, 2026

    @zhuangjianguo
    CollaboratorAuthor

    Ruling: batch #2 item 5 · letter A · maintainer 「同意」 2026-10-25T09:14:04Z

    裁定:A —— 任务写过的列重种时不再覆盖;走平台,期间 README 说真话

    维护者在第 2 批决策箱回了「同意」(= 推荐项 A,回退 B)。裁决文档:https://claude.ai/artifact/X4sZxhLrEYshLjkgYp4svr 。

    鲜度门:正文 2026-09-09T17:00Z;评论五条,最后一条 2026-10-10T04:32Z(#97 的两条新增读数)。录裁前重读,无变化。⚠️ 正文的复现步骤已被评论 5614657372 推翻,读者不要照抄;今天的事实形状是:未改动语料上重跑干净但把 18 期 overdue → partial 静默改回(合法边);terminated → active、expired → active 各响亮拒绝 1 行;is_expiring 复位导致 F12 续签提醒重发(静默);被拒的种子行整行丢弃(#50)。

    为什么走平台:同样的需求 hotsrm 先撞上,维护者在 objectstack#25577 裁了 B(每列 applyOn: 'create' | 'always'),PR #25664 于 2026-10-24 合并。但它落在 import 门(packages/core/src/utils/import-runner.ts、packages/rest/src/import-prepare.ts),seed loader(packages/runtime/src/seed-loader.ts)没有这个列策略。去重:#25577 / #25475(import,已关)、#8740(driver 内部,开);seed 数据集的为 0。

    裁后执行

    1. 本席立 objectstack 卡(类 ③:把 applyOn: 'create' 延伸到 seed 数据集的列),回链见下一条评论;本卡 Blocked-by 它。
    2. lane 现在可做:README 改成今天的真话 —— 重跑 pnpm demo 只在干净库上有「恢复已知语料」的保证;任务跑过之后重跑会沿合法边把任务写过的列改回,且被状态机拒绝的行整行不写。搭车 Refs #41,纯文档。
    3. 平台发布后 hotclm:给任务写过的列(status、is_expiring、各阶段时间戳等 —— 以卡 09 的「单一写手」清单为准)打 applyOn: 'create',README 改回「建账号 → 重跑 → 交接」的承诺。
    4. ⛔ 选项 C(种子先重置行)明确不采纳:系统上下文强改,给卡 09 刚收敛成单一写手的列再加一个写手,方向相反。

    ⛔ 本席不动标签;lane 席位派发时转标签并带 Ruling-ref:。


    Generated by Claude Code

  8. zhuangjianguo commented on Oct 25, 2026

    @zhuangjianguo
    CollaboratorAuthor

    Platform card filed per the A ruling above: objectstack-ai/objectstack#25761 — seed-dataset columns take the import door's applyOn: 'create' (#25577 / PR #25664), so a re-seed leaves job-written columns alone. Class ③, unlabelled, awaiting triage there.

    Blocked-by: objectstack-ai/objectstack#25761 (the README truth edit is not blocked and can go now, Refs #41)


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    Out of the decision box — Ruling-ref: comment 6395830052 (batch #2 · maintainer 「同意」).

    • State: needs-user-decision → pm:blocked, Blocked-by: objectstack-ai/objectstack#25761 (seed-dataset applyOn: 'create').
    • The ruling's step 2 (README states today's truth about re-running pnpm demo) is not blocked: the seat dispatches it now as a Refs #41 docs PR; the card stays pm:blocked for the column policy.

    Generated by Claude Code

  10. 5 remaining items

  11. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    Unblocked — 2026-10-27

    upstream state installable here
    objectstack-ai/objectstack#25761 closed fix commit 493a6c4 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

  12. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 17
    Ruling-ref: 6395830052
    Session: session_017nzWFRUo2U7evMkPdVniAn
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-41-seed-apply-on-create
    Worktree: hotclm-issue-41
    Domain: repo:hotclm (single-lane sister repo — no domain:*)
    Seat: repo:hotclm#1
    File surface: src/data/** (applyOn: 'create' on the job-written columns per card 09's single-writer list) · README.md (restore the re-run promise) — stop on breach; 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 demo (pnpm demo stays re-runnable after the daily jobs ran) | objectstack-ai/objectstack#25761 shipped in 18.2.0 | everyone who re-runs the demo
    Thread-read: 6437391562
    Premise: re-measured by the dev on origin/main 0159171 (@objectstack 18.2.0) with the declared demo staff (PR #171)
    Serial constraints cleared: #156 landed (PR #170, flows) and #28 landed (PR #171, config / README); round 17 dispatches #43, #160 and #41 together on disjoint files; in flight #169 (lifecycle actions). #19 (seeds) follows #41; #99, #161, #162 (launch wizard) follow.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 41,
      "status": "done",
      "branch": "claude/issue-41-seed-apply-on-create",
      "pr": "https://github.com/objectstack-ai/hotclm/pull/176",
      "session": "session_017nzWFRUo2U7evMkPdVniAn",
      "premise_still_valid": true,
      "summary": "Ruling 6395830052 letter A, step 3. On the 18.2.0 key `columns.FIELD.applyOn: 'create'`, the demo datasets now declare 24 lifecycle columns create-only. clm_contract has 18: status, is_expiring, is_backfilled, route_* x4, approval_status, the seven stage stamps submitted_at..closed_at, termination_reason, archive_no, archived_at. clm_obligation has status and completed_at. clm_payment_plan has status, actual_date, actual_amount and invoice_no. The set starts from the single-writer family contract.object.ts documents (state machine plus F2/F5/F9-F14, which includes card 09's jobs) and adds the columns written in the same write as a status transition, so a re-run cannot split a status from its stamps. owner_id and legal_owner stay always. README restores the re-run promise with three stated limits: other edited columns revert; decided deviations and signature rounds are not covered; lifecycle stamps keep their dates while fixture dates slide. The stale 'operator creates the accounts and runs pnpm demo again' comments in keys.ts, contract.seed.ts, negotiation.seed.ts and plan-contracts.ts are rewritten for PR #171's platform-created demo staff. Measured on main 0159171 and on the branch with one identical sequence: fresh demo, all six daily jobs fired, four persona desk actions plus two deletes, re-run. main: {inserted 2, updated 20, skipped 805, errored 3}, refusing terminated->active, expired->active and paid->due, with 18 overdue->partial reversals, is_expiring reset and the archive undone. Branch: {inserted 2, updated 0, skipped 828, errored 0}, every job and desk value kept, the deleted rows restored. On the next tick F12 re-sent on main (renewal notices 1 -> 2, payment_overdue 18 -> 36) and sent nothing on the branch (1 and 18). The daily jobs could not be started on demand on 18.2.0 (403, see findings). As the seat directed, they were fired by an uncommitted measurement-only cron override ('* * * * *' at src/flows/_daily-sweep.ts:108), reverted before each re-run and before the commit; git diff HEAD -- src/flows is 0 bytes.",
      "tests": "No unit-test surface in this metadata-only app; no ablation (seed fixture and docs). Evidence is the end-to-end measurement above. Logs: scratchpad issue-41/{main-r1,main-r2,main-r3,br-r1,br-r2,br-r3,br-r4}.log; row snapshots {main,br}-{B,C,D}.json; per-row diffs from diff.mjs (main B->C: archive_no/archived_at x1, is_expiring true->false x1, payment status overdue->partial x18, new x2; branch B->C: new x2 only). Persona reads in Chromium zh-CN through the Console sign-in on the branch after the re-run: finance@ overdue 30, partial 0; counsel2@ Cobalt MSA terminated with reason and closed_at, Basalt NDA expired; records@ Beacon expired ARC-41-TEST with archived_at. Console errors were only the pre-login 401 session probe (plus one 404 resource for finance). NOT MEASURED: a requester marking an obligation done (PATCH as requester2@ answered 403 'update on clm_obligation requires edit access to its master record'); no alternate route was taken.",
      "gates": "local, worktree hotclm-issue-41; the bytes were then committed unchanged as ab8a2af; each exit was captured to a file before any pipe: pnpm install exit 0; pnpm validate exit 0 ('✓ Validation passed (1104ms)'); pnpm lint exit 0 ('6 suggestion(s)', 0 warnings, none in src/data); pnpm typecheck exit 0; pnpm lint:i18n-gate exit 0 ('COVERAGE : 0 missing keys across 2 locale(s)'); CLM_DEMO_SEED=1 pnpm validate exit 0 ('✓ Validation passed (1341ms)', parses the 24 columns keys through defineSeed/SeedSchema). CI on PR #176: not read at report time.",
      "dogfood_battery": "n/a — sister repo; no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — fleet-write relay pr_create (dispatch fw-20261027T111439Z-34a1b8, run 40494672991 success, hotclm PR #176, body read back identical 9078 bytes, assignee zhuangjianguo); this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/hotclm/issues/41/comments). Plus git push of the branch (not REST). The demo-local REST writes against localhost:3153 (persona actions, deletes) are not GitHub writes.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: b · reach: public door, measured: POST /api/v1/automation/payment_overdue/trigger (and the other five daily sweeps) as the platform dev admin on @objectstack 18.2.0 answers 403 PERMISSION_DENIED 'This caller may not start this flow. A flow that runs on its own trigger starts there, or as a sub-flow from a parent flow's `subflow` node.' · evidence: runtime flow-start-admission.ts isElevatedSelfTriggered (objectstack#25643, f1ddace887) refuses any non-system caller for runAs:'system' schedule flows, and no capability opens it; IJobService.trigger()/replay() have no HTTP or CLI door, and a missed run is never replayed. Consequence: an operator or evaluator cannot run a system-run scheduled flow on demand (cards #39/#87/#97 measured with /trigger on 17.x; on 18.2 the only way is the cron itself) · Seam: spec:Flow.runAs+type schedule → runtime:flowStartRefusal at POST /automation/:name/trigger · dedupe words: scheduled flow run now, trigger 403 runAs system schedule, flow-start-admission elevated self-triggered, job service trigger HTTP, replay missed schedule · the seat decides whether to file it",
        "class: (none — not measured) · residual of #41's class not covered by the ruling's job-written columns: a re-run re-asserts clm_deviation.status and clm_signature.status, so a deviation decided or a signature round advanced by a user on a seeded row would be re-asserted to its fixture status and possibly refused (deviation_state_machine: decided once). Not measured; stated as a limit in the README · carrier: noted in PR #176 Acceptance notes, not filed",
        "carrier: noted, not filed — clm_review.reviewer still names the Dev Admin although Legal Counsel 1/2 exist at seed time since PR #171. Not changed: the right reviewer depends on the review stage (legal/compliance → counsel, finance → Finance Controller, business → requester), and scripts/demo.mjs (outside src/data) mirrors DEMO_USER as its pre-seed check. Comments in keys.ts and negotiation.seed.ts now say it is open.",
        "carrier: #64-family (controlled_by_parent edit) · noted, not filed — requester2@example.com cannot mark its own obligation done: PATCH /api/v1/data/clm_obligation/:id {status:'done'} answers 403 'requires edit access to its master record (master clm_contract not editable by this user (row-level security))' (the obligation's owner, dealt the contract's requester); may be the known controlled_by_parent shape (#64)"
      ],
      "governed_text_proposal": "none"
    }
  14. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    Contributor

    Ruling-ref: comment 6395830052 (letter A), step 3

    Landed — PR #176 · 2026-10-27

    reading value
    PR #176 MERGED (squash on main), merged at its ACCEPT head ab8a2af; Fixes #41 closes this card
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」

    Release: running pnpm demo again no longer walks back what the jobs and desks did. Statuses, stage stamps, approval routing and payment actuals stay as they are, and the fixture's own dates still slide forward. State: pm:dispatched removed, assignee cleared.


    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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions