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
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ console does not list them; this table is where they are named.

| Account | Sign in as | Position | What it holds in the demo |
|---|---|---|---|
| `Business Requester 1` · `2` · `3` | `requester1@example.com` · `requester2@example.com` · `requester3@example.com` | none — every member holds `clm_requester` | the 120 contracts' `owner_id`, dealt by counterparty — 43 / 43 / 34 · and the 143 delivery and reporting obligations under them — 50 / 53 / 40 · the 2 business reviews — 0 / 1 / 1 |
| `Business Requester 1` · `2` · `3` | `requester1@example.com` · `requester2@example.com` · `requester3@example.com` | none — every member holds `clm_requester` | the 120 contracts' `owner_id`, dealt by counterparty — 43 / 43 / 34 · and the 144 delivery and reporting obligations under them — 50 / 54 / 40 · the 2 business reviews — 0 / 1 / 1 |
| `Legal Counsel 1` · `2` | `counsel1@example.com` · `counsel2@example.com` | `clm_legal_counsel` | receives contracts on submission (round-robin by open load) and owns review · the `legal_owner` of every contract legal has accepted — 34 / 34 · all 56 compliance obligations — 29 / 27 · the `decided_by` of all 17 decided deviations — 10 / 7 · the reviewer of their contracts' 34 legal reviews — 16 / 18, the 7 still pending among them — 2 / 5 |
| `Head of Legal` | `legal.head@example.com` | `clm_legal_head` | rung 2 of the approval ladder whenever `route_legal_head` is stamped · the reviewer of the legal review of the contract handed back to the queue |
| `Finance Controller` | `finance@example.com` | `clm_finance_controller` | rung 2/3, reads every contract with `route_finance`, and the only position that may edit bank details · the reviewer of all 23 finance reviews, the 7 on contracts still in review or in approval among them, recorded there through **Record Finance Review** (记录财务审查) |
Expand Down Expand Up @@ -146,20 +146,20 @@ twice. The same goes for a contract someone has terminated or archived, or an
instalment finance has marked paid. Measured after one run of every daily job
plus those four desk actions: the re-run refused no row and dropped none.

Three limits. Any other column you edited, such as a summary or a reassigned
legal owner or a review's reviewer, goes back to the fixture's value. A
Three limits. Any other column you edited, such as a summary, a reassigned
legal owner or obligation owner, or a review's reviewer, goes back to the
fixture's value. A
deviation, signature round or review you decided is not covered yet: the re-run
puts its fixture status or decision back, and for a deviation or a signature
round the state machine may refuse that. The lifecycle columns also keep the dates they
were stamped with, while the fixture's own dates (start, end, due and planned
dates) move forward on each re-run. To get back to the corpus exactly as
shipped, start from a clean database: `rm -rf .objectstack/data && pnpm demo`.

**One obligation is deliberately left with no owner** and is not a name you have
missed. F10 (`obligation_due`) reminds an obligation's owner and takes a "nobody
to tell" edge when there is none — the demo keeps exactly one unowned row, due
seven days after boot, so that edge stays exercised. `src/data/plan-children.ts`
proves it reachable and says why one is both the minimum and the maximum.
**Every obligation has an owner**, its platform owner (`owner_id`), and that is
the person accountable for it: the one who finds it under **我负责的履约**, gets
F10's (`obligation_due`) reminders, and may update its progress. Legal and the
CLM administrator can hand an obligation to someone else; the owner cannot.

