Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
## Project context

An **ObjectStack** application: contract lifecycle management (intake → review → approval → signing
and sealing → obligations and payments → renewal and archive) defined as typed metadata. A sellable
and execution formalities → obligations and payments → renewal and archive) defined as typed metadata. A sellable
standard product, not a starter: every customer-specific need goes through `docs/requirements/`
triage (A already supported · B standard enhancement · C customer overlay · D decline) before it
touches `src/`.
Expand Down Expand Up @@ -48,7 +48,10 @@ Paste the three green tails into the PR body.
| Exports | `PascalCase`, barrel via `Object.values()` | `export { Contract } from './contract.object.js'` |

- **Industry-neutral, always.** No vertical vocabulary in any object, field, option value or label.
Contract types, approval thresholds, seal kinds, payment terms and signing entities live in seed data.
Contract types, approval thresholds, execution formalities, currencies, payment terms and signing entities live in seed data.
- **Global by default.** English is the default locale and the source of every label; `zh-CN` is a full second bundle.
Nothing in schema, option values or defaults assumes one country: a region-specific requirement is an
execution formality, a seed row or a connector, never a hard-coded path.
- **Reserved platform words — never as field names:** `role`, `position`, `permission_set`,
`business_unit` (ADR-0090 D3; `validate` refuses them as `security-role-word`). Use a domain word.
- **Never set `namespace` or `tableName` on an object.** Prefix lives in `name`.
Expand All @@ -72,7 +75,7 @@ src/objects/ clm_*.object.ts + *.hook.ts src/profiles/ src/sharing
src/views/ src/pages/ *.view.ts / *.page.ts src/flows/ F1–F15 (DESIGN.md §06)
src/apps/ one App, five audience groups src/skills/ S1–S4 (DESIGN.md §07)
src/datasets/ src/dashboards/ analytics src/mappings/ import projections
src/translations/ zh-CN (default), en src/data/ demo-zh/ · demo-en/
src/translations/ en (default), zh-CN src/data/ demo-en/ · demo-zh/
docs/backlog/ work cards docs/requirements/ customer requirement triage
```

Expand Down
177 changes: 109 additions & 68 deletions DESIGN.md

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# HotCLM

**Contract lifecycle management on [ObjectStack](https://github.com/objectstack-ai/objectstack) — buy-side, sell-side and everything in between, as typed metadata.**
Self-serve intake, a clause playbook, a data-driven approval matrix, sealing and e-signature, obligations and payment schedules: the whole lifecycle in one readable repository.
Self-serve intake, a clause playbook, a data-driven approval matrix, e-signature and execution formalities, obligations and payment schedules: the whole lifecycle in one readable repository.

**基于 ObjectStack 的合同全生命周期管理。** 业务自助发起、条款库与偏离、审批矩阵、用印与电子签、履约义务、收付款计划 —— 全部是类型化元数据。
**基于 ObjectStack 的合同全生命周期管理。** 业务自助发起、条款库与偏离、审批矩阵、电子签与执行形式、履约义务、收付款计划 —— 全部是类型化元数据。

> Status: **M0 — scaffold and configuration domain.** See [DESIGN.md](./DESIGN.md) for the model and
> [docs/backlog](./docs/backlog/README.md) for what is being built next. Sibling app of
Expand All @@ -16,8 +16,9 @@ Self-serve intake, a clause playbook, a data-driven approval matrix, sealing and
- **Ironclad-shaped, not OA-shaped.** A contract type *is* a workflow: intake fields, review, approval ladder, signing method, archive rules — configuration, not code.
- **Business users launch, legal controls.** One intake form per contract type; the approval matrix decides who signs off.
- **Contracts are data.** Obligations, payment schedules, renewals and deviations from the clause playbook are queryable records with reminders, not paragraphs in a PDF.
- **Built for the market it sells in.** Sealing requests (用印), a counterparty register with verification, and a `zh-CN`-first demo.
- **Industry-neutral by rule** — contract types, thresholds, seal kinds and payment terms live in seed data only.
- **Global by default, local by configuration.** English-first UI with full `zh-CN`; multi-entity, multi-currency, governing law and jurisdiction on every contract; e-signature through DocuSign, Adobe Acrobat Sign or Dropbox Sign; a company seal, notarization or witnessing are execution formalities a contract type can require, not modules.
- **AI is a participant under governance.** Intake by chat or MCP, extraction of executed contracts, review memos for approvers, deviation detection against the playbook — every AI step proposes, a person confirms, and the audit trail records both.
- **Industry- and region-neutral by rule** — contract types, thresholds, execution formalities, currencies and payment terms live in seed data only.

## Quick start

Expand Down
24 changes: 24 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# docs/ — how HotCLM manages goals, requirements and work

Four layers, each with one job. Nothing lives in two of them.

| Layer | Where | What it holds | Changes how |
|---|---|---|---|
| **Direction** | [`ROADMAP.md`](./ROADMAP.md) | Releases, themes, what each release unlocks and what it depends on | Maintainer edits; a dated entry per change |
| **Requirements baseline** | [`design/00-设计方案.md`](./design/00-设计方案.md) | The complete business design; §待确认事项 becomes 已确认口径 when the maintainer answers | Versioned, append-only version record; V1.0 = the baseline for release 1.0 |
| **Engineering authority** | [`../DESIGN.md`](../DESIGN.md) | Names, enums, OWD, guards, flows, milestones — what a card may not contradict | Amended by decision; a card that conflicts stops with `needs_decision` |
| **Customer asks** | [`requirements/`](./requirements/) *(created with the first customer)* | One file per raw requirement, verbatim, with an A/B/C/D disposition (already supported · standard enhancement · customer overlay · decline) | File per ask; only B lands in `src/` |
| **Work** | [`backlog/`](./backlog/) → GitHub issues | Dispatch-ready cards; an issue exists only while a card is ready or in flight | Card → issue with `pm:queue` → draft PR → closed on merge |

## Why issues stay small

GitHub issues are for **work that is ready to dispatch, bugs, and `needs-user-decision` questions** — nothing else. Goals live in the roadmap, requirements in the versioned design documents, and "what exists" in the feature inventory (`feature-inventory.md`, created at M4 with stable ids `CON-001 …`). An issue that would restate a design chapter is a sign the chapter is missing, not a reason to open the issue.

Traceability runs through ids, not through issue links: a customer ask (`requirements/NNNN-slug.md`) → a design chapter (§) → a feature-inventory row (`CON-nnn`) → a test. The dispatch report and the PR body name the ids they touched.

## GitHub conventions

- **Milestones** `M1 数据与权限骨架` · `M2 发起与审批` · `M3 签后与分析` · `M4 集成与发布` mirror `DESIGN.md` §11; every issue carries one.
- **Labels**: `pm:queue` (ready) · `pm:dispatched` (in flight) · `needs-user-decision` (blocked on the maintainer) · `bug` · `platform-gap` (reported upstream, fixture in place).
- **`Blocked-by: #n`** in the issue body is honoured by the dispatch loop.
- One issue per PR, draft PRs, squash merges by the maintainer.
26 changes: 26 additions & 0 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# HotCLM Roadmap

