opbox

The identity.* family (3 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.

Createidentity.create

PropertyValue
Capabilityidentity.write
Risk classSensitive - autonomy L2
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

(Sensitive)/MEMBER. The canonical mint path closing the DEC-15 gap (the frontend’s prisma.globalIdentifier.upsert is retired onto this). HMAC-SHA256s the NORMALISED value under the versioned secret and binds the irreversible hash to partyId (INV-10, no plaintext column). FAIL-CLOSED: the anchor party must resolve in ctx.workspace_id (else BadInput). On the dedup unique (type, hash, key_version) clash it does NOT auto-merge (INV-10): it RETURNS the EXISTING party id (dedupPartyId) as a Conflict so the caller routes to party.merge - the “DEC-21 identity.create” for-merge-routing contract. Emits ONE INV-8 audit event (no hash, no plaintext - PII single-home, C1).

Input { partyId, type, value } (fields ending in ? are optional)

Call POST /v/identity.create with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool identity.create.

Rehashidentity.rehash

PropertyValue
Capabilityidentity.rehash
Risk classWrite - autonomy L1
Min. permissionOwner
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

The at-rest secret-rotation operator verb the key_version column exists for (INV-10). The plaintext identifier was discarded at mint (there is by-construction no plaintext column), so this CANNOT re-derive the hash from the original value; it deterministically re-MACs the STORED hash bytes under the new versioned key and stamps key_version = toVersion. The rotation is DETERMINISTIC per (oldHash, type, toVersion), so the dedup unique index over (type, hash, key_version) STILL collapses two identities that shared a hash at the old version; and it is forward-only / OWNER-gated (toVersion must be strictly greater than the rows’ current version, so you rotate UP and never silently downgrade the at-rest secret; OWNER is the tier, a rotation re-keys the whole identity facet of the tenant). Scope: all rows of the workspace’s parties (the cross-ws-within-tenant dedup facet is RLS-scoped via the owning party), optionally narrowed to one partyId. RLS + the explicit party-workspace join keep it tenant-bound; a partyId in another workspace rehashes nothing (0 rows). Returns the count rotated.

Input { toVersion, partyId? } (fields ending in ? are optional)

Call POST /v/identity.rehash with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool identity.rehash.

Updateidentity.update

PropertyValue
Capabilityidentity.write
Risk classSensitive - autonomy L2
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

(Sensitive)/MEMBER. The “DEC-21 identity.update”: a one-row update of an EXISTING global_identifier’s verification standing (verified + the derived verified_at). It NEVER re-hashes (the at-rest secret rotation is identity.rehash’s job, party.rs), NEVER moves the party anchor, and NEVER touches identifier_hash/identifier_type/key_version (the dedup identity is immutable here - a different identity is a new identity.create, not a mutation). verified=true stamps verified_at = now(); verified=false clears it (verification withdrawn). FAIL-CLOSED: the row is resolved through its owning party’s workspace (the facet has no own workspace_id, 0019), so a gid whose party is in another workspace updates 0 rows -> not-found BadInput. Emits ONE INV-8 audit event (no hash, no plaintext).

Input { globalIdentifierId, verified } (fields ending in ? are optional)

Call POST /v/identity.update with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool identity.update.