The dev admin is deliberately none of them. Like every member it holds
`clm_requester`, so it sees 我的合同 and 分析 (`GET /api/v1/meta/app/clm` serves
Expand Down
57 changes: 29 additions & 28 deletions src/data/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { assertUniqueKeys } from './_shared.js';
* The account the demo is driven as — the platform's dev admin.
*
* No REFERENCE names it any more. `clm_contract.legal_owner` left for
* {@link LEGAL_OWNERS}, `clm_obligation.owner` for {@link obligationOwnerOf},
* {@link LEGAL_OWNERS}, `clm_obligation.owner_id` for {@link obligationOwnerOf},
* `clm_deviation.decided_by` for {@link deviationDeciderOf}, and the last one,
* the `required` `clm_review.reviewer`, for {@link reviewerOf} (#186).
* Enumerated rather than sampled: `grep -rn DEMO_USER src` over this tree
Expand Down Expand Up @@ -140,7 +140,7 @@ export const LEGAL_OWNERS: readonly string[] = [
* The lawyer who knows this counterparty — the one rule every column that
* names a lawyer is dealt by, extracted so there is one of it. Enumerated,
* those columns are three: `clm_contract.legal_owner` ({@link legalOwnerOf}),
* the compliance share of `clm_obligation.owner`
* the compliance share of `clm_obligation.owner_id`
* ({@link obligationOwnerOf}) and `clm_deviation.decided_by`
* ({@link deviationDeciderOf}).
*
Expand Down Expand Up @@ -169,8 +169,8 @@ export const legalOwnerOf = (contract: ContractPlan): string | null =>
contract.hasLegalOwner ? counselFor(contract) : null;

/**
* Who is accountable for PERFORMING an obligation — or `null` for the one row
* this fixture deliberately leaves unassigned.
* Who is accountable for PERFORMING an obligation — the row's platform owner
* `owner_id` (issue #64, ruled A: `clm_obligation.owner` is retired).
*
* ## Why not {@link DEMO_USER}, which is what this column held before
*
Expand All @@ -186,14 +186,14 @@ export const legalOwnerOf = (contract: ContractPlan): string | null =>
* hides 我负责的履约 from it, and F10's reminder is the one kind of notice whose
* whole point is that the person receiving it goes and does the thing.
*
* `owner` is optional (DESIGN.md §03, and the field's own description: "Empty
* means unassigned, not the contract owner"), so like the other two optional
* references a name no account carries lands NULL, silently, and never
* refuses the row. The five accounts it names are declared demo staff
* (`demoStaff`, PR #171), created by the platform on the demo boot before the
* seed runs, so the obligations reach them on the first `pnpm demo`. The deal
* names NO new account: it spends only the five the fixture already deals
* contracts to.
* Every row names someone: `owner_id` is never empty. Like the contract's
* `owner_id`, a name no account carries would resolve to nothing and the
* seed-settle claim would hand the row to the platform admin (see
* `contract.seed.ts`), silently. The five accounts it names are declared demo
* staff (`demoStaff`, PR #171), created by the platform on the demo boot
* before the seed runs, so the obligations reach them on the first
* `pnpm demo`. The deal names NO new account: it spends only the five the
* fixture already deals contracts to.
*
* ## The rule: the desk that performs it, and the counterparty decides which
*
Expand All @@ -215,22 +215,22 @@ export const legalOwnerOf = (contract: ContractPlan): string | null =>
* contract's `owner_id`. DESIGN.md §03 declines to default this column to the
* contract owner; a fixture that put all 200 on the launching requester would
* assert by construction the very thing §03 refuses to assert by default, and
* a reader could no longer tell from the data that the two columns are
* independent. Dealing the compliance third away from the business owner is
* what makes that independence visible — and it is why `obligation_metrics`
* (§09's third dataset, whose first dimension after status and kind is
* `owner`) reads as five bars rather than one.
*
* ⚠️ Nothing here claims to reproduce a flow. No flow assigns this column:
* F9 creates renewal obligations from the type's defaults and leaves the owner
* to a person, and S5's `extract_obligations` proposes a 负责人 that a person
* confirms (§07). The deal states a rule a legal desk would recognise, in the
* a reader could no longer tell from the data that the two are independent.
* Dealing the compliance third away from the business owner is what makes
* that independence visible — and it is why `obligation_metrics` (§09's third
* dataset, whose first dimension after status and kind is `owner_id`) reads as
* five bars rather than one.
*
* ⚠️ Nothing here claims to reproduce a flow. The one flow that assigns this
* column is F9, and only for the renewal obligation it opens on activation,
* which goes to the contract's business owner (the renewal decision is
* theirs); S5's `extract_obligations` proposes a 负责人 that a person confirms
* (§07). The deal states a rule a legal desk would recognise, in the
* same counterparty-relationship style {@link ownerOf} and {@link legalOwnerOf}
* already use — so `demo-en` and `demo-zh` deal identically, no locale bundle
* being consulted for any of it.
*/
export const obligationOwnerOf = (obligation: ObligationPlan): string | null => {
if (!obligation.hasOwner) return null;
export const obligationOwnerOf = (obligation: ObligationPlan): string => {
const contract = CONTRACT_PLAN[obligation.contractIndex]!;
return obligation.kind === 'compliance' ? counselFor(contract) : ownerOf(contract);
};
Expand Down Expand Up @@ -288,10 +288,11 @@ export const obligationOwnerOf = (obligation: ObligationPlan): string | null =>
*
* ## No deliberate unassigned set here, and that is not an oversight
*
* {@link legalOwnerOf} and {@link obligationOwnerOf} both keep one deliberately
* empty because a FLOW takes a "nobody to tell" edge over that column — F3 over
* `legal_owner`, F10 over `owner` — and an empty column is what exercises it.
* No flow reads `decided_by`: it is written once and displayed. An empty one
* {@link legalOwnerOf} keeps one deliberately empty because a FLOW takes a
* "nobody to tell" edge over that column — F3 over `legal_owner` — and an empty
* column is what exercises it. (F10 used to do the same over the obligation's
* `owner`; its accountable person is now `owner_id`, which is never empty, so
* that edge left with the column, issue #64.) No flow reads `decided_by`: it is written once and displayed. An empty one
* would exercise nothing and would instead assert the one thing this column
* cannot mean, that a decided deviation was decided by nobody. So every decided
* row names a lawyer and every open row is `null` — which is not an unassigned
Expand Down
Loading
Loading