> Direction, not a commitment. Each release lists what it ships, what it unlocks for a buyer, and what it depends on. Items move between releases by a dated entry in the log at the bottom; the design documents change first, this file second.

## Releases

| Release | Theme | Ships | Buyer can now | Depends on |
|---|---|---|---|---|
| **0.1** | Skeleton | 11 objects, state machine guards, positions and permission sets, sharing and FLS, configuration seeds (M1) | Load a contract register with correct visibility | — |
| **0.2** | Launch to approval | Intake screen flow, routing, five-rung approval ladder with 会签, signature record and execution formalities, legal workbench, contract page, full demo data (M2) | Run intake → review → deviation → approval → execution → active end to end | 0.1 |
| **0.3** | After signature | Obligations, payment schedules, renewal and expiry sweeps, archive, four datasets, three dashboards, `en` + `zh-CN` (M3) | Manage the live book: what is due, what is late, what renews | 0.2 |
| **1.0** | Marketplace GA | E-signature (DocuSign first), HotCRM hand-off, AI participation (six skills, approver memo, MCP tool surface), legacy import, docs site, screenshots, marketplace listing (M4) | Install with one click and sell it as a standalone CLM | 0.3 · cloud AI tier for AI features |
| **1.x** | Widen the core | Adobe Acrobat Sign and Dropbox Sign connectors; sanctions and registry screening connectors; Slack notifications; Salesforce contract hand-off; self-serve report views | Fit more stacks without custom work | Platform connectors as they land |
| **2.0** | Document layer | Template-driven generation, redline comparison, print and PDF, inbound email to version | Draft and negotiate inside the product, not in Word attachments | Platform: document generation and editor, PDF (#9), inbound channels (#39) |
| **2.x** | Outside the wall | Counterparty portal, external auditor read-only access, first region pack (China: regional e-sign providers, local registry screening, seal circulation) | Let the other side and outside reviewers in; sell in a region with its own formalities | Platform: external portal (#27); extension-package install (ADR-0126) |

## How items move

1. A capability enters the roadmap only with a named buyer outcome and a named dependency.
2. Anything whose dependency is a **platform gap** stays in 2.0/2.x until the gap closes upstream; the gap is reported once to objectstack-ai/objectstack and referenced here, never patched in this repo.
3. A customer ask (`docs/requirements/`) with disposition **B** may pull an item forward; disposition **C** never touches the roadmap.
4. AI capabilities ship only when they run for real on the cloud tier and degrade honestly elsewhere; no roadmap item is "AI-ready" or "scaffolded".

## Log

- 2026-09-07 — Created. Global-first revision folded in: execution formalities replace the sealing module, DocuSign leads e-signature, region packs move to 2.x.
17 changes: 9 additions & 8 deletions docs/backlog/02-contract-domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,40 @@ Milestone: M1 · Labels: `pm:queue` · Blocked-by: — (builds on the initial co

## Scope
`src/objects/contract.object.ts`, `contract-version.object.ts`, `review.object.ts`, `deviation.object.ts`,
`seal-request.object.ts`, plus `contract.hook.ts` (state machine) and `mirror.hook.ts` (display_name
`signature.object.ts`, plus `contract.hook.ts` (state machine) and `mirror.hook.ts` (display_name
stamps). Export from `src/objects/index.ts` under the "Contract domain" comment, `Contract` first.

## Spec — pinned in DESIGN.md §03, repeated here where a choice exists
**`clm_contract`** — `sharingModel: 'private'`, `nameField: 'title'`, icon `file-signature`. Fields exactly as
DESIGN.md §03, with these decisions taken:
- `contract_number`: **text, stored, generated by the hook** (DESIGN.md §13 Q5 — `autonumber` cannot express
- `contract_number`: **text, stored, generated by the hook** (ruled 2026-09-07, DESIGN.md §13 Q5 — `autonumber` cannot express
`<type.code>-<YYYY>-<seq>`). Format `${type.code}-${year}-${4-digit seq per type per year}`, stamped
beforeInsert, readonly, unique index `(contract_number)` scope organization.
- `category`, `direction`, `requires_seal`: stamped from `contract_type` beforeInsert/beforeUpdate, readonly.
- `amount`: currency, scale 2, min 0. `currency_code`: select seeded (`CNY` default, `USD`, `EUR`).
- `category`, `direction`, `execution_formalities`: stamped from `contract_type` beforeInsert/beforeUpdate, readonly.
- `amount`: currency, scale 2, min 0. `currency_code`: select seeded (`USD` default, `EUR`, `GBP`, `CNY`, `JPY`); the organization default is a setting, not schema. Add `governing_law` (text, ISO country or state, e.g. `US-NY`, `England and Wales`), `jurisdiction` (text), `contract_language` (select seeded `en` default).
- `status`: select with the exact values of the §03 state machine, default `draft`; every option carries a color.
- `route_*`, `approval_status`, stage timestamps (`submitted_at` … `closed_at`), `is_expiring`, `sealed_at`,
- `route_*`, `approval_status`, stage timestamps (`submitted_at` … `closed_at`), `is_expiring`, `executed_at`,
`archived_at`: readonly, hook/flow-written only.
- Lookups: `contract_type*` → `clm_contract_type`; `party*` → `clm_party`; `legal_owner` → `Field.user`;
`renewed_from` / `parent_contract` → `clm_contract`; `crm_contract` → lookup `crm_contract` **only if**
`objectstack validate` accepts a cross-package reference to an object not in this stack — otherwise leave
the field out and return `needs_decision` naming the refusal.
- `is_backfilled` boolean, readonly, default false — set only by the F16 `executed_upload` action (card 09).
- Roll-ups (`version_count`, `open_deviation_count`, `planned_amount`, `actual_amount`,
`overdue_obligation_count`) are **card 03** — declare nothing here.

**Children** — all `sharingModel: 'controlled_by_parent'`, `nameField: 'display_name'`, `contract*`
masterDetail `clm_contract` with `deleteBehavior: 'cascade'`, `inlineEdit: 'grid'`:
`clm_contract_version` (icon `file-stack`, inlineTitle `Versions`) · `clm_review` (icon `gavel`, `Reviews`) ·
`clm_deviation` (icon `git-branch`, `Deviations`) · `clm_seal_request` (icon `stamp`, `Seal Requests`).
`clm_deviation` (icon `git-branch`, `Deviations`) · `clm_signature` (icon `pen-line`, `Signatures`) — one execution record per signing round: `method` `esign/wet_ink`, `provider`, `envelope_id`, `signers` json (`[{ side: our|counterparty, name, email, order, status, signed_at }]`), `status` `draft/sent/completed/declined/voided`, `formalities_done` multiselect mirroring the type's `execution_formalities`, `completed_at`, `executed_file` file.
Fields and enums exactly as DESIGN.md §03. `display_name` is a stored text mirror stamped by `mirror.hook.ts`
with the format DESIGN.md gives per object.

**State machine** — `contract.hook.ts`, beforeUpdate on `status`: the transition table of DESIGN.md §03,
including every guard (required intake fields, party not `blocked`, no `open` deviation before
`in_approval`, `clean` version before `signing`, `final_signed` + `sealed_at` before `active`, `closed_at`
`in_approval`, `clean` version before `signing`, a `completed` signature whose `formalities_done` covers the type's `execution_formalities` + `final_signed` version before `active`, `closed_at`
on `terminated`). Refuse with a structured error (`code`, `status: 422`) — never silently coerce.
Stamp the stage timestamp on each entry. Child state machines (deviation, seal request) in the same file.
Stamp the stage timestamp on each entry. Child state machines (deviation, signature) in the same file.

## Acceptance
- Gates green; `pnpm validate` reports 9 objects.
Expand Down
10 changes: 5 additions & 5 deletions docs/backlog/04-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
Milestone: M1 · Labels: `pm:queue` · Blocked-by: 02, 03

## Scope
`src/profiles/*.profile.ts` (6 permission sets), `src/sharing/positions.ts` (8 positions),
`src/sharing/*.sharing.ts` (7 rules), FLS declarations, `src/security/bind-position-sets.ts` +
`src/profiles/*.profile.ts` (5 permission sets), `src/sharing/positions.ts` (7 positions),
`src/sharing/*.sharing.ts` (6 rules), FLS declarations, `src/security/bind-position-sets.ts` +
`onEnable` in `objectstack.config.ts`. Adds `requires: ['sharing']`.

## Spec — DESIGN.md §04, verbatim
Positions, sets, the permission matrix, the seven sharing rules and the FLS table are pinned there.
Positions, sets, the permission matrix, the six sharing rules and the FLS table are pinned there (global-first revision, 2026-09-07: no seal keeper; `clm_records_manager` covers execution and archive).
Capabilities granted via `systemPermissions`: `clm_requester.access` · `clm_legal.access` · `clm_finance.access`
· `clm_seal.access` · `clm_archive.access` · `clm_admin.access`; action gates `approve_contract` ·
`seal_contract` · `archive_contract` · `terminate_contract` · `manage_clauses` · `manage_approval_rules`.
· `clm_records.access` · `clm_admin.access`; action gates `approve_contract` ·
`execute_contract` · `archive_contract` · `terminate_contract` · `manage_clauses` · `manage_approval_rules`.
`contract_manager_reports` uses `writeScope: 'own_and_reports'` **only if** declaring it does not require the
`hierarchy-security` capability at validate time; if it does, declare the capability (it is safe on an
open-edition boot — see HotCRM's `objectstack.config.ts` note) and record the edition boundary in the PR.
Expand Down
2 changes: 1 addition & 1 deletion docs/backlog/05-intake-and-route.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type's `intake_fields` lists (`visibleWhen`/`requiredWhen` on the screen fields)
tick "draft from template" (which attaches the type's `template_file` as version 1 with kind `draft`) →
create `clm_contract` (`draft`) + `clm_contract_version` v1 → optional "submit now" toggle → `submitted`.
Declare `ai: { exposed: true }` with every input as an `isInput` variable so MCP can complete it headlessly.
F2 (hook, entering `submitted`): stamp category/direction/requires_seal; evaluate active
F2 (hook, entering `submitted`): stamp category/direction/execution_formalities; evaluate active
`clm_approval_rule` rows (category ∈ applies_to or empty; direction match or `any`; amount band;
`only_with_deviation`) and stamp the union of `route_*`; `submitted_at`; if the type requires legal review,
assign `legal_owner` round-robin among holders of `clm_legal_counsel` by open-contract count and enter
Expand Down
7 changes: 3 additions & 4 deletions docs/backlog/06-approval-ladder.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# F5 approval ladder · F7 seal request approval
# F5 approval ladder · F7 signature record and execution formalities

Milestone: M2 · Labels: `pm:queue` · Blocked-by: 04

## Scope
`src/flows/contract-approval.flow.ts` (F5), `src/flows/seal-request-approval.flow.ts` (F7).
`src/flows/contract-approval.flow.ts` (F5), `src/flows/signature-record.flow.ts` (F7).
Adds `requires: ['approvals', 'messaging']`.

## Spec — DESIGN.md §06 F5/F7
Expand All @@ -15,8 +15,7 @@ neither → skip. Decision `route_executive` → position `clm_executive`. Decis
`clm_general_manager`. `lockRecord: true`, `approvalStatusField: 'approval_status'`. Out-edges: approve →
`update_record` status `approved` + `approved_at`; reject → `rejected`; send-back → `draft`. `notify` the
owner on every terminal outcome (inbox). Approving is gated on `approve_contract`.
F7: `record_change` on `clm_seal_request` create → approval by `clm_legal_head` → `approved` + notify
`clm_seal_keeper` holders; keeper's transition to `sealed` stamps the parent's `sealed_at` (hook).
F7: `record_change` on `clm_signature` reaching `completed` → hook checks `formalities_done` against the type's `execution_formalities`; when covered, stamp the parent's `executed_at` and create the `final_signed` version from `executed_file`; when a formality is missing, notify legal (`clm_legal_counsel` owner) naming it. Wet-ink path: legal uploads the executed copy on the signature record and ticks the formalities.

## Acceptance
- Gates green; `os lint` shows no `approval-approver-not-membership-tier`.
Expand Down
Loading