Skip to content

The contract page's new-version form defaults "Current" off, and two versions of one contract can both be current - #112

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-96-one-current-version
Oct 25, 2026
Merged

zhuangjianguo merged 1 commit into
mainfrom
claude/issue-96-one-current-version

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #96 — ruling step 2 (code): at most one current version per contract, new versions default current
Clause-②: no
Metadata-protocol: none

Ruling-ref: issue comment 6395827965 (letters 1A 2A), carrying out the plan in os-dev-report 6093639240. Ruling step 1 (the DESIGN.md §03 line and the docs/design/00 §5.5 guard wording) touches governed files, so it is not in this PR. The maintainer edits those files separately, and #96 remains open for that half.

What changed

  • src/objects/contract-version.hook.ts (new): a contract_version_current hook on clm_contract_version. It runs on afterInsert and afterUpdate with runAs: 'system', re-reads the committed row, and does nothing unless that row is current. Then:
    • Non-final_signed row: every other current version of the same contract is un-marked.
    • final_signed row: it keeps is_current: true only while it is the contract's only version, which is the F16 backfill case. If the contract has other versions, the row is stored false and the other versions are left alone. This means filing an executed copy never takes the flag off the clean version, so signing → approved can still be re-entered.
    • The un-marking is a system write because requesters and records can create versions but cannot edit them (§04). The precedent is deviation_gate.
    • The hook does not loop: its own writes set is_current: false, and it returns at the first check for those.
  • src/objects/hooks.ts: registers the new hook.
  • src/objects/contract-version.object.ts: is_current now has defaultValue: true (it was false), and the comments explain the rule. There is no change to schema shape, seed or translations. The field's help text is unchanged.
  • contract.hook.ts is not touched.

Evidence (HTTP and browser)

Two pnpm demo boots, each on a clean .objectstack/data and the same 18.1.0 lockfile: this branch at 8b3c6f0, and main at 7b736a0 for the before picture. HTTP checks used /api/v1/data. Browser checks used Playwright with Chromium at /opt/pw-browsers/chromium. The rows below were read back with GET /api/v1/data/clm_contract_version?filter={contract}.

Before (main 7b736a0):

  • POST v1 clean with no is_current key → stored is_current=false.
  • PATCH status: signing → 422 INVALID_STATE "A current clean version is required before signing."
  • Adding v2 draft and v3 clean, both with is_current: true, leaves both current: v1 false, v2 true, v3 true.
  • In the browser, the Versions tab's New form opens with the Current switch aria-checked=false.

After (this branch):

  • (a) Form default. Contract page → Versions tab → New: the "Create Contract Version" dialog opens with the Current switch aria-checked=true (screenshot taken). This was the same for the dev admin and for the requester.
  • (b) Second current version un-marks the first. On an approved contract:
    • POST v1 clean with no key → v1 true.
    • v2 internal_redline → v1 false, v2 true.
    • v3 clean → v1 false, v2 false, v3 true.
    • In the browser, saving v3 clean through the form on NDA-2026-0012 (POST /api/v1/data/clm_contract_version → 201) gives v1 clean false, v2 final_signed false, v3 clean true. The Versions list shows the check only on v3.
  • (c) Non-admin requester. The user was created by /api/v1/auth/admin/create-user with permissionSet: clm_requester and holds create but not edit on versions.
    • The requester's own draft contract: POST v1 draft → v1 true. POST v2 internal_redline → v1 false, v2 true. Read back as admin, the result is the same.
    • The requester's own PATCH of v1 {is_current: true} → 403 PERMISSION_DENIED. This shows the un-marking only works as a system write.
    • In the browser, the requester saved v3 clean through the form (201) → v1 false, v2 false, v3 true.
  • (d) final_signed:
    • Contract with a clean v1: POST v2 final_signed with no key → stored v2 false, and v1 clean stays true. A later PATCH of v2 {is_current: true} → 200, and it reads back false with v1 still true.
    • Contract with no versions: POST v1 final_signed with no key → v1 true, because it is the only version. A later v2 clean → v1 false, v2 true.
  • (e) Signing guard with the new default. On the contract from (b), PATCH status approved → signing → 200. In the browser, Start Signing on NDA-2026-0012 sends POST /api/v1/actions/clm_contract/start_signing → 200 {"success":true}, and the status reads back signing.
  • (f) F7 and the rollback:
    • On the (b) contract in signing, inserting a completed wet-ink clm_signature runs F7, which files v5 final_signed with its explicit is_current: false. Read back: v3 clean true, v4 counterparty_redline false (explicit false on insert, kept), v5 final_signed false.
    • Then signing → approved → 200, and approved → signing again → 200.

Boot log. The same warning set appears on both boots. Timestamps, random ids and ports were normalised; seed-row ids and record indexes were masked. The boot-only section runs up to the seed summary. Main has 249 unique lines and the branch 248. diff shows exactly one extra line, on main: Port 3097 is in use — serving on 3098. A stray process of mine caused it, not the code. The branch boot adds no warning. The compiled artifact shows contract_version_current was lowered to a body, not bundled.

