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
Ruled: 6395827965 · letter 1A 2A · 2026-10-25T09:14Z
Unblocked: the 17.7.0 → 18.1.0 upgrade landed as PR #110 (7b736a0).
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."
}
Out of the decision box — Ruling-ref: comment 6395827965 (batch #2 · maintainer 「同意」).
State: needs-user-decision → pm:blocked, Blocked-by: the 18.1 upgrade PR, as the ruling prescribes.
Then one card: src/objects/contract-version.hook.ts (afterInsert/afterUpdate, runAs: 'system', one current version per contract; final_signed keeps the flag only when it is the only version) registered in hooks.ts, and is_currentdefaultValue: true. The §03 / docs/design/00 §5.5 wording goes with the design-document PR (Refs #96).
Claim: PM loop round 14
Session: session_017nzWFRUo2U7evMkPdVniAn
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-96-one-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.hook.ts (new) · src/objects/hooks.ts (register it) · src/objects/contract-version.object.ts (is_current default) — 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 clm_contract_version (no writer keeps is_current exclusive; the field defaults off) | none known | every requester who saves a clean version and then cannot sign
Ruling-ref: 6395827965 (batch #2 item 4 · letters 1A 2A)
Premise: re-measured by the dev on origin/main7b736a0 (18.1.0)
Serial constraints cleared: PR #110 merged 7b736a0. ⛔ contract.hook.ts not touched. Scope of this branch: ruling step 2 (code), Part of #96. Step 1 (the DESIGN.md §03 line and docs/design/00 §5.5 wording) is governed surface — it goes to the maintainer as a separate design-doc PR with Refs #96.
{
"issue": 96,
"status": "done",
"branch": "claude/issue-96-one-current-version",
"pr": "https://github.com/objectstack-ai/hotclm/pull/112",
"session": "session_017nzWFRUo2U7evMkPdVniAn",
"premise_still_valid": true,
"summary": "Ruling 6395827965 step 2 (code) is done, opened as draft PR #112 (`Part of #96`, with no closing keyword; step 1 is the maintainer's governed edit). There is a new `src/objects/contract-version.hook.ts` (`contract_version_current`, afterInsert/afterUpdate, runAs system), registered in `hooks.ts`. It re-reads the committed row and does nothing unless that row is current. A non-final_signed current row un-marks every other current version of the same contract. A final_signed row keeps the flag only while it is the contract's only version and is otherwise stored false, leaving its siblings alone. `is_current` now has `defaultValue: true`. The premise was re-measured on main 7b736a0: a version POSTed without the key was stored false, the signing guard refused with 422, and two versions of one contract could both be current. `contract.hook.ts` was not touched. Translations were not touched because no new string was needed.",
"tests": "No test suite in this repo; proof is measured on real `pnpm demo` boots with a clean .objectstack/data each. Branch 8b3c6f0 on :3096; main 7b736a0 on :3098 as the before picture. Before (main): POST v1 clean with no key stored is_current=false; PATCH approved->signing gave 422 INVALID_STATE 'A current clean version is required before signing.'; v2 draft and v3 clean both written true were both current; the browser form's Current switch was aria-checked=false. After (branch): (a) Chromium (/opt/pw-browsers/chromium): Versions tab -> New opens 'Create Contract Version' with the Current switch aria-checked=true, for admin and for the requester. (b) Admin on an approved contract: v1 clean -> [v1 true]; v2 internal_redline -> [v1 false, v2 true]; v3 clean -> [v1 false, v2 false, v3 true]. A browser save of v3 clean on NDA-2026-0012 (POST clm_contract_version 201) gave [v1 clean false, v2 final_signed false, v3 clean true]. (c) A requester was created via /api/v1/auth/admin/create-user with permissionSet clm_requester. On their own draft contract: v1 draft -> [v1 true]; v2 internal_redline -> [v1 false, v2 true], with the same rows read back as admin. The requester's direct PATCH of v1 is_current:true gave 403 PERMISSION_DENIED, so the un-marking is a system write. A browser save of v3 clean by the requester (201) gave [v1 false, v2 false, v3 true]. (d) final_signed with no key next to a clean v1 was stored false and v1 stayed true; a PATCH of that final_signed to is_current:true answered 200 and read back false. A final_signed as the only version -> true; a later v2 clean -> [v1 final_signed false, v2 clean true]. (e) PATCH approved->signing gave 200 with the default-current clean version. In the browser, Start Signing sent POST /api/v1/actions/clm_contract/start_signing -> 200 and the status read back signing. (f) A completed wet-ink clm_signature inserted on the signing contract ran F7, which filed v5 final_signed with an explicit false. Read back: v3 clean true, v4 (explicit false on insert) false, v5 false. Then signing->approved 200, and approved->signing again 200. F16 through its own flow: NOT MEASURED, reason: both the action route and the automation trigger answered 403 'This caller may not start this flow' for the dev admin. The only-version rule was measured with the same write F16 makes. Boot log: boot-only WARN/ERROR lines, normalised (timestamps, ids, ports, durations), were diffed main vs branch. There are 249 vs 248 unique lines, and the only difference is main's 'Port 3097 is in use — serving on 3098', caused by a stray process of mine. The branch adds no warning. The artifact shows contract_version_current lowered to a body. The browser console on the contract page shows the same set on both trees (one 404, one 401, five object-ui predicate warnings). No ablation: no tested behaviour pinned by a test file changed.",
"gates": "Local, on 8b3c6f0, each exit captured to its own file before any pipe: pnpm validate exit 0 ('✓ Validation passed (850ms)', 6 author-time warnings, all pre-existing approval-approvers-may-resolve-empty plus the hierarchy-security capability notice); pnpm lint exit 0 ('6 suggestion(s)'); pnpm typecheck exit 0 (tsc --noEmit); pnpm lint:i18n-gate exit 0 ('✓ i18n gate · 0 missing keys across 2 locale(s)'). dispatch-gates.mjs is objectstack-only, so the families were hand-derived from hotclm .github/workflows/ci.yml, which runs exactly these four in the single `Validate` job. PR check-runs on head 8b3c6f0 at report time: Validate completed success (run 40190224026, push), and Validate in_progress (run 40191618471, pull_request).",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm; hotclm has no dogfood package",
"mcp_calls": "0",
"api_writes": "2 strokes. (1) pr_create via the fleet-write relay (request fw-20261025T120522Z-66ce63, run 40191599839 success): POST /repos/objectstack-ai/hotclm/pulls (draft) and POST /repos/objectstack-ai/hotclm/issues/112/assignees [zhuangjianguo]. Body read back 8133/8133 bytes identical; assignee read back zhuangjianguo. (2) This os-dev-report comment on #96 via scripts/pm/post-stamped.mjs. The branch push is git, not REST. Local-only writes on the demo servers: test data, and a sys_user_permission_set clm_admin grant to the dev admin on the throwaway :3096 database.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — src/flows/signature-record.flow.ts comment at create_final_version says is_current is 'deliberately left at its `false` default'; the field default is now true. The flow writes is_current:false explicitly, so behaviour is unchanged (measured in f). Only the comment's words are stale, and the file is outside this card's declared surface. Listed in PR Acceptance notes.",
"carrier: none · noted, not filed — F16 `executed_upload` answered 403 'This caller may not start this flow' on POST /api/v1/actions/clm_contract/executed_upload (dev admin with clm_admin granted) and on /api/v1/automation/executed_upload/trigger. It was not reproduced through the Console toolbar button and not compared on main, so no class a reach is established. Dedupe words: executed_upload, may not start this flow, backfill 403.",
"carrier: none · noted, not filed — requester contract page: the browser console shows 403 on GET /api/v1/data/sys_approval_request (Approvals tab). This diff touches no page or permission, and it was not compared on main. Dedupe words: sys_approval_request 403 requester."
],
"deviations": [
"Proposed DESIGN.md §03 line for ruling step 1 (not edited, governed): in the clm_contract_version row, replace `is_current boolean` with `is_current boolean(每份合同至多一个当前版本:标记一个即取消其余,hook 强制,各入口一致;新的谈判版本默认为当前;final_signed 不夺取当前标记)`. It is in the PR's Acceptance notes.",
"origin/main moved to a7239e8 (#111: README.md, scripts/demo.mjs) after the branch point 7b736a0. There is no overlap and no conflict, so it was not merged. The before boot ran at 7b736a0 so that it compares like with like.",
"The commit carries the session's Co-Authored-By and Claude-Session trailers, as the dispatch ordered (this overrides os-dev.md's model-free trailer rule for this run).",
"Worktree /home/claude/hotclm-issue-96 had node_modules removed and was then removed with `git worktree remove` (no --force needed). The temporary main worktree in the scratchpad was also removed. All dev servers I started were stopped by process group."
]
}
Release: ruling step 2 (code) is delivered — one current version per contract, new versions default current, enforced by contract-version.hook.ts on every door. What stays on this card: ruling step 1, the DESIGN.md §03 is_current line and the docs/design/00 §5.5 guard wording (governed). The seat drafts it in one design-doc PR together with the other ruled governed lines (#100 §04, #38 §03 field lines); the maintainer merges that PR, and it closes this card. State: pm:dispatched → pm:queue, assignee cleared.
Maintainer-action: merge PR #120 — the governed design-doc PR carrying this card's remaining ruled line (code already landed: PR #112). It closes this card.
State: pm:queue → pm:awaiting-maintainer (no dev work left).
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
zhuangjianguo commentedon Oct 25, 2026
Ruling: batch #2 item 4 · letter 1A 2A · maintainer 「同意」 2026-10-25T09:14:04Z
裁定:1A 2A —— 每份合同至多一个当前版本(hook 强制);新谈判版本默认为当前,执行副本不夺取标记
维护者在第 2 批决策箱回了「同意」(= 推荐项 1A + 2A,与席位评论 6093651767 的推荐一致)。裁决文档:https://claude.ai/artifact/X4sZxhLrEYshLjkgYp4svr 。
鲜度门:正文 2026-10-10T03:30Z;评论三条,最后一条 2026-10-10T04:13Z。
maine4997a8 复测:contract-version.object.ts:83仍是defaultValue: false;hooks.ts只注册 contract 与 mirror 两个 hook,没有任何写入者取消兄弟版本的标记;F7 刻意写is_current: false。录裁前重读,无变化。裁后执行(给 lane 席位)
clm_contract_version行加:「is_current boolean(每份合同至多一个当前版本:标记一个即取消其余,hook 强制,各入口一致;新的谈判版本默认为当前;final_signed 不夺取当前标记)」。同一次受管编辑顺手对齐docs/design/00§5.5 的守卫措辞(今天写的是「存在清稿版本」,没有「当前」)。随设计文档 PR,Refs #96。src/objects/contract-version.hook.ts并在hooks.ts注册 —— afterInsert / afterUpdate,runAs: 'system'(发起人与档案岗对版本只有 create 没有 edit,取消兄弟标记必须是系统写,先例deviation_gate):一行成为当前即取消同合同其余行;final_signed行仅在它是该合同唯一版本时保留is_current: true(F16 补录),否则存 false。字段defaultValue: true。intake 的 true、F16 的 true、F7 的显式 false 含义不变,signing → approved回退后仍可再次进入签署。contract.hook.ts(若仍被其他 PR 持有)。⛔ 本席不动标签;lane 席位派发时转标签并带
Ruling-ref:。Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Out of the decision box — Ruling-ref: comment 6395827965 (batch #2 · maintainer 「同意」).
needs-user-decision→pm:blocked,Blocked-by:the 18.1 upgrade PR, as the ruling prescribes.src/objects/contract-version.hook.ts(afterInsert/afterUpdate,runAs: 'system', one current version per contract;final_signedkeeps the flag only when it is the only version) registered inhooks.ts, andis_currentdefaultValue: true. The §03 /docs/design/00§5.5 wording goes with the design-document PR (Refs #96).Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Unblocked: PR #110 (the 18.1 upgrade) merged as
7b736a0. Statepm:blocked→pm:queue; dispatch follows its ruling.Generated by Claude Code
5 remaining items
objectstack-fleet commentedon Oct 25, 2026
Claim: PM loop round 14
Session:
session_017nzWFRUo2U7evMkPdVniAnAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-96-one-current-versionWorktree:
hotclm-issue-96Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/objects/contract-version.hook.ts(new) ·src/objects/hooks.ts(register it) ·src/objects/contract-version.object.ts(is_currentdefault) — 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 clm_contract_version (no writer keeps is_current exclusive; the field defaults off) | none known | every requester who saves a clean version and then cannot signRuling-ref: 6395827965 (batch #2 item 4 · letters 1A 2A)
Premise: re-measured by the dev on
origin/main7b736a0(18.1.0)Serial constraints cleared: PR #110 merged
7b736a0. ⛔contract.hook.tsnot touched. Scope of this branch: ruling step 2 (code),Part of #96. Step 1 (theDESIGN.md§03 line anddocs/design/00§5.5 wording) is governed surface — it goes to the maintainer as a separate design-doc PR withRefs #96.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
os-dev-report
{ "issue": 96, "status": "done", "branch": "claude/issue-96-one-current-version", "pr": "https://github.com/objectstack-ai/hotclm/pull/112", "session": "session_017nzWFRUo2U7evMkPdVniAn", "premise_still_valid": true, "summary": "Ruling 6395827965 step 2 (code) is done, opened as draft PR #112 (`Part of #96`, with no closing keyword; step 1 is the maintainer's governed edit). There is a new `src/objects/contract-version.hook.ts` (`contract_version_current`, afterInsert/afterUpdate, runAs system), registered in `hooks.ts`. It re-reads the committed row and does nothing unless that row is current. A non-final_signed current row un-marks every other current version of the same contract. A final_signed row keeps the flag only while it is the contract's only version and is otherwise stored false, leaving its siblings alone. `is_current` now has `defaultValue: true`. The premise was re-measured on main 7b736a0: a version POSTed without the key was stored false, the signing guard refused with 422, and two versions of one contract could both be current. `contract.hook.ts` was not touched. Translations were not touched because no new string was needed.", "tests": "No test suite in this repo; proof is measured on real `pnpm demo` boots with a clean .objectstack/data each. Branch 8b3c6f0 on :3096; main 7b736a0 on :3098 as the before picture. Before (main): POST v1 clean with no key stored is_current=false; PATCH approved->signing gave 422 INVALID_STATE 'A current clean version is required before signing.'; v2 draft and v3 clean both written true were both current; the browser form's Current switch was aria-checked=false. After (branch): (a) Chromium (/opt/pw-browsers/chromium): Versions tab -> New opens 'Create Contract Version' with the Current switch aria-checked=true, for admin and for the requester. (b) Admin on an approved contract: v1 clean -> [v1 true]; v2 internal_redline -> [v1 false, v2 true]; v3 clean -> [v1 false, v2 false, v3 true]. A browser save of v3 clean on NDA-2026-0012 (POST clm_contract_version 201) gave [v1 clean false, v2 final_signed false, v3 clean true]. (c) A requester was created via /api/v1/auth/admin/create-user with permissionSet clm_requester. On their own draft contract: v1 draft -> [v1 true]; v2 internal_redline -> [v1 false, v2 true], with the same rows read back as admin. The requester's direct PATCH of v1 is_current:true gave 403 PERMISSION_DENIED, so the un-marking is a system write. A browser save of v3 clean by the requester (201) gave [v1 false, v2 false, v3 true]. (d) final_signed with no key next to a clean v1 was stored false and v1 stayed true; a PATCH of that final_signed to is_current:true answered 200 and read back false. A final_signed as the only version -> true; a later v2 clean -> [v1 final_signed false, v2 clean true]. (e) PATCH approved->signing gave 200 with the default-current clean version. In the browser, Start Signing sent POST /api/v1/actions/clm_contract/start_signing -> 200 and the status read back signing. (f) A completed wet-ink clm_signature inserted on the signing contract ran F7, which filed v5 final_signed with an explicit false. Read back: v3 clean true, v4 (explicit false on insert) false, v5 false. Then signing->approved 200, and approved->signing again 200. F16 through its own flow: NOT MEASURED, reason: both the action route and the automation trigger answered 403 'This caller may not start this flow' for the dev admin. The only-version rule was measured with the same write F16 makes. Boot log: boot-only WARN/ERROR lines, normalised (timestamps, ids, ports, durations), were diffed main vs branch. There are 249 vs 248 unique lines, and the only difference is main's 'Port 3097 is in use — serving on 3098', caused by a stray process of mine. The branch adds no warning. The artifact shows contract_version_current lowered to a body. The browser console on the contract page shows the same set on both trees (one 404, one 401, five object-ui predicate warnings). No ablation: no tested behaviour pinned by a test file changed.", "gates": "Local, on 8b3c6f0, each exit captured to its own file before any pipe: pnpm validate exit 0 ('✓ Validation passed (850ms)', 6 author-time warnings, all pre-existing approval-approvers-may-resolve-empty plus the hierarchy-security capability notice); pnpm lint exit 0 ('6 suggestion(s)'); pnpm typecheck exit 0 (tsc --noEmit); pnpm lint:i18n-gate exit 0 ('✓ i18n gate · 0 missing keys across 2 locale(s)'). dispatch-gates.mjs is objectstack-only, so the families were hand-derived from hotclm .github/workflows/ci.yml, which runs exactly these four in the single `Validate` job. PR check-runs on head 8b3c6f0 at report time: Validate completed success (run 40190224026, push), and Validate in_progress (run 40191618471, pull_request).", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm; hotclm has no dogfood package", "mcp_calls": "0", "api_writes": "2 strokes. (1) pr_create via the fleet-write relay (request fw-20261025T120522Z-66ce63, run 40191599839 success): POST /repos/objectstack-ai/hotclm/pulls (draft) and POST /repos/objectstack-ai/hotclm/issues/112/assignees [zhuangjianguo]. Body read back 8133/8133 bytes identical; assignee read back zhuangjianguo. (2) This os-dev-report comment on #96 via scripts/pm/post-stamped.mjs. The branch push is git, not REST. Local-only writes on the demo servers: test data, and a sys_user_permission_set clm_admin grant to the dev admin on the throwaway :3096 database.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — src/flows/signature-record.flow.ts comment at create_final_version says is_current is 'deliberately left at its `false` default'; the field default is now true. The flow writes is_current:false explicitly, so behaviour is unchanged (measured in f). Only the comment's words are stale, and the file is outside this card's declared surface. Listed in PR Acceptance notes.", "carrier: none · noted, not filed — F16 `executed_upload` answered 403 'This caller may not start this flow' on POST /api/v1/actions/clm_contract/executed_upload (dev admin with clm_admin granted) and on /api/v1/automation/executed_upload/trigger. It was not reproduced through the Console toolbar button and not compared on main, so no class a reach is established. Dedupe words: executed_upload, may not start this flow, backfill 403.", "carrier: none · noted, not filed — requester contract page: the browser console shows 403 on GET /api/v1/data/sys_approval_request (Approvals tab). This diff touches no page or permission, and it was not compared on main. Dedupe words: sys_approval_request 403 requester." ], "deviations": [ "Proposed DESIGN.md §03 line for ruling step 1 (not edited, governed): in the clm_contract_version row, replace `is_current boolean` with `is_current boolean(每份合同至多一个当前版本:标记一个即取消其余,hook 强制,各入口一致;新的谈判版本默认为当前;final_signed 不夺取当前标记)`. It is in the PR's Acceptance notes.", "origin/main moved to a7239e8 (#111: README.md, scripts/demo.mjs) after the branch point 7b736a0. There is no overlap and no conflict, so it was not merged. The before boot ran at 7b736a0 so that it compares like with like.", "The commit carries the session's Co-Authored-By and Claude-Session trailers, as the dispatch ordered (this overrides os-dev.md's model-free trailer rule for this run).", "Worktree /home/claude/hotclm-issue-96 had node_modules removed and was then removed with `git worktree remove` (no --force needed). The temporary main worktree in the scratchpad was also removed. All dev servers I started were stopped by process group." ] }Generated by Claude Code
At most one current version per contract; new versions default current (
objectstack-fleet commentedon Oct 25, 2026
Landed — PR #112 · 2026-10-25T12:09Z
MERGED, squashef93d9bonmain, merged at its ACCEPT head8b3c6f0Release: ruling step 2 (code) is delivered — one current version per contract, new versions default current, enforced by
contract-version.hook.tson every door. What stays on this card: ruling step 1, theDESIGN.md§03is_currentline and thedocs/design/00§5.5 guard wording (governed). The seat drafts it in one design-doc PR together with the other ruled governed lines (#100 §04, #38 §03 field lines); the maintainer merges that PR, and it closes this card. State:pm:dispatched→pm:queue, assignee cleared.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Maintainer-action: merge PR #120 — the governed design-doc PR carrying this card's remaining ruled line (code already landed: PR #112). It closes this card.
State:
pm:queue→pm:awaiting-maintainer(no dev work left).Generated by Claude Code
Sync the governed design text with the rulings landed on 2026-10-25
Sync the governed design text with the rulings landed on 2026-10-25
Sync the governed design text with the rulings landed on 2026-10-25
Merge pull request #120 from objectstack-ai/claude/design-sync-round14