Skip to main content

SPEC-118 v0.1 — prism_persona_ratify

Status: draft for Candi governance gate
Author: 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 dedicated prism_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:
{
  "contract_active": true,
  "registry_ratification_stale": true
}
until 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

prism_persona_ratify
Inputs:
{
  "pid": "PID-PGR01",
  "identity": "Carla",
  "persona_id": "45c51226-f316-42ce-9957-a4aa5e52c0ea",
  "contract_path": ".agent/personas/carla.json",
  "binding_path": ".agent/projects/PID-PGR01/bindings.json",
  "expected_contract_hash": "sha256:...",
  "expected_commit": "afdbd40...",
  "caller_identity": "Donna",
  "caller_session": "271a96a5-d494-48f5-bbf6-dca13e810720",
  "authorization_basis": "accepted_contract",
  "approval_evidence_refs": [
    "signal:7cd334fe-4816-477c-ad92-b11ffea9f7b5",
    "pr:400"
  ],
  "ratified_by": "Donna",
  "reason": "Ratify committed Carla Cursor HITL contract",
  "dry_run": true,
  "confirmation_token": null
}
Required:
  • pid
  • caller_identity
  • authorization_basis
  • approval_evidence_refs
  • ratified_by
  • reason
  • either persona_id or identity
Defaults:
  • contract_path defaults to .agent/personas/<lowercase identity>.json
  • binding_path defaults to .agent/projects/<pid>/bindings.json
  • dry_run defaults to true
Live mutation requires 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

{
  "dry_run": true,
  "pid": "PID-PGR01",
  "persona_id": "45c51226-f316-42ce-9957-a4aa5e52c0ea",
  "identity": "Carla",
  "contract_path": ".agent/personas/carla.json",
  "binding_path": ".agent/projects/PID-PGR01/bindings.json",
  "contract_hash": "sha256:...",
  "source_commit": "afdbd40...",
  "registry_ratification_stale": true,
  "idempotent_noop": false,
  "changes": {
    "implicit_bootstrap": {"from": true, "to": false},
    "canonical_role": {"from": "project_persona", "to": "coordination"},
    "launch_mode": {"from": "triggered", "to": "triggered"},
    "description": {"from": "old text", "to": "Cursor HITL Interface"}
  },
  "warnings": [],
  "audit_event_id": "uuid-or-null-for-dry-run"
}
The response MUST expose all planned changes during dry run. Live response MUST include the final postcondition state and audit id.

6. Validation

Ratification MUST fail closed unless all checks pass.

6.1 Registry Target

Resolve the target by persona_id when provided. identity is only a fallback lookup path and MUST resolve within the requested pid. Validation:
  • registry row exists
  • row tenant_id matches caller tenant
  • row pid matches input pid
  • row is not archived or deduped away
  • if both persona_id and identity are provided, both identify the same row

6.2 Contract File

