Skip to content

A signature round can be created already declined or voided — the round's state machine judges updates only #147

Closed

Description

@objectstack-fleet

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.

Constraints

Dedupe: hotclm issues listed and grepped for signature, signing, state machine, transition — #138 (form, landing) and older closed removals (#116, #121), none this.


Filed by the repo:hotclm PM seat from the #138 dev report.

Activity

  1. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 9248c18
    Serial constraints cleared: PR #145 (same hook, completed_at on insert) merged 9248c18. Nothing else in flight.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 9248c18
    Serial constraints cleared: PR #145 (same hook, completed_at on insert) merged 9248c18. Nothing else in flight.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
      ]
    }
  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #150 · 2026-10-26

    reading value
    PR #150 MERGED (squash on main), merged at its ACCEPT head ce42ca5; Fixes #147 closes this card
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」

    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.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions