The party.* family (12 verbs). Every verb enters through the one front door: it is capability-checked, permission-checked, scope-checked and audited before it runs (see Security & permissions). Each entry below lists its capability, risk class (which fixes the minimum autonomy level), the minimum caller permission, its availability tier, whether it is idempotent, and any outbound egress.
Archiveparty.archive
| Property | Value |
|---|---|
| Capability | party.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
ACTIVE|SUSPENDED -> ARCHIVED (+reason); stamps archived_at. Terminal (retained-not-deleted, INV-8): still readable via party.get; reversal is a fresh party.
Call POST /v/party.archive with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.
Createparty.create
| Property | Value |
|---|---|
| Capability | party.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
{ kind, displayName, identifiers?: [{type, value}], extra? }. WRITE/L1.
Creates ONE party row. If identifiers are supplied, each mints a GlobalIdentifier via HMAC
(INV-10) - and the dedup unique index is honoured: an identifier already bound (to ANY party in the
tenant) is a Conflict carrying the existing party id (the caller routes that to party.merge).
Input
| Field | Type | Required | Description |
|---|---|---|---|
kind | string | yes | |
displayName | string | yes |
Call POST /v/party.create with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.create.
Setparty.facet.set
| Property | Value |
|---|---|
| Capability | party.facet.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
{ partyId, kyc: { kycStatus?, riskLevel?, pepFlag?, sanctionsMatch?, nationality?, passportNumber?, dateOfBirth?, … } }. SENSITIVE/L2/ADMIN.
Upserts the party_kyc 1:1 facet. Every PII field is AES-256-GCM-before-write via the SHARED
crate::crypto AEAD (INV-7 - not a new cipher), AAD-bound to (workspace, party, field) so a blob
moved between rows fails to authenticate. The plaintext PII NEVER reaches a column - only the
ciphertext *_enc bytea + the explicit encryption_scheme.
Call POST /v/party.facet.set with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.facet.set.
Getparty.get
| Property | Value |
|---|---|
| Capability | party.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - drives an inline chat artefact and a GUI panel |
Returns the core, the (in+out) relationship edges, the count of bound identifiers (NEVER the hashes - they are irreversible and not display data, INV-10), and the KYC STANDING (status/risk/flags only - the encrypted PII columns are NOT decrypted into a read, INV-7).
Input
| Field | Type | Required | Description |
|---|---|---|---|
partyId | string | yes |
Call POST /v/party.get with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.get.
Reviewparty.kyc.review
| Property | Value |
|---|---|
| Capability | party.facet.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Legal basis | CC-OPBOX 27 |
/Sensitive/ADMIN ([2026] CC-OPBOX 27 / LEXBY-FI 5). The PARTIAL KYC re-verify:
rewrites ONLY the supplied STANDING fields on an EXISTING party_kyc row, sets kyc_verified_at, and DERIVES
kyc_expires_at (= verified_at + the validity period) on a VERIFIED transition - while PRESERVING every
unsupplied column, CRITICALLY every *_enc PII facet (they are NOT in the SET clause, so byte-for-byte
untouched - the court-ordered tested invariant). Distinct from party.facet.set (the full-PII (re)load,
which deliberately clears stale facets); routing through fact.set is forbidden (the single-value fact
store cannot hold party_kyc’s nine AAD-bound encrypted columns). Requires an existing standing (re-verify,
not create - use party.facet.set to load first). A VERIFIED write never lands without a kyc_expires_at.
Call POST /v/party.kyc.review with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.kyc.review.
Listparty.list
| Property | Value |
|---|---|
| Capability | party.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - drives an inline chat artefact and a GUI panel |
List parties in the workspace (filter kind/status), paginated +
RLS-scoped. Returns the slim standing (id/kind/displayName/status), NOT the unredacted facets (those
stay behind party.get, INV-7). Computes nothing authoritative (INV-14).
Input
| Field | Type | Required | Description |
|---|
Call POST /v/party.list with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.list.
Mergeparty.merge
| Property | Value |
|---|---|
| Capability | party.merge |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
The dedup-fold: source is folded
INTO target (source → status MERGED, merged_into_id = target). The merge is GATED on confidence
against MERGE_CONFIDENCE_FLOOR (INV-10):
• confidence ≥ floor → MERGE (re-point source’s identifiers + relationships to target, fold source).
• confidence < floor → NO merge; open a Review IDENTITY-lane item (the human decides). Returns
{ merged:false, reviewId } - the substrate NEVER auto-merges below the floor.
The match key is the HMAC GlobalIdentifier (no plaintext); merging re-binds source’s identifiers to
target (a re-bind that would collide on target’s own identity is the index’s job to reject).
Input { sourcePartyId, targetPartyId, confidence } (fields ending in ? are optional)
Call POST /v/party.merge with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.
Profileparty.profile
| Property | Value |
|---|---|
| Capability | party.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - drives an inline chat artefact and a GUI panel |
The compliance-officer STANDING view of one party: the core, a SUMMARY
of its relationship edges (counts by status, never the full graph - that stays behind party.get/
link.query), and the KYC STANDING (status/risk/flags/dates/nationality ONLY). NO decrypted PII ever
crosses this read (INV-7): the *_enc ciphertext columns are NOT selected, and the identifier hashes
are NOT returned (INV-10, a hash is not display data) - only a count of bound identities. Workspace-
scoped via the explicit workspace_id = $ predicate + RLS; a party in another workspace reads back
not-found (anti-enumeration, the same shape party.get uses).
Input { partyId } (fields ending in ? are optional)
Call POST /v/party.profile with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.profile.
Reactivateparty.reactivate
| Property | Value |
|---|---|
| Capability | party.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
SUSPENDED -> ACTIVE (+reason). Refuses ARCHIVED (terminal).
Call POST /v/party.reactivate with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.
Listparty.relationship.list
| Property | Value |
|---|---|
| Capability | party.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - drives an inline chat artefact and a GUI panel |
The workspace’s party relationships ([2026] CC-OPBOX 24 gate
(b)): the party_relationship rows the link writers mint (directors/shareholders/UBOs/secretaries with
role, share %, appointed/resigned, nominee) but which NO read verb surfaced - the company-registers read.
Slim standing; numeric/date cast to text so no decimal/date dep. { role?, status?, partyId? },
paginated, RLS-scoped (workspace-global, exactly like party.list - parties carry no per-row ACL). No
new store/write/state (the lawful gate-(b) substrate-extension; extends the existing party.* namespace).
Call POST /v/party.relationship.list with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.relationship.list.
Suspendparty.suspend
| Property | Value |
|---|---|
| Capability | party.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
ACTIVE -> SUSPENDED (+reason). Reversible via reactivate.
Call POST /v/party.suspend with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.
Updateparty.update
| Property | Value |
|---|---|
| Capability | party.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
Edit a party’s core fields (currently displayName). One write
path (INV-1). An empty update is rejected (no silent touch); a missing party is a not-found BadInput.
Call POST /v/party.update with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.update.