The first-slice trust path is MCP-shim supplied content:
  1. The MCP shim runs on the editor host and therefore has access to the working tree containing .agent/personas/*.json and .agent/projects/<pid>/bindings.json.
  2. 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.
  3. 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.
Backend GitHub blob fetch by SHA is an allowed future hardening path, not the v0.1 default. A backend that cannot verify supplied content/hash/commit MUST return contract_source_unverified. Validation:
  • contract file exists at contract_path
  • file is valid JSON
  • file validates against .agent/schemas/persona.schema.json
  • pid equals requested pid
  • persona_id equals registry row id
  • identity_binding.registry_identity equals registry identity unless an explicit rename operation is in flight
  • identity_binding.dedupe_of is null or points to a valid same-PID canonical persona per SPEC-106
  • identity_binding.implicit_bootstrap is false
The backend must treat file paths as audit labels, not proof. Content, canonical hash, and commit/source evidence are the proof inputs.

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_ref equals contract persona_id
  • binding role matches role:<contract.canonical_role>
  • binding launch matches contract.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
Ordinary agents cannot ratify themselves. A persona MAY request ratification, but the mutation requires an authorized caller. Live ratification requires one of two approval paths:
  1. 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.
  2. 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.
This preserves Humans Steer / operator authority while preventing non-operator agents from bypassing Candi/Texi gates by relaying an unverifiable authority claim.

6.5 Hash and Commit Checks

The verb computes a canonical contract hash from the committed contract content. If expected_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. On dry_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 current personas table does not have enough columns to represent the ratified contract. Add:
  • canonical_role TEXT NULL
  • assignment TEXT NULL
  • surface_preference TEXT NULL
  • capabilities JSONB NOT NULL DEFAULT '[]'::jsonb
  • launch_mode TEXT NULL
  • contract_path TEXT NULL
  • binding_path TEXT NULL
  • contract_hash TEXT NULL
  • binding_hash TEXT NULL
  • source_commit TEXT NULL
  • contract_snapshot JSONB NULL
  • binding_snapshot JSONB NULL
  • ratified_at TIMESTAMPTZ NULL
  • ratified_by TEXT NULL
  • authorization_basis TEXT NULL
  • approval_evidence_refs JSONB NOT NULL DEFAULT '[]'::jsonb
  • registry_ratification_stale BOOLEAN NOT NULL DEFAULT false
Use 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:
{
  "audit_kind": "persona_ratify",
  "pid": "PID-PGR01",
  "persona_id": "45c51226-f316-42ce-9957-a4aa5e52c0ea",
  "identity": "Carla",
  "caller_identity": "Donna",
  "caller_session": "271a96a5-d494-48f5-bbf6-dca13e810720",
  "authorization_basis": "accepted_contract",
  "approval_evidence_refs": [
    "signal:7cd334fe-4816-477c-ad92-b11ffea9f7b5",
    "pr:400"
  ],
  "ratified_by": "Donna",
  "reason": "Ratify committed Carla Cursor HITL contract",
  "contract_path": ".agent/personas/carla.json",
  "binding_path": ".agent/projects/PID-PGR01/bindings.json",
  "contract_hash": "sha256:...",
  "binding_hash": "sha256:...",
  "source_commit": "afdbd40...",
  "changed_fields": ["implicit_bootstrap", "canonical_role", "description"],
  "previous_state_ref": "hash-or-json-pointer",
  "new_state_ref": "hash-or-json-pointer"
}
Until Prism has a dedicated persona-audit table, the implementation may append this event to the persona history/audit structure already used by SPEC-106, but it must remain queryable by persona id and audit kind. For v0.1, append the ratification audit entry to 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:
{
  "identity": "Carla",
  "persona_id": "45c51226-f316-42ce-9957-a4aa5e52c0ea",
  "contract_active": true,
  "registry_ratification_stale": true,
  "implicit_bootstrap": true,
  "contract_hash": "sha256:...",
  "registry_contract_hash": null,
  "stale_fields": ["implicit_bootstrap", "description"]
}
After ratification:
{
  "identity": "Carla",
  "persona_id": "45c51226-f316-42ce-9957-a4aa5e52c0ea",
  "contract_active": true,
  "registry_ratification_stale": false,
  "implicit_bootstrap": false,
  "contract_hash": "sha256:...",
  "registry_contract_hash": "sha256:..."
}
First-slice implementation MUST expose these fields in the verb dry-run/live response and SHOULD expose them in 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

  1. Carla is runtime registered as a Cursor agent and has committed file .agent/personas/carla.json.
  2. .agent/projects/PID-PGR01/bindings.json contains:
{
  "identity": "Carla",
  "role": "role:coordination",
  "launch": "triggered",
  "persona_ref": "45c51226-f316-42ce-9957-a4aa5e52c0ea"
}
  1. Until ratification, status surfaces may show: contract_active=true, registry_ratification_stale=true.
  2. Authorized caller runs dry run:
prism_persona_ratify(
  pid="PID-PGR01",
  persona_id="45c51226-f316-42ce-9957-a4aa5e52c0ea",
  contract_path=".agent/personas/carla.json",
  reason="Ratify Carla Cursor HITL contract",
  dry_run=true
)
  1. Dry run returns exact changes.
  2. Authorized caller runs live ratification with confirmation.
  3. Registry row now reflects Carla’s committed coordination / cursor_hitl contract, implicit_bootstrap=false, and stale flag false.
  4. Donna signals Carla to re-bootstrap so the runtime session observes the ratified registry state.

13. Acceptance Criteria

  1. prism_persona_ratify exists as a named backend service and MCP verb, or as a backend service plus admin command in the first slice with the same semantics.
  2. prism_persona_create remains create-only.
  3. prism_start does not mutate persona registry state from files.
  4. Dry run returns a full diff and writes no registry changes.
  5. Live ratification requires authorized caller plus confirmation and either an accepted Candi/Texi contract path or explicit operator authorization / override recorded as approval evidence.
  6. Ratification validates registry row, contract file, binding row, hash, and commit/source evidence.
  7. Same-hash rerun is an idempotent no-op.
  8. Identity/persona_ref/hash/binding mismatches fail closed with specific error codes.
  9. Registry metadata, description/display, authority fields, and implicit_bootstrap reconcile to the committed contract and binding.
  10. Status surfaces can represent contract_active=true plus registry_ratification_stale=true until reconciliation succeeds.
  11. 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.
  12. Carla’s persona_ref=45c51226-f316-42ce-9957-a4aa5e52c0ea can be ratified without creating a parallel persona row.
  13. Description/display reconciliation is implemented by prism_persona_ratify or by an explicitly named sibling path; it is not left as manual backend repair.
  14. First slice exposes stale/ratified state in the verb response and targets prism_whois as the first follow-on status surface.
  15. 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.
  16. Additive migration persists contract/binding hash, source commit, snapshots, ratified metadata, approval evidence, and registry stale state.
  17. v0.1 audit entries are appended to personas.rename_history with kind="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.
Last modified on May 18, 2026