You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The contract page's new-version form defaults "Current" off, and two versions of one contract can both be current #96
Filing class: ① product defect (user-visible) — reach: browser, measured on 17.7.0. Reader: the repo:hotclm seat, dispatch from pm:queue.
Source: the #87 full browser pass on 17.7.0 (main @ c31c7e2) — its report is the os-dev-report comment 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; screenshots 079–085.
Symptom
The version form on the contract page defaults is_current to off, so a clean version saved as offered (079, 082) does not satisfy the signing guard ("A current clean version is required before signing.").
After ticking it, versions 1 (draft) and 3 (clean) were both is_current: true: nothing keeps "current" exclusive per contract.
Expected (verify against DESIGN.md §03 clm_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:hotclm PM seat from the #87 browser pass.
Claim: PM loop round 8
Session: session_01HihZ11bQSqjCgjzHbpv4M1
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-96-current-version
Worktree: hotclm-issue-96
Domain: repo:hotclm (single-lane sister repo — no domain:*)
Seat: repo:hotclm#1
File surface: src/objects/contract-version.object.ts · a new src/objects/contract-version.hook.ts (and its registration in src/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) — ⛔ not src/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 holds contract.hook.ts · contract-intake.flow.ts · contract-lifecycle.actions.ts; #93 holds contract-approval.flow.ts · README.md · scripts/demo.mjs; #97 holds the renewal / expiry flows — all outside this surface
Read at: 2026-10-10T04:04Z
{
"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."
}
Activity
objectstack-fleet commentedon Oct 10, 2026
Claim: PM loop round 8
Session:
session_01HihZ11bQSqjCgjzHbpv4M1Account:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-96-current-versionWorktree:
hotclm-issue-96Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File 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 reportContainer & 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 pageThread-read: none
Serial constraints cleared: PR #91 holds
contract.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 surfaceRead at: 2026-10-10T04:04Z
Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
os-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 commentedon Oct 10, 2026
决策:「当前版本」是否每份合同唯一?新版本是否默认为当前? · 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:「当前」是否每份合同至多一个?
is_current:当前 = 编号最大的非执行副本版本问题 2:新版本是否默认为当前?
final_signed)不夺取当前标记(唯一版本时除外,即补录场景)若选 1A + 2A,dev 建议的 §03 措辞:「is_current boolean(每份合同至多一个当前版本:标记一个即取消其余,hook 强制,各入口一致;新的谈判版本默认为当前;final_signed 不夺取当前标记)」。
四棱分析
main: drive the whole contract lifecycle as every audience, in en and zh-CN, and report what a real user hits #87 中按默认保存的清稿过不了签署守卫;F8(卡 12)必须知道发哪一份。另记(不需裁):
docs/design/00的守卫写的是「存在清稿版本」(无「当前」),与 §03 措辞有出入——属受管文档措辞漂移,随本决策一并由你定稿即可。Generated by Claude Code