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
A signature round can be created already declined or voided — the round's state machine judges updates only #147
Filing class: ① product defect (data integrity) — reach: HTTP, measured on 18.1.0 by the #138 dev (os-dev-report on #138, PR #145). Reader: the repo:hotclm seat, dispatch from pm:queue.
Symptom
POST /api/v1/data/clm_signature {method: 'esign', status: 'declined', …} as Legal Counsel 1 answers 201: a signature round can be created already in any status — declined, completed, voided — without passing through the states before it. Same on main before PR #145.
Why
signature_state_machine in src/objects/contract.hook.ts applies its transition table on beforeUpdate only. DESIGN.md §03 says the child objects' state machines are enforced in the write layer, on every door; an insert is a door.
Expected
An insert may only create a round in the machine's entry state(s) — measure which entries §03 and the app's own flows need (the manual wet-ink form legitimately creates a round as completed with an executed copy, PR #145; the e-signature connector, #83, will create rounds as sent or similar). Anything else is refused with a message in both bundles. Existing rows are not touched.
Claim: PM loop round 15
Session: session_017nzWFRUo2U7evMkPdVniAn
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-147-signature-insert-state
Worktree: hotclm-issue-147
Domain: repo:hotclm (single-lane sister repo — no domain:*)
Seat: repo:hotclm#1
File surface: src/objects/contract.hook.ts (signature_state_machine insert leg) · the refusal message in src/translations/{en,zh-CN}/objects.lifecycle.ts if the hook's message is bundle-keyed, or a validation on src/objects/signature.object.ts if 18.1 expresses it declaratively — 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_signature write rules (which status a round may be born in) | none known | every signature round created headlessly or by hand
Thread-read: none
Premise: measured by the #138 dev on 18.1.0 (POST clm_signature {status: declined} → 201); re-measured by the dev on origin/main9248c18
Serial constraints cleared: PR #145 (same hook, completed_at on insert) merged 9248c18. Nothing else in flight.
Claim: PM loop round 15
Session: session_017nzWFRUo2U7evMkPdVniAn
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-147-signature-insert-state
Worktree: hotclm-issue-147
Domain: repo:hotclm (single-lane sister repo — no domain:*)
Seat: repo:hotclm#1
File surface: src/objects/contract.hook.ts (signature_state_machine insert leg) · the refusal message in src/translations/{en,zh-CN}/objects.lifecycle.ts if the hook's message is bundle-keyed, or a validation on src/objects/signature.object.ts if 18.1 expresses it declaratively — 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_signature write rules (which status a round may be born in) | none known | every signature round created headlessly or by hand
Thread-read: none
Premise: measured by the #138 dev on 18.1.0 (POST clm_signature {status: declined} → 201); re-measured by the dev on origin/main9248c18
Serial constraints cleared: PR #145 (same hook, completed_at on insert) merged 9248c18. Nothing else in flight.
{
"issue": 147,
"status": "done",
"branch": "claude/issue-147-signature-insert-state",
"pr": "https://github.com/objectstack-ai/hotclm/pull/150",
"session": "session_017nzWFRUo2U7evMkPdVniAn",
"premise_still_valid": true,
"summary": "Premise re-measured on 9248c18 as Legal Counsel 1: REST POST, /api/v1/batch create and json import each accepted a round born declined, voided or e-signature completed (201/200/ok). Entry set = draft, sent (any method), completed only for wet ink; derived from DESIGN.md §03 (insert judged as the first edge out of draft, minus draft→voided which abandons an existing round), the manual wet-ink form (PR #145), the seed (esign/sent 4, wet_ink/sent 2, esign/completed 24) and an ASSUMED connector entry of sent (F8 #83 / objectstack#25747 are unbuilt; callback updates the round by envelope_id). Implemented as a declarative state_machine rule signature_initial_status on clm_signature (events insert, initialStates draft/sent/completed; message in en and zh-CN _validations; skipped by the engine on seedReplay and treatAsHistorical import) plus the method half in the signature_state_machine hook insert leg (esign not created completed, ctx.session.isSystem exempt, same exemption contract_type_stamp uses). That one hook refusal is English for zh-CN callers, because 18.1.0 hook errors have no translation seam and a script/conditional rule is not seed-exempt; raised as an open question. Wet-ink path, seed totals, boot warnings and the update leg are unchanged.",
"tests": "Runtime, pnpm demo --port 3147 on clean .objectstack/data, before 9248c18 and after ce42ca5, Legal Counsel 1 created the README way (create-user 200, sys_user_position clm_legal_counsel 201, change-password 200, sign-in 200). REST POST declined/voided (esign and wet_ink): before 201 each, after 400 VALIDATION_FAILED fields[0]={field:status,code:invalid_initial_state,constraint.allowed:'draft, sent, completed'}; accept-language zh-CN returns the zh message. esign completed: before 201, after 422 INVALID_STATE (English, hook). draft / omitted / esign sent / wet_ink sent / wet_ink completed: 201 both heads (completed_at stamped). /api/v1/batch create declined(en)/voided(zh-CN)/draft: before 200x3, after 400/400 (zh text)/200; batch esign completed after 422. Import json insert [esign declined, wet_ink voided, esign draft]: before ok=3, after ok=1 errors=2 code invalid_initial_state; zh-CN import returns zh text; import [esign completed, wet_ink completed, esign sent] after ok=2 errors=1. Update leg after: draft→sent→declined→draft→voided 200x4; seeded esign sent round PATCH envelope_id 200, sent→declined 200, declined→draft 200, draft→completed 422 (unchanged message). State introspection GET /api/v1/meta/object/clm_signature/state/status?from=sent → next [completed, declined, voided]. Browser Chromium /opt/pw-browsers/chromium as Legal Counsel 1: en form E-signature+Declined → POST 400, message under Status; zh-CN form 电子签+已拒签 → 400, zh message under 状态; zh-CN 电子签+已完成 → 422 English toast; en form Wet ink+Completed+PDF upload, Completed At empty → POST 201, completed_at 2026-10-26T14:47:32.586Z, F7 created final_signed v1 with the file; Records Manager 1 (clm_records_manager) activate_contract → 200, NDA-2026-0005 active, executed_at = signed_at = 14:47:32.586Z, activated_at 14:47:45.545Z. Console: pre-login 404/401 and pre-existing record.status predicate warnings only. Seed: both heads inserted 830 updated 0 errored 404; clm_signature 30 rows esign/completed 24, esign/sent 4, wet_ink/sent 2. Boot warning signatures (normalised): 425 before, 423 after, 0 only-after (the 2 only-before are probe lines). Same-DB re-run of pnpm demo (upsert replay): inserted 0 updated 74 skipped 755 errored 332, no clm_signature line. NOT MEASURED: editing a seeded esign completed round (dev admin PATCH answered 403, persona rule, no other route); treatAsHistorical import (system principal only); a runAs system flow creating rounds (none exists before F8). No unit-test suite in this metadata-only repo; no ablation (no tested behaviour pinned by a test).",
"gates": "head ce42ca5, each exit captured to a file before any pipe: pnpm install exit 0 (Done in 742ms); pnpm validate exit 0 (✓ Validation passed); pnpm lint exit 0 (6 suggestion(s), 0 warnings); pnpm typecheck exit 0; pnpm lint:i18n-gate exit 0 (COVERAGE 0 missing keys across 2 locales); CLM_DEMO_SEED=1 pnpm validate exit 0 (✓ Validation passed). CI on PR #150 head ce42ca5 at report time: Validate in_progress (one run) and Validate completed success (one run). dispatch-gates.mjs is objectstack-only and was not applied to this sister repo; the gate list is hotclm's own ci.yml/AGENTS.md set.",
"dogfood_battery": "n/a — sister repo, no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 — fleet-write relay: 1 repository_dispatch carrying pr_create (objectstack-ai/hotclm#150, body read back identical 12559 bytes, assignee zhuangjianguo); 1 os-dev-report comment on #147 via post-stamped. git push of the branch is not a REST write.",
"open_questions": [
{
"question": "The e-signature-completed-at-birth refusal (hook insert leg) reaches a zh-CN caller in English; how should it close?",
"options": [
"A keep as shipped (one English hook refusal, same as the existing update-leg refusal for the same guard) and ask the platform for a translation seam for hook refusals or a seed-replay-exempt CEL rule",
"B drop the hook half: esign completed becomes a legal birth for a person, making the update leg's draft→completed guard bypassable through an insert",
"C a translated script rule: refuses the 24 seeded esign completed rounds, needs a §10 seed change"
],
"recommendation": "A, because it keeps §03's method guard on every door and the language gap is the platform's, the same one #125 recorded"
}
],
"out_of_scope_findings": [
"class: none (observation, platform) · reach: browser form, NDA-2026-0005 Signing & Archive › New: the Status picker offers Draft/Sent/Completed/Declined/Voided although the object declares initialStates draft/sent/completed; the write refuses and the message lands under the field · carrier: none — noted in PR Acceptance notes, not filed · dedupe words: initialStates create form status options console picker",
"class: none (comment drift) · reach: none (read-only) · src/flows/signature-record.flow.ts header still says signature_state_machine guards beforeUpdate only, untrue since #145 · carrier: none — noted in PR Acceptance notes, outside this card's file surface",
"class: none (platform limitation, already recorded by #125) · hook-thrown refusals carry no translation seam on 18.1.0, so every hook refusal in this app is English for zh-CN callers · carrier: the open question above (seat) · dedupe words: hook error message translation locale i18n refusal"
]
}
Release: a signature round can only be created as draft, sent, or (wet ink) completed, on every door; the seed and the wet-ink path are unchanged. The one English-only refusal (an e-signature round created as completed) waits for objectstack-ai/objectstack#26170. State: pm:dispatched removed, assignee cleared.
Activity
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 15
Session:
session_017nzWFRUo2U7evMkPdVniAnAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-147-signature-insert-stateWorktree:
hotclm-issue-147Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/objects/contract.hook.ts(signature_state_machineinsert leg) · the refusal message insrc/translations/{en,zh-CN}/objects.lifecycle.tsif the hook's message is bundle-keyed, or a validation onsrc/objects/signature.object.tsif 18.1 expresses it declaratively — 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_signature write rules (which status a round may be born in) | none known | every signature round created headlessly or by handThread-read: none
Premise: measured by the #138 dev on 18.1.0 (
POST clm_signature {status: declined}→ 201); re-measured by the dev onorigin/main9248c18Serial constraints cleared: PR #145 (same hook,
completed_aton insert) merged9248c18. Nothing else in flight.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 15
Session:
session_017nzWFRUo2U7evMkPdVniAnAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-147-signature-insert-stateWorktree:
hotclm-issue-147Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/objects/contract.hook.ts(signature_state_machineinsert leg) · the refusal message insrc/translations/{en,zh-CN}/objects.lifecycle.tsif the hook's message is bundle-keyed, or a validation onsrc/objects/signature.object.tsif 18.1 expresses it declaratively — 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_signature write rules (which status a round may be born in) | none known | every signature round created headlessly or by handThread-read: none
Premise: measured by the #138 dev on 18.1.0 (
POST clm_signature {status: declined}→ 201); re-measured by the dev onorigin/main9248c18Serial constraints cleared: PR #145 (same hook,
completed_aton insert) merged9248c18. Nothing else in flight.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 147, "status": "done", "branch": "claude/issue-147-signature-insert-state", "pr": "https://github.com/objectstack-ai/hotclm/pull/150", "session": "session_017nzWFRUo2U7evMkPdVniAn", "premise_still_valid": true, "summary": "Premise re-measured on 9248c18 as Legal Counsel 1: REST POST, /api/v1/batch create and json import each accepted a round born declined, voided or e-signature completed (201/200/ok). Entry set = draft, sent (any method), completed only for wet ink; derived from DESIGN.md §03 (insert judged as the first edge out of draft, minus draft→voided which abandons an existing round), the manual wet-ink form (PR #145), the seed (esign/sent 4, wet_ink/sent 2, esign/completed 24) and an ASSUMED connector entry of sent (F8 #83 / objectstack#25747 are unbuilt; callback updates the round by envelope_id). Implemented as a declarative state_machine rule signature_initial_status on clm_signature (events insert, initialStates draft/sent/completed; message in en and zh-CN _validations; skipped by the engine on seedReplay and treatAsHistorical import) plus the method half in the signature_state_machine hook insert leg (esign not created completed, ctx.session.isSystem exempt, same exemption contract_type_stamp uses). That one hook refusal is English for zh-CN callers, because 18.1.0 hook errors have no translation seam and a script/conditional rule is not seed-exempt; raised as an open question. Wet-ink path, seed totals, boot warnings and the update leg are unchanged.", "tests": "Runtime, pnpm demo --port 3147 on clean .objectstack/data, before 9248c18 and after ce42ca5, Legal Counsel 1 created the README way (create-user 200, sys_user_position clm_legal_counsel 201, change-password 200, sign-in 200). REST POST declined/voided (esign and wet_ink): before 201 each, after 400 VALIDATION_FAILED fields[0]={field:status,code:invalid_initial_state,constraint.allowed:'draft, sent, completed'}; accept-language zh-CN returns the zh message. esign completed: before 201, after 422 INVALID_STATE (English, hook). draft / omitted / esign sent / wet_ink sent / wet_ink completed: 201 both heads (completed_at stamped). /api/v1/batch create declined(en)/voided(zh-CN)/draft: before 200x3, after 400/400 (zh text)/200; batch esign completed after 422. Import json insert [esign declined, wet_ink voided, esign draft]: before ok=3, after ok=1 errors=2 code invalid_initial_state; zh-CN import returns zh text; import [esign completed, wet_ink completed, esign sent] after ok=2 errors=1. Update leg after: draft→sent→declined→draft→voided 200x4; seeded esign sent round PATCH envelope_id 200, sent→declined 200, declined→draft 200, draft→completed 422 (unchanged message). State introspection GET /api/v1/meta/object/clm_signature/state/status?from=sent → next [completed, declined, voided]. Browser Chromium /opt/pw-browsers/chromium as Legal Counsel 1: en form E-signature+Declined → POST 400, message under Status; zh-CN form 电子签+已拒签 → 400, zh message under 状态; zh-CN 电子签+已完成 → 422 English toast; en form Wet ink+Completed+PDF upload, Completed At empty → POST 201, completed_at 2026-10-26T14:47:32.586Z, F7 created final_signed v1 with the file; Records Manager 1 (clm_records_manager) activate_contract → 200, NDA-2026-0005 active, executed_at = signed_at = 14:47:32.586Z, activated_at 14:47:45.545Z. Console: pre-login 404/401 and pre-existing record.status predicate warnings only. Seed: both heads inserted 830 updated 0 errored 404; clm_signature 30 rows esign/completed 24, esign/sent 4, wet_ink/sent 2. Boot warning signatures (normalised): 425 before, 423 after, 0 only-after (the 2 only-before are probe lines). Same-DB re-run of pnpm demo (upsert replay): inserted 0 updated 74 skipped 755 errored 332, no clm_signature line. NOT MEASURED: editing a seeded esign completed round (dev admin PATCH answered 403, persona rule, no other route); treatAsHistorical import (system principal only); a runAs system flow creating rounds (none exists before F8). No unit-test suite in this metadata-only repo; no ablation (no tested behaviour pinned by a test).", "gates": "head ce42ca5, each exit captured to a file before any pipe: pnpm install exit 0 (Done in 742ms); pnpm validate exit 0 (✓ Validation passed); pnpm lint exit 0 (6 suggestion(s), 0 warnings); pnpm typecheck exit 0; pnpm lint:i18n-gate exit 0 (COVERAGE 0 missing keys across 2 locales); CLM_DEMO_SEED=1 pnpm validate exit 0 (✓ Validation passed). CI on PR #150 head ce42ca5 at report time: Validate in_progress (one run) and Validate completed success (one run). dispatch-gates.mjs is objectstack-only and was not applied to this sister repo; the gate list is hotclm's own ci.yml/AGENTS.md set.", "dogfood_battery": "n/a — sister repo, no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — fleet-write relay: 1 repository_dispatch carrying pr_create (objectstack-ai/hotclm#150, body read back identical 12559 bytes, assignee zhuangjianguo); 1 os-dev-report comment on #147 via post-stamped. git push of the branch is not a REST write.", "open_questions": [ { "question": "The e-signature-completed-at-birth refusal (hook insert leg) reaches a zh-CN caller in English; how should it close?", "options": [ "A keep as shipped (one English hook refusal, same as the existing update-leg refusal for the same guard) and ask the platform for a translation seam for hook refusals or a seed-replay-exempt CEL rule", "B drop the hook half: esign completed becomes a legal birth for a person, making the update leg's draft→completed guard bypassable through an insert", "C a translated script rule: refuses the 24 seeded esign completed rounds, needs a §10 seed change" ], "recommendation": "A, because it keeps §03's method guard on every door and the language gap is the platform's, the same one #125 recorded" } ], "out_of_scope_findings": [ "class: none (observation, platform) · reach: browser form, NDA-2026-0005 Signing & Archive › New: the Status picker offers Draft/Sent/Completed/Declined/Voided although the object declares initialStates draft/sent/completed; the write refuses and the message lands under the field · carrier: none — noted in PR Acceptance notes, not filed · dedupe words: initialStates create form status options console picker", "class: none (comment drift) · reach: none (read-only) · src/flows/signature-record.flow.ts header still says signature_state_machine guards beforeUpdate only, untrue since #145 · carrier: none — noted in PR Acceptance notes, outside this card's file surface", "class: none (platform limitation, already recorded by #125) · hook-thrown refusals carry no translation seam on 18.1.0, so every hook refusal in this app is English for zh-CN callers · carrier: the open question above (seat) · dedupe words: hook error message translation locale i18n refusal" ] }objectstack-fleet commentedon Oct 26, 2026
Landed — PR #150 · 2026-10-26
MERGED(squash onmain), merged at its ACCEPT headce42ca5;Fixes #147closes this cardRelease: a signature round can only be created as draft, sent, or (wet ink) completed, on every door; the seed and the wet-ink path are unchanged. The one English-only refusal (an e-signature round created as completed) waits for objectstack-ai/objectstack#26170. State:
pm:dispatchedremoved, assignee cleared.Generated by Claude Code