Repository navigation
The contract page's new-version form defaults "Current" off, and two versions of one contract can both be current #96
Description
Activity
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsClaim: PM loop round 8
Session:session_01HihZ11bQSqjCgjzHbpv4M1
Account:zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-96-current-version
Worktree:hotclm-issue-96
Domain:repo:hotclm(single-lane sister repo — nodomain:*)
Seat:repo:hotclm#1
File surface:src/objects/contract-version.object.ts· a newsrc/objects/contract-version.hook.ts(and its registration insrc/objects/index.ts) ·src/pages/contract_detail.page.ts(the version form's default, if that is where it lives) ·src/translations/**(only for a new message) — ⛔ notsrc/objects/contract.hook.ts(held by PR #91) — stop on breach; explain in the report
Container & model:S,mode:subagent,model: opus(TIER_DEFAULT; hand-derived for a sister repo)
Clause-②: no
Responsibility:this app's version object and its hooks | none | legal and records adding versions on every contract page
Thread-read: none
Serial constraints cleared: PR #91 holdscontract.hook.ts·contract-intake.flow.ts·contract-lifecycle.actions.ts; #93 holdscontract-approval.flow.ts·README.md·scripts/demo.mjs; #97 holds the renewal / expiry flows — all outside this surface
Read at: 2026-10-10T04:04Z
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 96, "status": "needs_decision", "branch": "claude/issue-96-current-version", "pr": null, "session": "session_01HihZ11bQSqjCgjzHbpv4M1", "premise_still_valid": true, "tree": "46e65f0", "platform": "@objectstack/* 17.7.0 (pnpm-lock unchanged since the #87 measurement tree c31c7e2)", "summary": "Stopped before code, as Zone 1 and the card both rule: DESIGN.md section 03 does not pin either half. The clm_contract_version row lists only `is_current boolean`, and the approved → signing guard reads 「存在 `kind: clean` 的当前版本」. That says nothing about how many versions may be current, and nothing about a default. The governed proposal docs/design/00 section 5.5 doesn't even require 'current': its guard is 「送签必须存在一份清稿」 / 「存在清稿版本」. The premise holds on origin/main 46e65f0 and the default is app metadata, not a Console defect. Two questions with options and a recommendation are below. No PR, no files changed, nothing filed.", "premise_evidence": [ "Form default = app metadata: src/objects/contract-version.object.ts:83-88 declares `is_current: Field.boolean({ defaultValue: false })`. The page's Versions tab (src/pages/contract_detail.page.ts:265-274) is a bare record:related_list with no form defaults, so the platform form shows exactly what the object declares. This is not a Console defect, and there is no platform cause to report.", "Exclusivity is not enforced anywhere: the only hook on clm_contract_version is `contract_version_display_name` (src/objects/mirror.hook.ts, it stamps display_name only). src/objects/hooks.ts registers contract.hook + mirror.hook and nothing else. `grep -rn is_current src` finds no writer that un-marks siblings.", "#87 measurement reapplies byte-for-byte: `git diff --stat c31c7e2 origin/main` = src/profiles/requester.profile.ts only (#88, sys_comment grants). The version object, page, hooks, flows and pnpm-lock are identical to the tree the #87 browser pass measured (finding 16, screenshots 079-085).", "Flows' deliberate writes: contract-intake.flow.ts:375/395/410 write v1 `is_current: true` (the only version). executed-upload.flow.ts:268 (F16 backfill) writes v1 final_signed `is_current: true` (the only version). signature-record.flow.ts:308-314 (F7) writes final_signed `is_current: false` on purpose: 'the negotiation flag is what approved → signing reads … DESIGN.md section 03 keeps signing → approved open for a failed signing round — moving the flag onto the executed copy would make that rollback unre-enterable.'", "Caller rights for an un-marking write: clm_requester and clm_records hold create without edit on versions (src/profiles/requester.profile.ts:70, records.profile.ts:35). clm_legal holds edit (legal.profile.ts:56). An exclusivity rule must therefore un-mark siblings as a system write. In-repo precedent: `deviation_gate` (contract.hook.ts:819-846) runs afterUpdate with `runAs: 'system'` and writes the parent from outside its hook chain.", "Downstream reader that needs one answer: docs/backlog/12-esign-and-crm-handoff.md:13 has F8 post 'the current `clean` version' to the e-sign provider, i.e. one document." ], "tests": "No code was written, so no tests ran. Premise verification commands: `git rev-parse origin/main` → 46e65f0a66e4; `git diff --stat c31c7e2 origin/main` → '1 file changed, 29 insertions(+)' (src/profiles/requester.profile.ts); `grep -rn is_current src --include=*.ts` (12 hits: 10 in objects/flows/page, 2 in the en and zh-CN bundles). The browser re-run on 46e65f0 is NOT MEASURED, reason: the result is a decision, and the #87 browser measurement's inputs are byte-identical on this tree (see premise_evidence).", "gates": { "pnpm verify": "NOT RUN, reason: no diff (needs_decision, files_changed empty); a gate run here would measure origin/main, not a change" }, "line_budget": "n/a", "files_changed": [], "deviations": [ "Skipped `pnpm install` in worktree hotclm-issue-96: the stop came before any code, and nothing in the stop needs node_modules. The worktree was removed clean after this report.", "The branch claude/issue-96-current-version was pushed empty (equal to origin/main 46e65f0) as the claim's landing marker and is left on the remote for the seat to keep or delete." ], "mcp_calls": "0", "api_writes": "1: this report comment on objectstack-ai/hotclm#96, posted through scripts/pm/post-stamped.mjs (fleet relay). The branch push is git, not REST. Reads: GET issues/96, GET issues/96/comments, GET issues/comments/6092819369 (the #87 report).", "open_questions": [ { "question": "Q1: Is 'current' exclusive per contract? (section 03 lists `is_current boolean` and nothing more)", "options": [ "A: At most one current version per contract, enforced by a new src/objects/contract-version.hook.ts (afterInsert/afterUpdate, `runAs: 'system'`): when a row becomes current, every other current row on the same contract is un-marked. This binds every door: form, REST, flows, MCP. Business need: the field's own help text reads 'the version negotiation is currently on' / 「谈判当前所在的版本」, which is singular. F8 has to send ONE clean file to the e-sign provider. The #87 state (draft v1 + clean v3 both current) leaves 'which file gets signed' unanswered. Long-term: one invariant in one hook, and F8 reads 'the' current version without inventing a tie-break. AI-error: an agent adding a version through MCP/REST cannot leave two currents behind, and it does not need to know to un-tick the old one. Startup-focus: one hook file plus one line in hooks.ts, with no schema change and no seed change (no versions are seeded, src/data/index.ts:33). Cost: the un-marking must be a system write, because requester and records cannot edit versions (see precedent `deviation_gate`). Section 03 needs one sentence, a governed edit that is yours to make.", "B: Not exclusive. 'Current' is a per-version tag, and the guard keeps reading 'any current clean version'. No code, and the #87 state becomes legal by definition. Business need: the Versions timeline shows several 'Current' rows. Long-term: F8 (card 12) needs its own choosing rule, so the cost moves to card 12. AI-error: high, because nothing tells a writer the flag means anything. Startup-focus: cheapest today.", "C: Retire `is_current`. 'Current' becomes the highest-numbered version that is not final_signed, and the guard becomes 'the newest negotiation version is clean'. No flag to get wrong, and it matches docs/design/00 section 5.5's guard (「存在清稿版本」, no 'current'). Cost: a section 03 field removal, rewriting the intake/F16/F7 writes, the page column and both translation bundles. It also loses the ability to sign an earlier clean copy after a later counterparty redline is rejected (legal would re-upload it as a new version)." ], "recommendation": "A. Business need: one version is what the field already says it means, and what F8 needs to send. Long-term: a single hook makes every door obey the rule, where B pushes the ambiguity onto card 12 and C removes a capability. AI-error: A is the only option where a writer that knows nothing about the flag still leaves a coherent contract. Startup-focus: A is a ~40-line hook with no schema or seed change, against C's multi-file removal. Proposed section 03 wording for the clm_contract_version row: 「is_current boolean(每份合同至多一个当前版本:标记一个即取消其余,hook 强制,各入口一致)」." }, { "question": "Q2: Does a new version default to current? (section 03 is silent; today the field declares `defaultValue: false`, and the #87 pass hit exactly that)", "options": [ "A: Yes for every negotiation kind (draft, internal_redline, counterparty_redline, clean); `final_signed` never takes the flag from a negotiation version. Mechanism for the implementer: field `defaultValue: true`, so the form offers it ticked and a write that omits the key stores true. In the Q1 hook, a final_signed row keeps `is_current: true` only when it is the contract's only version (F16's backfill case), and is otherwise stored false. F7's explicit false, intake's true and F16's true all keep their meaning, and signing → approved stays re-enterable. Business need: legal saves a clean version as offered and Start Signing passes, which is the #87 path. Long-term: the newest negotiation round is what the contract is on. AI-error: low, because the writer does nothing special. Startup-focus: a one-line field change plus a few lines in the same hook.", "B: Yes for every kind (`defaultValue: true` only, no kind rule). Simplest. The cost: an executed copy added through the version form (records hold create on versions, 「执行副本」) would take the flag off the clean version under Q1-A. That is exactly what F7's comment protects against: after a failed signing round is rolled back to approved, signing could not be re-entered until someone re-ticks the clean version.", "C: No, keep `defaultValue: false` and legal ticks it. The #87 symptom stays: a clean version saved as offered does not satisfy the guard. It can only be softened by rewording the refusal ('mark the clean version current'). Cheapest, but the trap stays." ], "recommendation": "A. Business need: it removes the #87 dead end (save as offered → guard refuses) without asking legal to know about a flag. Long-term: it keeps F7's deliberate design, where the executed copy never displaces the negotiation version, and B breaks that. AI-error: a default that is right for every negotiation write means an agent that omits the key still gets the correct state. Startup-focus: it lives in the same hook file as Q1-A, plus one field line. If section 03 takes the wording, append to the Q1 sentence: 「新的谈判版本默认为当前;final_signed 不夺取当前标记」." } ], "out_of_scope_findings": [ "carrier: the maintainer, through this decision · noted, not filed. DESIGN.md section 03's approved → signing guard reads 「存在 `kind: clean` 的当前版本」, while the governed proposal docs/design/00-设计方案.md section 5.5 / its state table reads 「送签必须存在一份清稿」 / 「存在清稿版本」 (no 'current'). Q1-C would align the two; A and B leave docs/design/00 as the looser statement. A governed-document wording drift, outside classes a/b/c. Dedupe words: is_current, 当前版本, clean version guard" ], "platform_causes": "none: the form's 'Current off' default is the app's own `defaultValue: false`, and the Console honours it. Nothing filed, per the lane ruling." }
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actions决策:「当前版本」是否每份合同唯一?新版本是否默认为当前? · 2026-10-10T04:13Z
Governing text:
DESIGN.md§03(clm_contract_version行只写is_current boolean;状态机approved → signing守卫「存在kind: clean的当前版本」)· §06 F7(签署记录把执行副本写为is_current: false,以保留签署失败回退后可再次进入签署)·docs/backlog/12(F8 要把「当前 clean 版本」这一份文件发给电子签服务商)维护者速读
事情:浏览器测试(#87)里,法务在合同页按默认值存了一份清稿,「发起签署」仍被守卫拒绝——因为版本表单把「当前」默认关闭(这是应用自己的
defaultValue: false,不是平台问题);勾上之后,同一合同的草稿 v1 与清稿 v3 同时是「当前」,没有任何规则保证唯一。dev 读完 §03 后停下:§03 两件都没规定,这是你的产品语义(且 §03 属受管文本)。你要做的:回两个字母,如「1A 2A」。
问题 1:「当前」是否每份合同至多一个?
做法 代价 / 收益 1A(推荐) 至多一个:新增版本对象的 hook,标记一个即以系统身份取消其余,所有入口(表单 / REST / 流程 / MCP)一致 与字段帮助文字「谈判当前所在的版本」一致;F8 只发一份文件无需另立规则;约 40 行 hook,无 schema / 种子改动;§03 需加一句(受管,由你写) 1B 不唯一:「当前」只是每个版本的标签 今天零成本;但「到底签哪一份」推给卡 12 解决 1C 取消 is_current:当前 = 编号最大的非执行副本版本没有可写错的标记;但要删 §03 字段、改三个流程与页面与两套翻译,并失去「回签较早清稿」的能力 问题 2:新版本是否默认为当前?
做法 代价 / 收益 2A(推荐) 谈判类版本(草稿 / 内部修订 / 对方修订 / 清稿)默认当前;执行副本( final_signed)不夺取当前标记(唯一版本时除外,即补录场景)法务按默认保存即可发起签署(#87 的死路消失);保留 F7 的刻意设计 2B 所有版本一律默认当前 最简单;但档案岗新增执行副本会把标记从清稿上拿走,签署失败回退后无法再次进入签署——正是 F7 注释所防的 2C 保持默认关闭,由法务手动勾选 零改动;#87 的陷阱继续存在 若选 1A + 2A,dev 建议的 §03 措辞:「is_current boolean(每份合同至多一个当前版本:标记一个即取消其余,hook 强制,各入口一致;新的谈判版本默认为当前;final_signed 不夺取当前标记)」。
四棱分析
- 实际业务需求:真实且已测——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 中按默认保存的清稿过不了签署守卫;F8(卡 12)必须知道发哪一份。 - 项目长远合理性:1A 把不变量放在一个 hook 里、所有入口遵守;1B 把歧义推给后续卡;1C 删能力。
- 防 AI 写错:1A + 2A 下,一个对这个标记一无所知的写入者(agent 经 MCP / REST 添加版本)也会留下一致的合同;1B / 2C 依赖写入者「知道要去勾」。
- 创业阶段不扩散:1A + 2A 是一个 hook 文件加一行字段默认值,无 schema、无种子改动。
另记(不需裁):
docs/design/00的守卫写的是「存在清稿版本」(无「当前」),与 §03 措辞有出入——属受管文档措辞漂移,随本决策一并由你定稿即可。
Generated by Claude Code
- 实际业务需求:真实且已测——Full browser test on 17.7.0
Filing class: ① product defect (user-visible) — reach: browser, measured on 17.7.0. Reader: the
repo:hotclmseat, dispatch frompm:queue.Source: the #87 full browser pass on 17.7.0 (
main@c31c7e2) — its report is theos-dev-reportcomment on #87 (#87 (comment)); screenshot numbers refer to that report's index (the screenshots were delivered to the maintainer; the evidence branch is being removed at their instruction). Finding 16; screenshots079–085.Symptom
is_currentto off, so a clean version saved as offered (079,082) does not satisfy the signing guard ("A current clean version is required before signing.").is_current: true: nothing keeps "current" exclusive per contract.Expected (verify against
DESIGN.md§03clm_contract_version)A new version is current by default when it is the newest, and marking one current un-marks the others (in a hook, so every door obeys it). If §03 does not pin which, stop with
needs_decision.Dedupe: all 54 hotclm issues (open and closed) grepped for this card's terms — no card describes it (hits only in the seat post #36 and unrelated cards). (terms:
is_current)Filed by the
repo:hotclmPM seat from the #87 browser pass.