WACP: User
WACP: User
Metadata
title: "WACP: User"
id: wacp-spec-user
type: constituent-spec
tier: abstract
category: primitives
status: complete
created: 2026-02-24
lineage: PROTOCOL.md (wacp-v0.1)
protocol_sections:
- §4.8 (user identity)
depends_on:
- wacp-spec-identity (identifier rules, uniqueness scopes)
- wacp-spec-workspace (state model — user states mirror workspace states; originator tree)
authors:
- Akil Abderrahim (Lead)
- Claude Opus 4.6 (co-author)
tags: [wacp, user, identity, ownership, capability, accountability, multi-user]
Table of Contents
- Purpose
- User Identity
- User Lifecycle
- Ownership
- Originator Propagation
- Capabilities
- The Coordinator Model
- Trail Events
- Conformance Requirements
- Implementation Notes
- References
1. Purpose
The protocol defines three principals: agents (who do the work), the coordinator (who orchestrates the work), and users (on whose behalf the work exists). The Identity spec (§2) defines how protocol objects are named. The Roles spec (§2) defines what agents are permitted to do. Neither addresses the human side — who initiated the work, who is accountable for the outcome, who receives escalations, and who makes domain decisions.
User is that missing principal. It defines user_id as a protocol-level primitive — the human identity that flows through workspaces, trail events, and the highway. It is the protocol’s equivalent of uid in Unix: assigned at the system boundary, carried through everything the protocol touches, never managed by the protocol itself.
The spec addresses five concerns:
- How humans are identified.
user_idis a primitive identifier that follows the Identity spec’s six rules (Identity spec §2) with one adaptation: it is assigned at the authentication boundary, not generated by the runtime. The protocol carries it; the deployment layer assigns it. - What state a user identity has. Unlike other identifiers,
user_idhas a lifecycle — at minimumactiveanddeactivated. A deactivated user’s identity persists in the trail and on existing workspaces, but the runtime rejects new actions from that identity. - How work is attributed to humans. Every workspace has an owner. Every human-originated action in the trail carries a
user_id. Theoriginatorfield propagates through delegated subtrees, so “show me everything user X caused” is answerable without walking the tree. - What humans are permitted to do. The capability model defines a fine-grained set of coordinator-level actions that can be granted to users. The protocol defines the atoms; deployment composes them into profiles.
- How the coordinator relates to users. The coordinator is the system — categorically different from users. It is not a privileged user; it is a different kind of entity. Its actions carry
originator: "system". Users interact through the highway; the coordinator mediates.
2. User Identity
user_id is the protocol’s human identifier. It follows the Identity spec’s six rules (Identity §2) with one structural adaptation and one behavioral extension.
2.1 Identifier Rules
The six rules apply to user_id as follows:
| Rule | Application to user_id |
|---|---|
| 1. Runtime-assigned | Adapted. user_id is assigned at the authentication boundary, not generated by the runtime. The runtime accepts and validates the identity; it does not create it. This is analogous to how the Unix kernel carries uid but does not manage /etc/passwd. |
| 2. Opaque | Unchanged. The protocol does not interpret user_id contents. It may be a UUID, an email address, an LDAP DN, or any string the deployment layer produces. |
| 3. Unique within scope | Unchanged. user_id is globally unique (§2.2). |
| 4. Immutable | Unchanged. Once assigned, a user_id never changes. The same human always has the same user_id. |
| 5. Referenceable | Unchanged. user_id appears in owner, originator, and actor fields across workspaces, envelopes, trail events, and highway interactions. |
| 6. Non-recyclable | Unchanged. A user_id is never reused — not after deactivation, not after the user leaves the organization. Trail entries reference it permanently. |
Rule 1 adaptation rationale. Every other identifier in the protocol is generated by the runtime because the objects they name (workspaces, envelopes, signals) are created by the runtime. Users are not created by the runtime — they exist in an external identity system (LDAP, OAuth provider, SSO, local accounts). The runtime’s role is to accept the identity at the system boundary, validate it against whatever authentication mechanism the deployment provides, and carry it from that point forward. The protocol defines what happens after authentication; it does not define how authentication works.
2.2 Uniqueness Scope
user_id is globally unique — across all runs, all sessions, and all time. The same user_id always refers to the same human. This is required for cross-run audit (“what did user X do across all runs?”) and for the non-recyclability guarantee (rule 6).
One person, one identity. A user_id MUST correspond to exactly one human. Shared accounts — where two humans operate under the same user_id — violate the accountability invariant. If the protocol cannot distinguish who acted, the trail’s attribution is meaningless. Implementations SHOULD enforce this at the authentication boundary, but the protocol cannot verify it — it is a deployment discipline, like the Unix convention that root should not be a shared login.
One identity, one person. A human SHOULD have exactly one user_id within a deployment. Multiple identities for the same person (e.g., separate accounts for different roles) are not a protocol violation but degrade audit coherence — actions by the same human appear as actions by different users. The protocol does not enforce this; deployment policy does.
3. User Lifecycle
Unlike every other identifier in the protocol, user_id has state. A workspace has a lifecycle; its workspace_id does not. A user_id has both — it names a human and tracks whether that human is currently authorized to act.
The user is not just a principal with state flags — the user is the root of their originator tree. Every workspace with an originator field traces its causal chain back to a human. That human’s state is the state at the top of the tree. When the root is suspended, blocked, or deactivated, the entire tree is affected — not automatically (the protocol does not prescribe cascade behavior), but informationally: the coordinator is notified and decides how to respond, the same way it responds to any workspace state change in the tree.
This is why user states mirror workspace states. The user occupies the same structural position as a root workspace. Their state governs the same kind of downstream effects.
3.1 States
A user identity exists in one of four states:
| State | Meaning | Can initiate work | Can respond to escalations | Escalation behavior | Referenced in trail |
|---|---|---|---|---|---|
active | The user is authorized to interact with the system. | Yes | Yes | Delivered | Yes |
suspended | The user’s authorization is temporarily paused by administrative action. | No | No | Queued | Yes |
blocked | The user is waiting on an external condition. | No | No | Queued | Yes |
deactivated | The user’s authorization has been permanently revoked. The identity persists for attribution. | No | No | Rejected | Yes (permanently) |
active is the normal operating state. The user can create workspaces, respond to escalations, inject directives through the highway, and exercise capabilities.
suspended is an administrative pause. A human with sufficient capability (or the system) decides to temporarily halt this user’s ability to act. Causes: security review, policy enforcement, organizational change, leave of absence. Resume requires explicit administrative action. While suspended, escalations addressed to this user are queued — not delivered, not rejected — because the system expects the user to return. This mirrors workspace suspension (Workspace spec, §4): the workspace is paused, its inbox freezes, but state is preserved and work resumes on resumption.
blocked is a conditional pause. Something external must happen before the user can act again. Causes: MFA re-verification, credential renewal, pending administrative approval, rate limiting. The user returns to active when the blocking condition resolves — which may be automatic (credential renewed) or manual (approval granted). While blocked, escalations are queued. This mirrors workspace blocking (Workspace spec, §4): the workspace waits for a specific condition (an envelope response, a gate resolution) and resumes when that condition clears.
deactivated is permanent revocation. The user cannot act, escalations are rejected (not queued), and reactivation requires a deliberate administrative decision. This mirrors workspace terminal states (closed, failed): the work is done, the identity persists for attribution, but no new actions originate from this root.
The queued-vs-rejected distinction is the core behavioral difference between temporary states (suspended, blocked) and terminal state (deactivated). Queued escalations accumulate and deliver when the user returns to active. Rejected escalations force the coordinator to reroute or fail the dependent work. This determines whether downstream workspaces block (waiting for the human) or must find an alternative path.
3.2 User Creation
A user identity enters the protocol when the authentication service first validates a user_id at the system boundary. This is the moment the protocol begins carrying the identity — before this event, the user_id does not exist within the system.
Trail event: A user_created event MUST be recorded when a user_id is first accepted:
user_created:
user_id: user_id
timestamp: timestamp
created_by: user_id | "system" # the actor that initiated user creation
user_created is recorded exactly once per user_id. The identity enters the system in the active state — there is no intermediate “pending” or “unverified” state within the protocol. Authentication verification happens at the OS service layer before the protocol ever sees the identity.
3.3 Authentication Events
The protocol records credential verification outcomes at the system boundary. These events track when and how users prove their identity — the audit trail for the authentication surface.
Successful authentication:
authentication_succeeded:
user_id: user_id
timestamp: timestamp
method: string # implementation-defined: "oauth", "api_key", "certificate", etc.
Failed authentication:
authentication_failed:
entity: string # identifier of the entity that failed (may not be a valid user_id)
context: string # what the entity was attempting (workspace_creation, highway_access, etc.)
reason: string # "invalid_credentials" | "expired_token" | "unknown_identity" | ...
source: string # where the attempt originated (transport address, interface identifier)
timestamp: timestamp
authentication_failed uses entity rather than user_id because the claimant may not correspond to any known user — or may not be a user at all (agents and coordinators also authenticate). The canonical schema is defined in the Security spec (mechanisms/security.md §9), which covers authentication failures for all entity types. This spec references the same event in the user context.
3.4 State Transitions
active → suspended (administrative suspension)
active → blocked (blocking condition arises)
active → deactivated (permanent revocation)
suspended → active (resumed by administrative action)
suspended → blocked (blocking condition arises while suspended)
suspended → deactivated (deactivated while suspended)
blocked → active (blocking condition resolved)
blocked → suspended (administratively suspended while blocked)
blocked → deactivated (deactivated while blocked)
deactivated → active (reactivation)
Suspension. A user is suspended when an administrator (or the system) temporarily halts their authorization. The runtime rejects all new actions from a suspended user_id:
- Workspace creation requests on behalf of the user are rejected.
- Highway injections from the user are rejected.
- Capability exercise is rejected.
- Escalation responses from the user are queued, not rejected.
Suspension does not affect existing work. Workspaces in the user’s originator tree continue running — agents are unaware of user state. The coordinator is notified of the suspension and decides how to respond: it may suspend some or all of the user’s workspaces, leave them running, or take no action. This is a coordinator policy decision, not an automatic cascade. The protocol defines the signal; the coordinator decides the response.
Resumption. A suspended user is resumed by administrative action — their user_id returns to active. Queued escalations are delivered. The user’s capabilities, ownership records, and trail history are unchanged by the suspension/resumption cycle.
Blocking. A user is blocked when an external condition prevents them from acting. The runtime rejects all new actions from a blocked user_id with the same rules as suspension. The distinction is causal: suspension is imposed by an administrator; blocking is imposed by a condition. The user returns to active when the condition resolves — which may be automatic (credential renewed, rate limit expired) or manual (approval granted).
user_blocked:
user_id: user_id
timestamp: timestamp
blocking_condition: string # implementation-defined: "mfa_required", "credential_expired", "pending_approval", etc.
blocked_by: user_id | "system"
Unblocking. When the blocking condition resolves, the user returns to active. Queued escalations are delivered.
user_unblocked:
user_id: user_id
timestamp: timestamp
resolved_condition: string # the condition that was resolved
Deactivation. A user is deactivated when their authorization is permanently revoked — they leave the organization, their account is disabled, their identity is retired. Deactivation can occur from any non-terminal state (active, suspended, blocked). The runtime rejects all new actions from a deactivated user_id:
- Workspace creation requests on behalf of the user are rejected.
- Highway injections from the user are rejected.
- Escalation responses from the user are rejected (not queued — the system does not expect the user to return).
- Capability exercise is rejected.
Deactivation does not affect existing work. Workspaces in the user’s originator tree continue running — agents are unaware of user identity. The coordinator is notified and decides how to respond: abort the user’s workspaces (with ownership-boundary reparenting per §4.4), transfer ownership, or let autonomous work complete. This is a coordinator policy decision, not an automatic consequence of deactivation.
Reactivation. A deactivated user may be reactivated — their user_id returns to active. This is a deliberate administrative decision, not an automatic process. The user’s capabilities, ownership records, and trail history are unchanged.
Trail events for all transitions:
user_suspended:
user_id: user_id
timestamp: timestamp
reason: string
suspended_by: user_id | "system"
user_resumed:
user_id: user_id
timestamp: timestamp
reason: string
resumed_by: user_id | "system"
user_deactivated:
user_id: user_id
timestamp: timestamp
reason: string
deactivated_by: user_id | "system"
prior_state: string # "active" | "suspended" | "blocked"
user_reactivated:
user_id: user_id
timestamp: timestamp
reason: string
reactivated_by: user_id | "system"
The user_blocked and user_unblocked schemas are defined above with their respective transitions. The prior_state field on user_deactivated records what state the user was in before deactivation — this matters for audit because deactivation from suspended (already paused) is different from deactivation from active (abrupt).
3.5 Coordinator Notification
Every user state transition produces a trail event (§3.4) and notifies the coordinator. The coordinator’s response is a policy decision — the protocol does not prescribe cascade behavior. This mirrors how the protocol handles workspace state transitions: the state change is recorded, the coordinator is informed, and the coordinator decides what downstream action to take.
Typical coordinator responses (non-normative):
- User suspended → suspend user’s workspaces. The coordinator may suspend some or all workspaces in the user’s originator tree, preserving state for when the user returns.
- User suspended → no action. The coordinator may leave workspaces running if the agent work is autonomous and does not require human interaction.
- User blocked → queue-aware routing. The coordinator may reroute escalations to a delegate user while the primary user is blocked.
- User deactivated → abort with reparenting. The coordinator may abort workspaces in the user’s ownership tree, reparenting cross-owned children per §4.4.
- User deactivated → transfer ownership. The coordinator may transfer the user’s workspaces to another user before or instead of aborting.
3.6 Deactivation Is Not Deletion
A deactivated user_id is not removed from the system. It persists everywhere it was ever referenced:
- Trail entries attributed to the user remain attributed to them.
- Workspaces owned by the user retain their
ownerfield. - The
originatorfield on workspaces and envelopes is unchanged. - The deduplication of “who did what” remains intact across the user’s entire history.
This mirrors Unix account management: userdel removes the login, not the uid. Files owned by uid 1042 still show uid 1042. The trail, like the filesystem, remembers.
4. Ownership
Every workspace has an owner — the human on whose behalf the work exists. Ownership is the primary link between the user and the agent system. It determines who receives escalations by default, who is accountable for outcomes, and what “my workspaces” means in capability scoping.
4.1 The owner Field
Every workspace carries an owner: user_id field, set at creation. The coordinator creates workspaces on behalf of users — the workspace exists because a human needs something done.
workspace:
id: workspace_id
owner: user_id # the human principal
role: string # the agent role (coordinator, worker, observer)
parent: workspace_id # the workspace tree structure
# ... other fields
owner identifies the human. role identifies the agent’s permissions. These are orthogonal — a workspace owned by user X with role worker means “user X’s work, performed by a worker agent.” The owner is the principal; the agent is the executor.
4.2 Ownership at Creation
The owner field is set when the workspace is created and follows these rules:
- Explicit assignment. The coordinator specifies
ownerat workspace creation. This is the normal case — the coordinator knows which user’s work this workspace serves. - Inheritance from parent. If the coordinator does not specify an owner, the workspace inherits the owner of its parent workspace. This is the default for delegated subtrees — when a delegate creates child workspaces, they inherit the originating user’s ownership unless explicitly overridden.
- Root workspace. The root workspace is the coordinator’s workspace. The coordinator is the system, not a user. The root workspace’s owner is implementation-defined — it may be a system-level identity or the user who initiated the run. The root workspace always carries
originator: "system".
4.3 Tree Ownership
Ownership flows through the workspace tree. When a user owns a workspace, they transitively own its entire subtree — every workspace created beneath it, at every depth.
User X owns W1
W1 (owner: X) creates delegate D1
D1 (owner: X, inherited) creates W2, W3
W2 (owner: X, inherited)
W3 (owner: X, inherited)
W3 creates W4
W4 (owner: X, inherited)
Tree ownership means that “user X’s workspaces” includes not just the workspaces directly created for user X, but the entire tree of work that user X caused. This is how capability scoping works — suspend_own means “suspend any workspace in your ownership tree,” not just “suspend workspaces where you are the direct requester.”
Explicit override. The coordinator MAY set a different owner on a child workspace, breaking the inheritance chain. This creates a new ownership boundary within the tree:
User X owns W1
W1 creates W5 (owner: Y, explicit)
W5 creates W6
W6 (owner: Y, inherited from W5)
W5 and W6 belong to user Y, not user X, even though they exist in X’s subtree structurally. This supports handoff scenarios — user X’s work produces a subtask that belongs to user Y’s domain.
4.4 Ownership Boundaries and Abort
When a workspace is aborted, the abort cascades downward through its subtree — but stops at ownership boundaries. Child workspaces owned by a different user are not aborted; they are reparented.
Cascade within ownership. If user X aborts workspace W1, and W1’s children W2 and W3 are also owned by X, the abort cascades to W2 and W3. All workspaces in the subtree that share the same owner are aborted together. This is the normal case — a user cleaning up their own work.
Stop at ownership boundary. If W1 (owner: X) has a child W5 (owner: Y), aborting W1 does not abort W5. The ownership boundary acts as a firewall. User X’s authority does not extend to user Y’s property.
Reparent orphans. When a parent workspace is aborted and a child workspace survives (different owner), the child is an orphan — its parent is dead. The runtime reparents the orphaned workspace to the coordinator, which becomes the new structural parent. This is the protocol’s equivalent of Unix orphan reparenting to init (PID 1). The reparented workspace continues running uninterrupted — agents are unaware of the structural change.
Before abort:
W1 (owner: X)
W2 (owner: X)
W3 (owner: X)
W5 (owner: Y)
W6 (owner: Y)
User X aborts W1:
W1 → failed (aborted)
W2 → failed (cascade, same owner)
W3 → failed (cascade, same owner)
W5 → reparented to coordinator (different owner, preserved)
W6 → preserved (child of W5, same owner Y)
Trail events. The reparenting produces a trail event:
workspace_reparented:
workspace_id: string # the orphaned workspace
old_parent: workspace_id # the aborted parent
new_parent: workspace_id # the coordinator (or nearest living ancestor)
reason: string # "parent_aborted_cross_ownership"
timestamp: timestamp
Coordinator notification. The coordinator is notified of the reparented workspace. It may reassign the workspace to a new parent in user Y’s scope, leave it under coordinator management, or consult user Y through the highway. This is a coordinator policy decision, not an automatic protocol action.
No blocked aborts. The runtime never refuses an abort because the subtree contains cross-owned workspaces. The abort proceeds for the requesting user’s workspaces; others are reparented. The user can always manage their own work.
4.5 Ownership Transfer
Ownership of a workspace can be transferred from one user to another. Transfer changes the owner field on the workspace and is recorded as a trail event.
Constraints:
- Only the coordinator (or a user with the
transfer_ownershipcapability) can transfer ownership. - Transfer applies to a single workspace. It does not automatically propagate to child workspaces — each child’s ownership must be transferred individually if desired. This prevents accidental mass-transfer of an entire subtree.
- Transfer does not rewrite history. Trail entries recorded before the transfer retain the original owner’s attribution. The transfer event marks the boundary.
Trail event:
workspace_ownership_transferred:
workspace_id: string
from_user: user_id
to_user: user_id
timestamp: timestamp
reason: string # optional: why the transfer occurred
transferred_by: user_id # the actor who performed the transfer
Effect on active work. Transfer is transparent to agents — they are unaware of ownership. The workspace’s inbox, state, and processing are unaffected. What changes is human-side routing: escalations from the workspace now route to the new owner by default, and the new owner’s capabilities govern what human actions are permitted on the workspace.
5. Originator Propagation
Ownership answers “whose work is this?” The originator field answers a different question: “what caused this?” Every workspace carries an originator — there is no workspace without a declared causal origin. The originator is either a user_id (a human caused this) or "system" (the system caused this). The coordinator’s root workspace always carries originator: "system" — the coordinator is the system (PROTOCOL §4.8).
5.1 The originator Field
Workspaces and envelopes carry a required originator field. Its type is user_id | "system". It records the causal origin — distinct from owner (who is accountable) and from from (which agent sent the envelope).
workspace:
owner: user_id # who is accountable (may change via transfer)
originator: user_id | "system" # what caused this (immutable, required)
envelope:
from: workspace_id # the agent that sent it
originator: user_id | "system" # the causal origin (required on workspaces; on envelopes, set when human-injected)
owner can change (via transfer). originator never changes — it records the causal origin, which is a historical fact.
5.2 Propagation Rules
Rule 1: Required. Every workspace MUST carry an originator. The field is never absent.
Rule 2: The root workspace carries originator: "system". The coordinator is the system. Its root workspace is always system-originated.
Rule 3: Autonomous actions set originator to "system". When the coordinator creates workspaces based on its own logic — recovery, bootstrap, maintenance, or any action not traceable to a human injection — the workspace carries originator: "system". The system is an explicit causal origin, not an absence.
Rule 4: Human injection sets originator to user_id. When a human injects a directive through the highway, the resulting workspace carries originator: user_id of the injecting human. This overrides inheritance at the injection point — the injected workspace has a different originator than its structural parent. This is the only mechanism that creates causal boundaries (PROTOCOL §4.8, rule 4).
Rule 5: Unconditional inheritance. When a workspace creates child workspaces (via delegation, not injection), the children inherit the originator. This propagation is automatic and unconditional — unlike owner, which can be explicitly overridden, originator always flows downward. A human-rooted subtree remains human-rooted at every depth. A system-rooted subtree remains system-rooted at every depth.
Rule 6: Immutable. Once set, originator never changes. Ownership transfer does not change the originator. Reparenting does not change the originator. The causal chain is a permanent record.
Rule precedence. Rule 4 (injection) overrides rule 5 (inheritance) at injection points. Rule 5 applies everywhere else. Injection is the single exception to unconditional inheritance.
5.3 Originator vs. Owner
The two fields serve different purposes and may diverge:
| Field | Answers | Set by | Mutable | Scope |
|---|---|---|---|---|
owner | Who is accountable for this workspace? | Coordinator at creation; transfer changes it | Yes (via transfer) | Current responsibility |
originator | What caused this workspace to exist? | Propagated from the injection or system action that started the chain | No | Historical causation |
When they match: User X injects a directive, the workspace is created with owner: X, originator: X. The common case for human-injected work.
When they diverge: User X injects a directive, the workspace is created with originator: X. Later, ownership is transferred to user Y (owner: Y). The workspace now shows: “caused by X, currently Y’s responsibility.” Both facts are preserved.
System-originated with human owner: The coordinator creates a recovery workspace (originator: "system") and assigns it to user Y (owner: Y). The workspace shows: “the system caused this, Y is responsible for reviewing it.”
Audit use case: “Show me everything user X caused” queries originator for a specific user_id. “Show me everything the system initiated” queries originator for "system". “Show me everything user Y is responsible for” queries owner. All are first-class trail queries.
6. Capabilities
The protocol defines a set of coordinator-level actions that can be granted to users. These are the protocol’s equivalent of Linux capabilities (CAP_*) — fine-grained permissions that decompose the coordinator’s authority into discrete, independently grantable atoms.
6.1 Capability Model
Capabilities govern what a user can do within the system. They are distinct from agent roles (Roles spec §2) — roles define what an agent is permitted to do inside a workspace; capabilities define what a human is permitted to do to the system.
The protocol defines the atoms. Each capability corresponds to one coordinator-level action. The set is defined by the protocol and is not extensible by deployment — this ensures that capability semantics are portable across implementations.
Deployment composes profiles. A deployment assigns capabilities to users. The protocol does not prescribe how — RBAC, ABAC, static configuration, or any mechanism is valid. The protocol defines what each capability means; the deployment decides who gets it.
The runtime enforces. When a user attempts a privileged action, the runtime checks whether the user holds the required capability. If not, the action is rejected and a trail event is recorded. Enforcement is mandatory — the runtime does not trust the deployment layer to pre-filter.
6.2 Capability Set
Capabilities are scoped as _own (applies to the user’s ownership tree) or _any (applies to all workspaces regardless of owner). The _own / _any split follows the principle of least privilege — most users need authority only over their own work.
| Capability | Scope | Action |
|---|---|---|
create_workspace | own | Create workspaces owned by self |
create_workspace_any | any | Create workspaces on behalf of any user |
suspend_own | own | Suspend and resume workspaces in own tree |
suspend_any | any | Suspend and resume any workspace |
abort_own | own | Abort workspaces in own tree (with ownership-boundary reparenting, §4.4) |
abort_any | any | Abort any workspace |
transfer_ownership | any | Reassign workspace owner (§4.5) |
inject_directive | own | Inject directives through the highway to own workspaces |
inject_directive_any | any | Inject directives to any workspace |
approve_integration | own | Approve or reject integration decisions for own workspaces |
approve_integration_any | any | Approve or reject integration decisions for any workspace |
modify_budget | own | Adjust resource budgets for own workspaces |
modify_budget_any | any | Adjust resource budgets for any workspace |
view_trail_own | own | Query trail entries for own workspaces |
view_trail_any | any | Query the full trail |
grant_delegation | own | Grant delegate capability to workers in own workspaces |
deactivate_user | any | Deactivate or reactivate user identities (§3.2) |
6.3 Implicit Capabilities
Some capabilities are granted implicitly by the protocol:
- Every active user holds
view_trail_own. A user can always see what their own workspaces are doing. This cannot be revoked — it is the minimum visibility required for a user to participate meaningfully. - The coordinator holds all capabilities. The coordinator is the system — it can perform any action. The capability model governs users, not the coordinator.
6.4 Capability Enforcement
When a user attempts a privileged action:
- The runtime verifies the user is
active(§3.1). If suspended, blocked, or deactivated, the action is rejected. - The runtime checks whether the user holds the required capability.
- For
_ownscoped capabilities, the runtime verifies the target workspace is in the user’s ownership tree (§4.3). - If the check fails, the action is rejected and a
capability_deniedtrail event is recorded:
capability_denied:
user_id: user_id
capability: string # the capability that was required
action: string # what the user attempted
target: workspace_id # the workspace they attempted to act on (if applicable)
reason: string # "missing_capability" | "wrong_scope" | "user_not_active"
timestamp: timestamp
6.5 Capability Changes
When a capability is granted to or revoked from a user, the change MUST be recorded in the trail. Capability changes are security-critical events — an auditor must be able to answer “when did user X gain this privilege?” and “who revoked it?”
capability_granted:
user_id: user_id
capability: string # the capability that was granted
granted_by: user_id | "system" # the actor that performed the grant
timestamp: timestamp
capability_revoked:
user_id: user_id
capability: string # the capability that was revoked
revoked_by: user_id | "system" # the actor that performed the revocation
timestamp: timestamp
reason: string # optional: why the capability was revoked
The protocol does not prescribe the mechanism that triggers these changes. The grant or revocation may result from static configuration changes, RBAC role assignment, ABAC policy evaluation, or direct administrative action. Regardless of mechanism, the trail event is mandatory.
6.6 Capability Assignment Mechanisms
The protocol does not prescribe how capabilities are assigned to users. Deployment mechanisms include:
- Static configuration. Each user’s capabilities are defined in a configuration file or environment variable at system startup.
- Role-based access control (RBAC). Users are assigned human roles (administrator, developer, reviewer) that map to capability sets. Not to be confused with agent roles (Roles spec §2) — these are deployment-layer constructs.
- Attribute-based access control (ABAC). Capabilities are derived from user attributes (department, clearance level, team membership) at runtime.
The protocol requires only that the runtime can answer “does user X hold capability Y?” at enforcement time. How that answer is computed is deployment-defined.
7. The Coordinator Model
The coordinator is the system — categorically different from users (PROTOCOL §4.8). It is not a privileged user; it is a different kind of entity. The topology spec (topology/causation.md) defines the causal tree formally. This section describes the user-side implications.
7.1 The Coordinator as System
The coordinator has no user_id. The root workspace carries originator: "system". System-initiated workspaces carry originator: "system". Human-injected workspaces carry originator: <user_id>.
User-side implications:
- The coordinator’s trail events use
actor: "system". System-initiated actions are distinguishable from human-initiated actions by type, not by privilege level. - No user can “become” the coordinator. Even a user with all capabilities acts through the highway — the coordinator mediates. Direct coordinator commands do not exist for users.
- The boundary between “user action” and “coordinator action” is absolute. Users request; the coordinator decides. The highway is the only human-system interface.
7.2 Mixed Trees
Every tree with human interaction is a mixed tree — it contains both the system-originated sub-forest (rooted at the coordinator) and human-originated subtrees (one per injection point). A fresh tree with no human injections has only the system sub-forest. The first injection creates the first human subtree.
Example: The coordinator (originator: "system") manages recovery and maintenance workspaces autonomously. A human injects a directive through the highway — the resulting subtree carries originator: <user_id>. The originator on each workspace declares whether the system or a human is the causal origin.
7.3 Protocol Invariants
| Invariant | Holds |
|---|---|
Every workspace has an owner | Yes |
Every workspace has an originator (user_id or "system") | Yes |
Coordinator actions are attributed to "system" | Yes |
Human actions are attributed by user_id | Yes |
| Capabilities are enforced | Yes |
| The highway mediates escalations | Yes |
| The trail is auditable | Yes |
originator propagates through subtrees (except at injection points) | Yes |
8. Trail Events
This section consolidates all trail events introduced by the User spec.
| Event | Trigger | Key fields |
|---|---|---|
user_created | Identity first accepted at authentication boundary (§3.2) | user_id, created_by, timestamp |
authentication_succeeded | Successful credential verification (§3.3) | user_id, method, timestamp |
authentication_failed | Failed credential verification (§3.3; canonical schema in Security spec §9) | entity, context, reason, source, timestamp |
user_suspended | User administratively suspended (§3.4) | user_id, reason, suspended_by, timestamp |
user_resumed | User resumed from suspension (§3.4) | user_id, reason, resumed_by, timestamp |
user_blocked | User blocked on external condition (§3.4) | user_id, blocking_condition, blocked_by, timestamp |
user_unblocked | Blocking condition resolved (§3.4) | user_id, resolved_condition, timestamp |
user_deactivated | User permanently deactivated (§3.4) | user_id, reason, deactivated_by, prior_state, timestamp |
user_reactivated | User reactivated from deactivated (§3.4) | user_id, reason, reactivated_by, timestamp |
workspace_ownership_transferred | Ownership reassigned (§4.5) | workspace_id, from_user, to_user, reason, transferred_by, timestamp |
workspace_reparented | Orphan reparented after cross-ownership abort (§4.4) | workspace_id, old_parent, new_parent, reason, timestamp |
capability_granted | Capability assigned to user (§6.5) | user_id, capability, granted_by, timestamp |
capability_revoked | Capability removed from user (§6.5) | user_id, capability, revoked_by, reason, timestamp |
capability_denied | User attempted action without required capability (§6.4) | user_id, capability, action, target, reason, timestamp |
All events follow the write-ahead invariant (PROTOCOL §9.1) — the trail entry is written before the operation takes effect.
9. Conformance Requirements
9.1 Required Behaviors (MUST)
Identity:
user_idMUST follow the six identifier rules from the Identity spec §2, with the Rule 1 adaptation for authentication-boundary assignment.user_idMUST be globally unique and non-recyclable (§2.2).
Lifecycle and Authentication:
- The runtime MUST record a
user_createdtrail event when auser_idis first accepted at the authentication boundary (§3.2). This event MUST be recorded exactly once peruser_id. - The runtime MUST record
authentication_succeededandauthentication_failedtrail events for credential verification outcomes (§3.3). - The runtime MUST track user state (
active/suspended/blocked/deactivated) and reject actions from non-active users (§3.1). - All state transitions MUST produce trail events (§3.4). The state machine defined in §3.4 is exhaustive — no transitions outside the defined set are permitted.
- Escalations to
suspendedorblockedusers MUST be queued. Escalations todeactivatedusers MUST be rejected (§3.1). - The coordinator MUST be notified of every user state transition (§3.5).
- Deactivation MUST NOT remove or modify existing trail entries, ownership records, or originator fields (§3.6).
Ownership:
- Every workspace MUST have an
owner: user_idfield set at creation (§4.1). - If no owner is specified, the workspace MUST inherit the owner of its parent (§4.2).
- Abort MUST cascade within ownership boundaries and reparent cross-owned children to the coordinator (§4.4).
- Ownership transfer MUST produce a
workspace_ownership_transferredtrail event and MUST NOT rewrite prior trail entries (§4.5).
Originator:
originatorMUST be set on envelopes and workspaces that result from human highway injections (§5.1).originatorMUST propagate to all child workspaces unconditionally (§5.2, Rule 2).originatorMUST be immutable (§5.2, Rule 4).
Capabilities:
- The runtime MUST enforce capabilities on every privileged user action (§6.4).
- Failed capability checks MUST produce
capability_deniedtrail events (§6.4). - Capability grants and revocations MUST produce
capability_grantedandcapability_revokedtrail events (§6.5). - Every active user MUST hold
view_trail_ownimplicitly (§6.3).
9.2 Recommended Behaviors (SHOULD)
- Deployments SHOULD enforce one-person-one-identity at the authentication boundary (§2.2).
- Deployments SHOULD enforce one-identity-one-person to maintain audit coherence (§2.2).
- The coordinator SHOULD notify the relevant user when their workspaces are reparented due to a cross-ownership abort (§4.4).
- The coordinator SHOULD evaluate the user’s originator tree when a user transitions to
suspendedordeactivated, and take appropriate action on affected workspaces (§3.5). - Implementations SHOULD bound the escalation queue depth for suspended and blocked users to prevent unbounded resource consumption.
9.3 Implementation-Defined Parameters
| Parameter | Reference | Constraint |
|---|---|---|
| Authentication mechanism | §2.1 | Must produce a globally unique, non-recyclable user_id |
| Blocking conditions | §3.4 | Must document which conditions trigger blocked state and how resolution is detected |
| Escalation queue limits | §3.1 | Must document maximum queue depth for suspended/blocked users |
| Capability assignment mechanism | §6.6 | Must answer “does user X hold capability Y?” at enforcement time |
| Root workspace ownership | §4.2 | Must define who owns the root workspace |
10. Implementation Notes
These notes are non-normative. They capture practical guidance for implementers.
Authentication boundary. The protocol begins after authentication. A simple implementation accepts user_id as a trusted string from the deployment layer (e.g., from an HTTP header set by a reverse proxy). A production implementation validates the identity against an external provider (OAuth token validation, LDAP bind, SAML assertion). The protocol does not care — it trusts whatever identity the runtime accepts at the boundary.
Capability storage. A static capability map (Map[user_id] → Set[Capability]) is the simplest implementation. For RBAC deployments, an intermediate layer maps human roles to capability sets: role → capabilities, user → role. The runtime resolves the chain at enforcement time. Capability checks should be fast — they occur on every privileged action.
Ownership tree traversal. The _own scope check requires determining whether a workspace is in the user’s ownership tree. A simple implementation walks the owner fields up to the root. An optimized implementation maintains a per-user index of owned workspace IDs, updated on workspace creation, transfer, and reparenting.
Reparenting. When a cross-ownership abort triggers reparenting (§4.4), the runtime must update the orphaned workspace’s parent field and record the trail event atomically. The reparented workspace’s agent is unaware of the change — it continues processing with a new structural parent. Signal propagation (PROTOCOL §4.3, §6.5) must be updated to route signals to the new parent.
Escalation queuing. When a user is suspended or blocked, escalations addressed to them accumulate in a queue. A simple implementation uses a per-user list ordered by arrival timestamp. On resumption or unblocking, the queue drains in order — each escalation is delivered as if it had just arrived. Implementations should bound queue depth to prevent unbounded growth during long suspensions. When the queue overflows, the oldest escalations are dropped and a capability_denied trail event is recorded with reason: "escalation_queue_overflow".
Originator tree traversal. The coordinator’s response to user state changes (§3.5) requires traversing the user’s originator tree — all workspaces where originator matches the affected user_id. A simple implementation scans workspace metadata. An optimized implementation maintains a per-user index of originator-linked workspace IDs, updated on workspace creation. This is analogous to the ownership tree index but follows the originator field instead of owner.
Blocked condition resolution. The protocol defines blocked state and blocking_condition but not the resolution mechanism — that is OS-level (authentication service). A simple implementation polls the condition periodically. A production implementation uses event-driven notification from the authentication service (e.g., MFA completed, credential renewed) to trigger the user_unblocked transition immediately.
11. References
PROTOCOL.md
| Section | Referenced in | Topic |
|---|---|---|
| §4.3, §6.5 | §10 | Signal propagation — reparenting updates signal routing |
| §4.8 | §1, §2, §3, §4, §5, §8 | User identity model — user_id assignment, authentication boundary, ownership, persistence |
| §6.4 | §4, §6.1 | Authority — frozen after workspace leaves idle; user capabilities bound at creation |
| §6.6 | §6.1 | Resource budgets — user-owned budget allocation |
| §6.8 | §6.1 | Dynamic visibility grants — user visibility scope |
| §8 | §6.1 | Human highway — user interaction model |
| §9.1 | §8 | Write-ahead trail — trail entry written before operation takes effect |
Constituent Specs
| Spec | Section | Referenced in | Topic |
|---|---|---|---|
| Workspace spec | §4 | §3.1, §3.4, §3.5 | User state mirrors workspace state — user is root of originator tree; suspension, blocking, and deactivation analogs |
| Identity spec | §2 | §2.1, §2.2, §3 | Identifier rules — user_id follows six identifier rules (runtime-assigned adaptation, opaque, unique, immutable, referenceable, non-recyclable) |
| Roles spec | §2 | §6.1, §6.6, §9.1 | Role model — capabilities bound to workspaces; RBAC maps user roles to capability sets |
| Security spec | §3, §9 | §3.3, §8 | Threat model, integrity requirements, authentication_failed canonical schema |
WACP constituent specification — authored by Akil Abderrahim and Claude Opus 4.6 Protocol: PROTOCOL.md | Taxonomy: TAXONOMY.md