SPEC-118 v0.1 — prism_persona_ratify
Status: draft for Candi governance gateAuthor: Texi, architecture
Created: 2026-05-14
Owner: Texi architecture; Candi governance gate; Donna engineering implementation
Related: SPEC-105, SPEC-106, SPEC-107, SPEC-111, task #57, postmortem
904392ee, signal 7b7f6081
1. Purpose
Prism needs a named, audited path that reconciles an existing runtime persona row against its committed.agent/personas/<identity>.json contract.
Today, implicit-bootstrap personas can become real behavioral participants before
their registry row reflects the committed contract. prism_persona_create is
create-only and fails for an existing identity. prism_start intentionally does
not read committed persona files. SPEC-106’s prism_persona_promote exists as a
backend service and /personas/promote HTTP route, but it is not exposed as an
MCP verb and is flip-only: it records a path and flips implicit_bootstrap=false
without validating/reconciling the committed contract, binding row, description,
authority fields, or stale-registry state.
prism_persona_ratify closes that gap.
2. Decision
Add a dedicatedprism_persona_ratify verb.
Do not make prism_persona_create an upsert. Do not make prism_start
mutate persona registry state from filesystem contracts. Ratification is a
governance state transition, so it needs its own verb, preconditions, audit
event, idempotency semantics, and fail-closed drift behavior.
3. Definitions
Committed persona contract: The JSON file at.agent/personas/<identity>.json, committed to the project repository and
validating against .agent/schemas/persona.schema.json.
Binding row: The project binding at
.agent/projects/<pid>/bindings.json that maps identity to a role,
launch, and persona_ref.
Registry row: The backend personas table row for the PID-scoped persona.
The authoritative join key is persona_id, not the mutable identity string.
Contract-active, registry-stale state: A persona may have a committed
contract with behavioral authority already in force while the backend registry
still shows stale metadata. Status surfaces MUST represent this explicitly as:
prism_persona_ratify succeeds.
4. Non-Goals
prism_persona_ratify does not:
- create a new persona row
- create a new binding row
- mint authority from an uncommitted file
- ratify governance content without recorded approval evidence
- auto-run during
prism_start - change launch mode outside the committed binding row
- bypass Donna/Candi/Texi/Andora/Samantha gates
- deploy, launch, or restart the persona session
5. Verb Contract
5.1 MCP Verb
pidcaller_identityauthorization_basisapproval_evidence_refsratified_byreason- either
persona_idoridentity
contract_pathdefaults to.agent/personas/<lowercase identity>.jsonbinding_pathdefaults to.agent/projects/<pid>/bindings.jsondry_rundefaults totrue
dry_run=false plus a confirmation token or an
equivalent authorized operator/session gate. The audit MUST distinguish the
actor invoking the mutation (caller_identity, caller_session) from the
authority that approved it (authorization_basis, approval_evidence_refs,
ratified_by).
5.2 Response
6. Validation
Ratification MUST fail closed unless all checks pass.6.1 Registry Target
Resolve the target bypersona_id when provided. identity is only a fallback
lookup path and MUST resolve within the requested pid.
Validation:
- registry row exists
- row
tenant_idmatches caller tenant - row
pidmatches inputpid - row is not archived or deduped away
- if both
persona_idandidentityare provided, both identify the same row
6.2 Contract File
The first-slice trust path is MCP-shim supplied content:- The MCP shim runs on the editor host and therefore has access to the working
tree containing
.agent/personas/*.jsonand.agent/projects/<pid>/bindings.json. - The shim reads the contract and binding files, canonicalizes their JSON,
computes SHA-256 hashes, verifies the files are committed and the relevant
paths are clean at
source_commit, then sends content + hashes + commit to the backend. - The backend re-validates JSON schema, recomputes hashes from the supplied
content, verifies
expected_*inputs, and persists only the backend-validated canonical content/hash metadata.
contract_source_unverified.
Validation:
- contract file exists at
contract_path - file is valid JSON
- file validates against
.agent/schemas/persona.schema.json pidequals requestedpidpersona_idequals registry row ididentity_binding.registry_identityequals registry identity unless an explicit rename operation is in flightidentity_binding.dedupe_ofis null or points to a valid same-PID canonical persona per SPEC-106identity_binding.implicit_bootstrapisfalse
6.3 Binding File
Validation:- binding file exists at
binding_path - binding JSON validates against
.agent/schemas/bindings.schema.json - exactly one row matches the persona identity
persona_refequals contractpersona_id- binding
rolematchesrole:<contract.canonical_role> - binding
launchmatchescontract.identity_binding.launch_mode - no other binding row in the PID points to the same
persona_ref
6.4 Authorization
prism_persona_ratify is restricted to governance/persona authorities:
- operator
- Candi, when acting within governance/persona-contract scope
- Donna, when implementing an accepted Candi/Texi contract
- an explicit future admin role with persona-ratify permission
- Accepted contract path: governance and architecture gates have accepted the committed contract shape. Example: Candi governance gate plus Texi architecture ownership, recorded by PR, signal, SPEC status, or audit ref.
- Explicit operator authorization / override: Frank/operator explicitly
authorizes ratification. Operator approval may satisfy the governance approval
requirement, but the override MUST be recorded in
approval_evidence_refs.
6.5 Hash and Commit Checks
The verb computes a canonical contract hash from the committed contract content. Ifexpected_contract_hash is provided and does not match, return
contract_hash_mismatch.
If expected_commit is provided and the contract/binding are not read from that
commit or a descendant accepted by policy, return contract_commit_mismatch.
7. Effects
Ratification reconciles the registry to already-committed authority. It does not mint new scope. Ondry_run=false, the operation updates the registry row from the committed
contract and binding:
implicit_bootstrap=false- registry identity/display label, unless rename is separately governed
- canonical role
- specialization
- assignment
- surface preference
- capabilities
- launch mode
- active/archive status fields
- description/display summary
- persona contract/schema versions
- persona path
- binding path
- contract hash
- source commit
- ratified_at
- ratified_by
- ratification audit id
registry_ratification_stale=false
7.1 Registry Storage
Implement SPEC-118 with an additive migration. The currentpersonas table does
not have enough columns to represent the ratified contract. Add:
canonical_role TEXT NULLassignment TEXT NULLsurface_preference TEXT NULLcapabilities JSONB NOT NULL DEFAULT '[]'::jsonblaunch_mode TEXT NULLcontract_path TEXT NULLbinding_path TEXT NULLcontract_hash TEXT NULLbinding_hash TEXT NULLsource_commit TEXT NULLcontract_snapshot JSONB NULLbinding_snapshot JSONB NULLratified_at TIMESTAMPTZ NULLratified_by TEXT NULLauthorization_basis TEXT NULLapproval_evidence_refs JSONB NOT NULL DEFAULT '[]'::jsonbregistry_ratification_stale BOOLEAN NOT NULL DEFAULT false
description, surface_hint, specialization, implicit_bootstrap,
persona_class, and existing status/archive fields where they already exist.
contract_snapshot stores the canonical validated persona JSON used for
ratification so future drift checks have a stable comparison even if the
repository branch moves.
Authority-bearing fields are synchronized from the committed contract only after
either an accepted Candi/Texi contract path or explicit operator
authorization has been recorded. The verb is not an authority authoring path; it
is a registry reconciliation path.
Description reconciliation is in scope. Misleading persona-row descriptions are
the same class of stale registry state as implicit_bootstrap; they MUST NOT
require undocumented manual backend repair.
8. Idempotency and Drift
If the registry already matches the same contract hash, binding hash, and source commit,prism_persona_ratify returns success with idempotent_noop=true.
If the row is explicit but differs from the committed contract, the verb fails
closed with contract_drift and returns a diff. It MUST NOT silently overwrite
an explicit registry row whose current state appears to come from a different
contract.
If the binding is missing, points to another persona, or disagrees with the
contract role/launch, fail with binding_mismatch.
If identity/ref/hash/commit disagree, fail with a specific mismatch code.
9. Audit
Every live ratification writes an append-only audit event. Audit payload:personas.rename_history with
kind="ratify" and the payload above. The name is legacy, but the column exists
today and already acts as the persona append-only history surface. A future
schema may split persona_audit_events out; that must preserve these entries.
operator_id is not a generic caller field. It may appear inside
approval_evidence_refs or an approval-evidence object when the operator is the
authority basis, but the mutation actor still records as caller_identity /
caller_session.
10. Status Surfaces
Status surfaces MUST distinguish contract state from registry state. Examples:prism_whois. Boot packets and dashboard
contract-state views SHOULD follow once the registry fields exist. Until those
surfaces land, the verb response is the minimum required status surface for the
reconciliation state.
11. Relationship to Existing Verbs
prism_persona_create
Remains create-only. If a matching row exists, it returns an existing-identity
error or a pointer to prism_persona_ratify; it MUST NOT upsert.
prism_start
Remains bootstrap/session lifecycle. It may report stale registry state, but it
MUST NOT reconcile persona registry rows from files.
prism_persona_promote
prism_persona_promote exists today as a backend service and HTTP route, not as
an MCP verb. It is a flip-only path and is superseded for committed-contract
reconciliation. It may remain as a compatibility alias only if it calls the
ratify service with the same validation and audit rules. A flip-only
implementation is insufficient for SPEC-118 acceptance.
Donna may use the existing promote endpoint as an explicitly interim operational
repair for a single persona row before SPEC-118 ships, but that must be labeled
interim-only and does not satisfy SPEC-118 ratification.
.agent/scripts/validate.py
The validator remains a read-only file/config check. It MAY gain a
ratify-ready mode that emits the exact prism_persona_ratify input payload,
but it MUST NOT mutate backend state.
12. Carla Walkthrough
- Carla is runtime registered as a Cursor agent and has committed file
.agent/personas/carla.json. .agent/projects/PID-PGR01/bindings.jsoncontains:
- Until ratification, status surfaces may show:
contract_active=true,registry_ratification_stale=true. - Authorized caller runs dry run:
- Dry run returns exact changes.
- Authorized caller runs live ratification with confirmation.
- Registry row now reflects Carla’s committed coordination /
cursor_hitlcontract,implicit_bootstrap=false, and stale flag false. - Donna signals Carla to re-bootstrap so the runtime session observes the ratified registry state.
13. Acceptance Criteria
prism_persona_ratifyexists as a named backend service and MCP verb, or as a backend service plus admin command in the first slice with the same semantics.prism_persona_createremains create-only.prism_startdoes not mutate persona registry state from files.- Dry run returns a full diff and writes no registry changes.
- Live ratification requires authorized caller plus confirmation and either an accepted Candi/Texi contract path or explicit operator authorization / override recorded as approval evidence.
- Ratification validates registry row, contract file, binding row, hash, and commit/source evidence.
- Same-hash rerun is an idempotent no-op.
- Identity/persona_ref/hash/binding mismatches fail closed with specific error codes.
- Registry metadata, description/display, authority fields, and
implicit_bootstrapreconcile to the committed contract and binding. - Status surfaces can represent
contract_active=trueplusregistry_ratification_stale=trueuntil reconciliation succeeds. - A live ratification writes an append-only audit event with changed fields,
source refs, caller identity/session, authorization basis,
approval-evidence refs, and
ratified_by. - Carla’s
persona_ref=45c51226-f316-42ce-9957-a4aa5e52c0eacan be ratified without creating a parallel persona row. - Description/display reconciliation is implemented by
prism_persona_ratifyor by an explicitly named sibling path; it is not left as manual backend repair. - First slice exposes stale/ratified state in the verb response and targets
prism_whoisas the first follow-on status surface. - First slice uses the MCP-shim trust path: shim reads committed clean files, passes canonical content/hash/commit, and backend revalidates schema/hash before mutating.
- Additive migration persists contract/binding hash, source commit, snapshots, ratified metadata, approval evidence, and registry stale state.
- v0.1 audit entries are appended to
personas.rename_historywithkind="ratify"unless a dedicated persona-audit table lands in the same slice.
14. Open Implementation Notes
- GitHub blob fetch by SHA is a future hardening option for Cloud/server-side ratification, but the v0.1 implementation target is MCP-shim supplied content/hash/commit because the backend container has no editor-host checkout.
- If the current description/display field is separate from persona contract metadata, reconcile it in this verb or define the sibling update path in the implementation plan before shipping.
- After all active rows are explicit and every binding has
persona_ref, the validator warning for missing persona refs can escalate to error per SPEC-106.

