A Persona is a declared identity construct. It establishes a named participant role that may be referenced in operation allowed_personas sets, flow step persona fields, handoff steps, compensation steps, and escalation targets. Personas are the authority namespace of the contract -- they define who can act.
DSL Syntax
persona <PersonaId>A persona declaration is a single line. There are no attributes, no metadata, no delegation, no hierarchies, and no semantic content beyond identity.
persona buyer
persona seller
persona escrow_agent
persona compliance_officerPersonaId is a non-empty UTF-8 string, unique within the contract. The set of all declared personas is finite, fixed at contract definition time, and statically enumerable.
Personas Are Roles, Not Users
A persona represents a role -- warehouse_manager, compliance_officer, billing_system -- not an individual user. If Alice and Bob are both warehouse managers, they share the warehouse_manager persona.
The mapping from concrete identities to personas happens outside the contract, in the executor's identity layer. This separation is deliberate:
- The contract declares authority boundaries and reasons about what roles can do.
- The executor handles identity-to-persona mapping at runtime.
This is an executor concern, governed by obligations E15 and E16. The contract never sees user ids, API keys, or session tokens.
Usage in Operations
Personas appear in the allowed_personas field of operations, controlling who can invoke each operation:
operation release_escrow {
personas: [escrow_agent]
require: verdict_present(can_auto_release)
effects: [EscrowAccount: held -> released]
outcomes: [released]
}
operation release_escrow_with_compliance {
personas: [compliance_officer]
require: verdict_present(delivery_ok)
effects: [EscrowAccount: held -> released]
outcomes: [released]
}When an operation is invoked, the first step in the execution sequence is persona check: persona in op.allowed_personas as a simple set membership test.
Usage in Flows
Personas appear throughout flow steps:
flow escrow_release {
snapshot: at_initiation
entry: step_confirm
steps: {
// OperationStep -- persona executes the operation
step_confirm: OperationStep {
op: confirm_delivery
persona: seller
outcomes: { confirmed: step_check }
on_failure: Terminate(outcome: failure)
}
// BranchStep -- persona evaluates the condition
step_check: BranchStep {
condition: verdict_present(within_threshold)
persona: escrow_agent
if_true: step_release
if_false: step_handoff
}
// HandoffStep -- authority transfers between personas
step_handoff: HandoffStep {
from_persona: escrow_agent
to_persona: compliance_officer
next: step_compliance_release
}
step_release: OperationStep {
op: release_escrow
persona: escrow_agent
outcomes: { released: Terminal(success) }
on_failure: Terminate(outcome: failure)
}
step_compliance_release: OperationStep {
op: release_escrow_with_compliance
persona: compliance_officer
outcomes: { released: Terminal(success) }
on_failure: Terminate(outcome: failure)
}
}
}Personas also appear in:
- CompensationStep
persona-- who executes the compensation operation - Escalate
to_persona-- who receives the escalation - SubFlowStep
persona-- who initiates the sub-flow
Authority Topology (S4)
Static analysis S4 derives the complete authority topology from the contract. For any declared persona P and entity state S, the set of operations P can invoke in S is statically derivable. Whether a persona can cause a transition from S to S' is answerable without executing anything.
Consider a contract with four personas:
persona customs_officer
persona quality_inspector
persona port_authority
persona shipping_agentAnd three operations affecting Shipment:
| Operation | allowed_personas | Shipment effect |
|---|---|---|
begin_inspection | [customs_officer] | arrived -> inspecting |
release_shipment | [port_authority] | inspecting -> cleared |
hold_shipment | [customs_officer] | inspecting -> held |
The proof that customs_officer cannot release a shipment is visible in the contract text: release_shipment lists [port_authority], not customs_officer. No runtime check needed. No log analysis. The authority topology is a structural fact.
Running tenor check produces:
Authority (S4): 4 personas, 8 authority entriesUnreferenced Personas
A declared persona that is never referenced in any operation or flow is not an error. The elaborator does not reject unused personas. Static analysis tooling may optionally warn about them, but this is advisory, not normative.
This is useful when:
- A persona is defined for a system composition that uses it in shared_personas across contracts.
- A persona is declared for future use as the contract evolves.
Full Working Example
A complete contract demonstrating persona authority boundaries:
// --- Personas ---
persona requestor
persona department_head
persona finance_controller
persona procurement_admin
// --- Entity ---
entity PurchaseOrder {
states: [draft, submitted, dept_approved, finance_approved, rejected, fulfilled]
initial: draft
transitions: [
(draft, submitted),
(submitted, dept_approved),
(submitted, rejected),
(dept_approved, finance_approved),
(dept_approved, rejected),
(finance_approved, fulfilled)
]
}
// --- Facts ---
fact order_amount {
type: Money(currency: "USD")
source: "procurement_service.order_amount"
}
fact department_budget_available {
type: Bool
source: "budget_service.dept_has_funds"
default: false
}
// --- Rules ---
rule budget_check {
stratum: 0
when: department_budget_available = true
produce: verdict budget_ok { payload: Bool = true }
}
// --- Operations ---
// requestor can ONLY submit drafts
operation submit_order {
personas: [requestor]
require: order_amount > 0
effects: [PurchaseOrder: draft -> submitted]
outcomes: [submitted]
}
// department_head approves or rejects submitted orders
operation dept_approve {
personas: [department_head]
require: verdict_present(budget_ok)
effects: [PurchaseOrder: submitted -> dept_approved]
outcomes: [approved]
}
// finance_controller approves or rejects dept-approved orders
operation finance_approve {
personas: [finance_controller]
require: verdict_present(budget_ok)
effects: [PurchaseOrder: dept_approved -> finance_approved]
outcomes: [approved]
}
// both department_head and finance_controller can reject
operation reject_order {
personas: [department_head, finance_controller]
require: true
effects: [
PurchaseOrder: submitted -> rejected,
PurchaseOrder: dept_approved -> rejected
]
outcomes: [rejected]
}
// procurement_admin fulfills approved orders
operation fulfill_order {
personas: [procurement_admin]
require: true
effects: [PurchaseOrder: finance_approved -> fulfilled]
outcomes: [fulfilled]
}Authority topology for this contract:
| Persona | Can invoke | Cannot invoke |
|---|---|---|
requestor | submit_order | Everything else |
department_head | dept_approve, reject_order | submit_order, finance_approve, fulfill_order |
finance_controller | finance_approve, reject_order | submit_order, dept_approve, fulfill_order |
procurement_admin | fulfill_order | Everything else |
No persona can bypass the approval chain. The requestor cannot approve their own order. The procurement_admin cannot approve or reject. These are structural facts, provable by tenor check.
Constraints
- Persona identifiers are unique within a contract. Duplicates are elaboration errors.
- Persona ids occupy a distinct namespace from other construct kinds. A Persona named
Foodoes not conflict with an Entity namedFoo. - Every persona reference in an operation
allowed_personasset must resolve to a declared Persona. Unresolved references are Pass 5 errors:"undeclared persona '<id>'". - Every persona reference in flow steps (
OperationStep,BranchStep,SubFlowSteppersona fields),HandoffStepfrom_persona/to_persona,CompensationSteppersona, andEscalateto_personamust resolve to a declared Persona. - Unreferenced persona declarations are NOT errors. Tooling may optionally warn.
Common Mistakes
Creating a persona for every user. Personas represent roles, not individuals. If Alice and Bob are both warehouse managers, they share warehouse_manager. The executor maps identities to personas.
Undeclared persona references. If an operation references logistics_admin but no persona logistics_admin exists, elaboration fails. Declare all personas explicitly.
Confusing personas with hierarchies. Personas have no inheritance. A senior_manager persona does not automatically inherit the authority of manager. If both should approve orders, list both in allowed_personas.
Missing handoffs in flows. If a flow requires two different personas at different steps, there must be a HandoffStep between them. A flow cannot silently assume authority it was not granted.
How Personas Connect to Other Constructs
- Operations reference personas in
allowed_personas. The persona check is the first step of the execution sequence. - Flows reference personas at every step: who executes an operation, who evaluates a branch, who hands off to whom.
- Systems can share personas across contracts via
shared_personas. A shared persona creates identity equivalence: the same role has authority in multiple contracts. - Rules do NOT reference personas. Verdict production is persona-independent.
- Facts do NOT reference personas. Fact values are identity-neutral.
Static Analysis Properties
- S4 (Authority topology) is the primary analysis consuming personas. It produces a complete map of (persona, entity state) -> available operations.
- The complete set of declared persona identifiers is statically known from the contract (enumerable at Pass 2).
- Authority boundaries are derivable without execution, enabling automated security auditing.