Browser console on the contract page is the same on both trees: one 404, one 401, and five [object-ui] conditional predicate failed to evaluate warnings for header actions.

Gates (on 8b3c6f0, each exit code captured before any pipe)

pnpm validate        exit 0   ✓ Validation passed (850ms)   (6 author-time warnings, all approval-approvers-may-resolve-empty + 1 hierarchy-security capability notice, same as main)
pnpm lint            exit 0   6 suggestion(s) (812ms)
pnpm typecheck       exit 0   tsc --noEmit
pnpm lint:i18n-gate  exit 0   ✓ i18n gate · COVERAGE : 0 missing keys across 2 locale(s)

Acceptance notes

  • Proposed §03 line (ruling step 1, maintainer's governed edit, not made here). In the clm_contract_version row, replace is_current boolean with:
    is_current boolean(每份合同至多一个当前版本:标记一个即取消其余,hook 强制,各入口一致;新的谈判版本默认为当前;final_signed 不夺取当前标记).
    The same edit aligns the docs/design/00 §5.5 guard wording.
  • F7 code comment is now stale. src/flows/signature-record.flow.ts's comment at create_final_version says is_current is "deliberately left at its false default". The flow writes is_current: false explicitly, so its behaviour is unchanged (measured in (f)). Only the words "its false default" no longer describe the field. The file is outside this card's declared surface, so it is not edited here. Carrier: none.
  • F16 backfill through its own flow is NOT MEASURED. Reason: POST /api/v1/actions/clm_contract/executed_upload and /api/v1/automation/executed_upload/trigger both answered 403 "This caller may not start this flow" for the dev admin (the action route after clm_admin was granted to it). The (d) "only version keeps the flag" rule was measured with the same write F16 makes (a final_signed v1 inserted on a contract with no versions). I did not investigate the 403 further. It is outside this diff, and I have not reproduced it as a user would through the Console button.
  • Requester console 403s. On the requester's contract page, the browser console showed 403 on GET /api/v1/data/sys_approval_request (the Approvals tab). This diff touches no page or permission. I did not compare it on main. Noted, not filed.
  • Branch base. The branch is behind origin/main by Turn on the platform's scheduled work for pnpm demo; README says how a deployment does the same #111 only (README.md and scripts/demo.mjs), with no overlap and no conflict.

🤖 Generated with Claude Code

https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn

…rrent

A version saved as offered on the contract page was not current
(`defaultValue: false`), so a clean copy did not satisfy the
approved -> signing guard, and nothing kept the flag exclusive: two
versions of one contract could both be current.

Ruled 1A 2A on #96 (comment 6395827965). `is_current` now defaults to
true, and a new `contract_version_current` hook (afterInsert /
afterUpdate, runAs system) un-marks every other current version of the
contract when a row is current after the write. A `final_signed` row
keeps the flag only while it is the contract's only version (the F16
backfill) and is otherwise stored false, so the clean version stays
current and signing -> approved remains re-enterable. The writes are
system writes because requesters and records hold create, not edit,
on versions (same precedent as `deviation_gate`).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · head 8b3c6f0 · 2026-10-25T12:08Z

check reading
shape draft → main, line 1 Part of #96 — ruling step 2 (code) …, no closing keyword; assignee zhuangjianguo
CI on the head both Validate check-runs success on 8b3c6f0 = PR head.sha
scope 3 files (+96/−4): new contract-version.hook.ts, its registration in hooks.ts, is_current defaultValue: true; contract.hook.ts untouched; nothing governed
the ruling 6395827965 step 2: afterInsert/afterUpdate, runAs: 'system', a current row un-marks its siblings, final_signed keeps the flag only as the contract's sole version, default true — all present; the hook judges the committed row (re-read), and its own false writes exit at the first check, so no recursion
measured (dev, 18.1.0, real boots) main: default false, signing guard 422, two current rows; branch: form switch on, v1→v2→v3 leaves one current (admin and requester; requester's direct PATCH 403, so the un-marking is the system write), final_signed with siblings stored false, sole final_signed true, approved → signing 200, F7's explicit false and the signing → approved → signing round trip intact; no new boot warning
not measured F16 through its own flow (403 "This caller may not start this flow" — the known executed_upload refusal tracked on #43 / objectstack#25643); its only-version write was measured directly
stays on #96 ruling step 1 — the DESIGN.md §03 line and docs/design/00 §5.5 wording (governed; proposed text in the PR's Acceptance notes)
seat follow-up signature-record.flow.ts comment says is_current is "left at its false default"; behaviour unchanged (F7 writes false explicitly), wording now stale — joins the #95 remainder

Landing: under the maintainer's merge authorization (project thread: 「你可以自己审核的就自己合并」).


Generated by Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants