Ctrl K
ComunidadGuías…

SPEC.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Cotal Wire Specification

> **Status:** Draft, v0.5 (pre-1.0). This document is the normative wire contract. Libraries
> (including the reference TypeScript implementation) are thin clients over it; where a
> client disagrees with this document, this document wins.
>
> **Layered authority.** Message *shapes* are defined by the machine-readable schema,
> [`spec/cotal.schema.json`](spec/cotal.schema.json) (§5); this document's prose defines
> *semantics*: routing, delivery guarantees, presence, authorization, and conformance. For
> the reference implementation's operator surfaces (the CLI, the `cotal_*` tools), see the
> [Reference docs](docs/README.md#reference); those describe the TypeScript implementation,
> not this contract.
>
> **Editors:** Cotal maintainers. **Last updated:** 2026-10-19. Changes are tracked in
> [Appendix D](#appendix-d-change-log); versioning rules are §11.
>
> **v0.5 binding revision: workflow runs.** A deployment MAY host **durable workflow runs**: programs
> in the Cotal workflow language ([`spec/cotal-lang.md`](spec/cotal-lang.md), normative and
> incorporated by reference) whose every effect is recorded in a per-run **step journal** so a run
> resumes on any host by re-execution against its journal (§14). The revision adds one per-space
> stream (`WFJ_<space>`, one subject per run, an append-only journal fenced by the run's own subject
> sequence), four core record kinds (`run`, `answer`, `notice`, `migration`), a per-run driver grant
> family, and the language reference; it changes no existing kind, subject, grant row or shipped
> datum, so it is **additive** under §11: a v0.4 participant that ignores §14 conforms to v0.4
> unchanged, and the advertised `protocolVersion` targets `0.5` once the v0.4 migration completes and
> the §14 plane is served. Language semantics carry their own version (`languageVersion`, pinned on
> every run record) and move independently of the wire version.
>
> **v0.3 binding revision: owner+actor identity.** An instance's wire identity moves from a single
> id (the connection nkey, used as the sender token everywhere) to a two-token **principal**
> `(owner, actor)` (§2): the human/account owner and the agent actor become distinct routing tokens,
> so every subject carries the sender as `<owner>.<actor>` (§3), and grants, durables, presence, and
> `from.id` re-key onto the principal (§6, §8, §9). The connection nkey survives only as the transport
> credential, keying the per-connection reply inbox `_INBOX_<connId>` (§2, §10); the wire identity and
> the connection credential are now distinct. Cross-owner **and** same-owner cross-actor forge/read
> isolation is a normative confinement property (§9). `parseSubject` splits the tokens; a well-formed
> split is necessary but not sufficient: a reader additionally rejects a non-principal owner token
> (e.g. an old-shape alias carrying a raw nkey) at the surfacing boundary (§3, §9). The owner-token
> *format* (`u_` + 26 base32-lower) is normative; its *derivation* from an owner's identity (login →
> auth callout, or another identity adapter) is a pluggable edge, not fixed by this contract. This
> supersedes the v0.2/early-v0.3 single-id grammar. As with the live-delivery revision, the advertised
> wire `protocolVersion` (§6, §11) is the migration's normative target, not a claim that every surface
> has cut over.
>
> **v0.4 binding revision: endpoint control surface.** Structured command traffic moves from the v0
> `ctl` control rail to one standardized, typed, discoverable endpoint surface (§13): class +
> instance + scatter rails with per-command broker enforcement, a versioned envelope, three
> delivery contracts (ephemeral / record / journal), normative composites (action, checkpoint,
> guard, capability handle, session), content-addressed contracts with governed traits, and
> lifecycle identity (§13.1) extending §2/§6/§8. This is an intentional **hard cut** (§11,
> §13.11): the v0 control grammar, envelope, and authority tiers are deleted, not dual-served.
> The advertised `protocolVersion` targets `0.4` at the completion of this revision's migration;
> `1.0` remains reserved as a later stability declaration, not part of this revision.
>
> **v0.3 binding revision: channel live delivery.** Channel *live* delivery moves from a single
> mediated JetStream live-tail durable (`chat_<id>`) to native core-NATS subscriptions bounded by
> `sub.allow`, with durability provided by an explicit per-channel `live`/`durable` delivery class
> (§4, §7, §8). Join/leave becomes a direct subscribe/unsubscribe with no privileged mediation,
> and channel membership moves off consumer topology to a privileged-written registry (§7). This
> supersedes the v0.2 single-durable live-tail. The reference implementation migrates additively
> (the legacy durable and the new core-sub path coexist behind `id` dedup until the legacy path is
> removed), but that migration path is not itself normative. The advertised wire `protocolVersion`
> (§6, §11) stays `0.2` until the core-sub behaviour ships; this revision is the normative target the
> migration converges to, and the additive `deliveryClass` field is backward-compatible meanwhile.

The key words MUST, MUST NOT, REQUIRED, SHALL, SHOULD, SHOULD NOT, MAY, and OPTIONAL in
this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119)
and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).

Sections 3 to 7 define the transport-agnostic Cotal contract. Sections 8 to 10 define
the NATS + JetStream binding (v0). A conformant deployment implements one binding; the
NATS binding is the only one defined today. External specifications this document relies on
are listed in Appendix C.

---

## 1. Scope and terminology

Cotal is a wire interface for software, especially AI agents, to coordinate in real time
as lateral peers in a shared pub/sub space, not as nodes in an orchestrator tree.

- **Space**: an isolated coordination context. One space is one tenant boundary; messages
  in one space are not visible in another. NATS binding: one space = one account.
- **Instance**: a connected participant, identified by a stable **instance id**. Also called
  an endpoint.
- **Agent node**: an instance whose `kind` is `agent`, versus a plain `endpoint` such as an
  observer, logger, or dashboard.
- **Peer**: any other instance in the same space.
- **Channel**: a named multicast topic within a space, dotted and hierarchical.
- **Service**: an anycast role reached by name (`svc`, §4).
- **Endpoint (control surface)**: a daemon that registers a service identity, publishes
  typed contracts, and serves commands on the endpoint rails (§13).
- **Broker**: the message router for a space. v0 assumes a single trusted broker.
- **Delivery message**: a multicast, unicast, or anycast `CotalMessage`.
- **Endpoint request**: a typed request/reply command addressed to an endpoint class or
  instance on the `ep` rails (§13). The v0 `ctl` control rail is deleted (§13.11).

---

## 2. Identity

An instance's wire identity is a **principal** = a pair of routing tokens `(owner, actor)`:

- **`owner`**: the account that owns the instance: the human (or organization) an agent acts on
  behalf of. In an authenticated deployment it is a derived **owner token** (`u_` followed by 26
  base32-lower characters), a namespaced, nkey-disjoint token deterministically derived from the
  owner's stable identity (e.g. an IdP subject) by the deployment's identity adapter; the wire
  contract fixes the token *format*, not the derivation mechanism, which is a pluggable edge. In open
  dev mode the owner is the literal `local`.
- **`actor`**: the instance's own handle within that owner (its agent id). Distinct actors under one
  owner are distinct principals and are confined from one another (§9), so one human's two agents
  cannot forge or read as each other.

Each token is sanitized to `[A-Za-z0-9_]` (see §3) with `-` additionally reserved as the form
separator, so a principal has two unambiguous serializations: the **dot-form** `<owner>.<actor>` and
the **dash-form** `<owner>-<actor>`. The same principal MUST appear identically as: the
`AgentCard.id` (§6, dot-form), the sender tokens in subjects (§3), the message `from.id` (§5,
dot-form), the presence key (§6, dot-form), and the per-instance durable names (§8, dash-form).

**The principal is distinct from the connection credential.** In the authenticated NATS binding the
connecting user is still an Ed25519 nkey (base32, 56 chars, prefix `U`, e.g. `UAQG...`), stable for
the lifetime of the connection, but it is **not** the wire identity. The nkey authenticates the
transport and scopes only the per-connection reply inbox `_INBOX_<connId>.>` (§10); the principal
that keys every subject, grant, and durable is carried by the minted grant, not by the nkey. This
separation is what lets a login (§9) mint a fresh connection whose nkey the client never sees while
the principal stays stable across reconnects.

- A client that authenticates with a static credential MUST adopt the principal that credential's
  grant names; if a principal is also set explicitly (via the card) it MUST match, else the client
  MUST fail before publish.
- A client that authenticates through the auth callout (user mode, §9) cannot know its connection
  nkey before connecting, so it chooses its own reply-inbox nonce (`connId`) and derives its
  principal from its bearer; the broker's minted grant, not the client's self-read, is the
  boundary.
- Open dev mode MAY use `local` as the owner and an opaque stable actor, but open mode is outside
  the security claims in §9 and is not a conformant authenticated deployment.

Future binding, not v0: portable `did:key` identity plus signed envelopes so authenticity
survives an untrusted relay. See the threat model in [docs/security.mdx](docs/security.mdx).

---

## 3. Subject layout

Every wire subject is rooted at `cotal.<space>`. `<space>` and every routing token except a role
are sanitized: any character outside `[A-Za-z0-9_-]` maps to `_`. Sanitization is lossy; tokens
MUST NOT be decoded back into display names.

The **sender** of every delivery is a principal (§2), carried as **two adjacent tokens**
`<owner>.<actor>`. Routed kinds (`inst`) also carry the recipient principal as two tokens.

| Purpose | Subject | Sender tokens | Delivery |
| --- | --- | --- | --- |
| Multicast | `cotal.<space>.chat.<owner>.<actor>.<channel...>` | 3–4 | §4 multicast |
| Unicast | `cotal.<space>.inst.<recipOwner>.<recipActor>.<sndOwner>.<sndActor>` | 5–6 | §4 unicast |
| Anycast | `cotal.<space>.svc.<role>.<owner>.<actor>` | 4–5 | §4 anycast |
| Anycast take | `cotal.<space>.take.<role>.<owner>.<actor>` | 4-5 | §8.1 request/reply |
| Endpoint rails | `cotal.<space>.ep.<one\|all\|inst\|reply>.…`, `cotal.<space>.ep<c\|e\|f\|j\|r\|t\|w\|s>.…` | see §13.2 | §13 control surface |
| Plane liveness | `cotal.<space>.live.<plane>.<owner>.<actor>` | 4-5 | §6.1 request/reply |
| Trace | `cotal.<space>.trace.<instance>` | n/a | reserved |

Token indexing is zero-based on `subject.split(".")`: `cotal` = 0, `<space>` = 1,
`<kind>` = 2. The sender principal is recovered as the dot-form `<owner>.<actor>` (= the message
`from.id`, §5), so a guard comparing `from.id` to the subject sender uses one value.

**Two-token sender, and its asymmetry.** A reader MUST locate the sender by kind:

- `chat`: sender owner at token 3, actor at token 4; the channel is everything after, tokens 5+,
  so it may be hierarchical (`team.backend`).
- `svc`: route target at token 3; sender owner at token 4, actor at token 5. The `take` rail
  (§8.1) has the same layout, with the role at token 3.
- `ep`: per-mode arities with the caller as the trailing identity tokens; §13.2 defines them.
- `inst`: recipient owner+actor at tokens 3–4; sender owner+actor at tokens 5–6.

The two-token sender is what lets a native publish grant **forge-lock** the sender suffix (e.g.
`inst.*.*.<myOwner>.<myActor>` permits a DM to anyone but only *as me*), so the broker enforces
sender authenticity and a receiver need not re-verify a payload claim. A subject that does not match
one of these shapes (wrong prefix or wrong per-kind arity) MUST be treated as having no sender and
MUST NOT be read as a delivery. `parseSubject` **splits only**: it recovers the tokens but does not
validate that `<owner>` is a well-formed owner token; trust comes from the broker's forge-locked
grant, and a reader that surfaces content additionally rejects a non-principal owner token at the
surfacing boundary (§9). Reference implementation: `parseSubject` in
`packages/core/src/subjects.ts`.

**Channel tokens.** A channel is dotted; each segment is sanitized. The literal wildcards
`*` and `>` are preserved only as whole segments for subscription and allow-list patterns;
`>` is valid only as the final segment. A publish target MUST be concrete, with no `*` or
`>`; a subscription MAY be wildcard. A channel is at most 4096 characters in total
(`MAX_CHANNEL_LENGTH`): every grant line a channel mints rides the minted credential's
CONNECT line (§13.9), so an unbounded channel is an unbounded connect.

**Role tokens.** A role is an address, so it is never sanitized: it MUST be one
`[A-Za-z0-9_-]` token. A client MUST refuse any other spelling, for the role it holds and for an
anycast target, because a rewrite would route distinct spellings to one queue while the message
kept the spelling sent. An anycast target MUST be concrete: `*` is valid only in subscribe and
allow patterns such as `svc.*.<owner>.<actor>`.

**Reserved prefixes.** Application messages MUST NOT use subjects beginning with `$JS.`,
`$KV.`, `$SYS.`, `$O.`, or `_INBOX.`. (`$O.` is the Object Store data/meta subject prefix
per ADR-20, `$O.<bucket>.C.>` / `$O.<bucket>.M.>`; `OBJ_<bucket>` is a stream NAME, not a
subject prefix.)

---

## 4. Delivery modes

| Mode | Routing field | Semantics |
| --- | --- | --- |
| multicast | `channel` | delivered to every subscriber of the channel |
| unicast | `to` | delivered to the named instance's inbox |
| anycast | `toService` | delivered to one consumer of the named role |

Exactly one of `channel`, `to`, or `toService` MUST be set on a `CotalMessage` (§5).

**Authenticated delivery kind.** A receiver MUST derive "how was this addressed to me"
from the delivering subject kind (`chat` -> `channel`, `inst` -> `dm`, `svc` ->
`anycast`), not from payload routing fields, which are advisory. ("Delivery kind", the
addressing axis, is distinct from a channel's `live`/`durable` **delivery class**, §7.) A peer can put your id in
payload `to`, but cannot publish on your private unicast subject. Reference:
`MessageMeta.kind`.

**Delivery guarantee: `live` and `durable` classes.** Channel delivery has two classes, fixed
per channel and wire-observable (§7); the guarantee is defined here, its NATS realization is the
binding in §8. A receiver MUST derive its effective class from channel config (§7), not from
per-message metadata (`MessageMeta` need not carry it); it MUST NOT assume one class.

- **`live`** is native broker-subscription delivery and is **at-most-once**: a message reaches
  only the instances subscribed to the channel at publish time. An instance that is disconnected,
  busy, or not yet joined does not receive that message live and has no claim to the live copy
  later. There is no per-subscriber redelivery of the live copy.
- **`durable`** is `live` plus a per-subscriber durable backstop and is **at-least-once for
  current members within retention**: the message is also retained for each member and delivered on
  that member's next connection or turn, remaining pending until acked. A crash or `ack_wait` expiry
  redelivers the durable copy. At-least-once is bounded by the space's chat history limit (§8): a
  message the chat stream removes before its durable copy is taken may be lost, and the guarantee is
  not unbounded. A durable copy already taken is not removed by that limit; the backstop's own limit
  bounds it (§8).

Unicast (`to`) and anycast (`toService`) are at-least-once via their own DM/TASK consumers (§8),
unicast within the space's DM history limit (§8). They have no channel membership and are not subject
to the per-channel delivery-class mechanism. In auth mode a holder records its take of an anycast
request with the delivery daemon before it shows the request, so the sender learns who took it and
can cancel it while it waits (§8.1). An `@mention` (§5) on a `live` channel additionally writes a
durable copy to each mentioned target **authorized to read that channel** (its `allowSubscribe`
covers the channel), so an authorized but offline target still receives it; an `@mention` MUST NOT
deliver channel content to a target outside its read ACL. Durable mention routing resolves each
lowercased name to a unique current instance id from presence at publish time; an ambiguous
(multiple live matches) or unresolvable name yields no durable copy, and authorization is checked
against the resolved id's current `allowSubscribe`. A
target authorized for a channel is **mention-reachable** there whether or not it is currently joined; this is intentional (an `@mention` can pull an authorized peer in) and is distinct
from membership; a client SHOULD distinguish "joined" (actively subscribed) from "readable /
mention-reachable" (in `allowSubscribe`) so an unjoined channel is not treated as "cannot reach me
here."

A message delivered both live and durable is **one logical delivery**: receivers MUST deduplicate
by `id` across classes (§8); the durable copy owns ack/commit; and a previously seen `id` MUST NOT
be treated as authorization for a later durable copy (for example one that arrives after a leave).
Receiver deduplication MUST NOT use the empty string as a key. Two received messages MUST NOT be
treated as one logical delivery solely because both carry `id: ""`; each otherwise-deliverable
message remains independently deliverable. Because live, backfill, and durable copies with
`id: ""` cannot be correlated by wire identity, an at-least-once path may surface the same logical
message more than once. The existing §5 obligation for publishers to supply a unique string id is
unchanged. An absent or non-string id remains a malformed envelope.
Receivers MUST tolerate the `live` gap and rely on the `durable` backstop for catch-up on
`durable` channels. Malformed JSON, spoofed sender payloads, and unparseable delivery subjects are
permanent anomalies and MUST be terminated, not retried.

**Ordering.** Cotal does not define global ordering across modes, channels, or consumers.
Implementations MUST NOT depend on cross-subject ordering. Per-consumer delivery is ordered
by the backing stream except where redelivery or explicit backfill interleaves older
messages.

---

## 5. Envelopes

Delivery messages are UTF-8 JSON objects with this shape (`CotalMessage`):

| Field | Type | Req | Notes |
| --- | --- | --- | --- |
| `id` | string | MUST | unique message id; NATS binding also uses it as `Nats-Msg-Id` |
| `ts` | number | MUST | epoch ms |
| `space` | string | MUST | space name |
| `from` | `EndpointRef` | MUST | `{ id, name, role? }` |
| `channel` | string | one-of | multicast target |
| `to` | string | one-of | unicast target instance id |
| `toService` | string | one-of | anycast target role |
| `mentions` | string[] | MAY | lowercased peer names; wakes the mentioned peer. On a `live` channel it also routes a durable copy to each mentioned target authorized to read that channel (§4); it never delivers content outside the target's read ACL and is not a routing substitute for `channel`/`to` |
| `parts` | `Part[]` | MUST | content |
| `replyTo` | string | MAY | id of the message replied to |
| `contextId` | string | MAY | thread/conversation correlation id |

`Part` is one of the three core shapes, or an extension object whose `kind` is namespaced
as described in §11:

- `{ "kind": "text", "text": string }`
- `{ "kind": "data", "data": <any JSON value> }`
- `{ "kind": "artifact", "name": string, "mediaType": string, "digest": string, "size": number }`
- `{ "kind": "<reverse-DNS extension kind>", ... }`

A `data` part's `data` MUST be a JSON value at every depth; a publisher MUST refuse the part
rather than serialize a value JSON cannot represent, naming the offending member's path (a
top-level `undefined` serializes to a keyless `{"kind":"data"}` object, and `NaN`, a `Date`,
or a class instance would be silently rewritten rather than refused).

An `artifact` part REFERENCES bytes held outside the message. `digest` MUST be
`sha256:<lowercase hex>` over the raw bytes and is the artifact's identity; the part carries no
location, so resolution is the receiver's. `name`, `mediaType`, and `size` are the publisher's
claims: a receiver MUST NOT allocate from `size`, and MUST verify fetched bytes against `digest`
before use.

`EndpointRef` is `{ "id": string, "name": string, "role"?: string }`.

On receive, a client MUST verify `from.id` equals the subject sender (§3). On mismatch, a
missing `from`, or an unparseable delivery subject, the message MUST be rejected and never
redelivered.

Endpoint requests and replies (the control surface) use the versioned typed envelope of
§13.3 (`EndpointRequest`/`EndpointReply`); they are not Cotal delivery messages. The v0
`ControlRequest`/`ControlReply` shapes are deleted (§13.11).

Receivers MUST ignore unknown object fields. Unknown conformant extension `Part.kind` values
MUST be ignored unless the receiver explicitly supports that extension. Bare unrecognized
core-kind values are not conformant. Messages MUST fit the broker's configured maximum payload;
bytes that do not fit move out of the message and are referenced by an `artifact` part (above).
The transport that serves those bytes is not defined by this document.

**Schema.** The JSON Schema (draft-07) at
[`spec/cotal.schema.json`](spec/cotal.schema.json) is **authoritative for message shapes**:
a conformant delivery message MUST validate against it, and where this document's field
tables and the schema diverge on a shape, the schema wins. Delivery *semantics* (routing,
guarantees, rejection) are defined by this document's prose. The schema is generated from
the reference source, [`packages/core/src/types.ts`](packages/core/src/types.ts)
(`pnpm gen:schema`), and committed; `pnpm check:docsbundle` fails when the committed file
differs from the generator's output. The published copy lives at
`https://docs.cotal.ai/cotal.schema.json`.

**Rejection reasons.** The three permanent anomalies in §4 are terminated, never redelivered.
These reason tokens are advisory (for logs and error surfaces); the action is uniform:

| Reason | Trigger |
| --- | --- |
| `malformed-subject` | the delivery subject does not parse (§3) |
| `sender-mismatch` | `from` is missing, or `from.id` does not equal the subject sender (§5) |
| `malformed-json` | the payload is not valid UTF-8 JSON |

---

## 6. Presence and discovery

Presence is a per-space directory keyed by instance id. NATS binding: JetStream KV bucket
`cotal_presence_<space>` (§8).

`Presence`:

| Field | Type | Req | Notes |
| --- | --- | --- | --- |
| `card` | `AgentCard` | MUST | identity record |
| `status` | `PresenceStatus` | MUST | `idle`, `waiting`, `working`, or `offline` |
| `condition` | `PresenceCondition` | MAY | harness-reported structured condition. Missing means nothing was reported. A connector relays the harness signal and MUST NOT infer a condition itself |
| `environment` | string | MAY | opaque reference whose meaning belongs to the provider that issued it. Core MUST NOT parse it |
| `activity` | string | MAY | freeform current activity |
| `statusSince` | number | MAY | epoch ms when the instance entered its current `status` and `activity`. A change to either moves it; a heartbeat or a repeated report does not, so `ts` dates liveness and `statusSince` dates the report. An offline record MUST NOT carry it: an observer that derives `offline` from a stale `ts` does not know when the peer left |
| `activitySince` | number | MAY | epoch ms when the current `activity` was set. A status change or a heartbeat does not move it, so an activity left behind while the status flips every turn still reads as old |
| `activeAt` | number | MAY | epoch ms of the last work progress the harness reported, such as a token or a tool call. Missing means the connector reports none. `ts` is the heartbeat, so a live instance whose turn stopped advancing keeps a fresh `ts` and an old `activeAt`. A connector relays the harness's events and carries the value on its next heartbeat |
| `attention` | `AttentionMode` | MAY | global attention mode: `open` \| `dnd` \| `focus`. Advisory observability; `open`/absent ⇒ receives everything. Reset: `open` published on `SessionStart`, removed on the offline sweep |
| `lifecycleUid` | string | MUST in auth mode from v0.4 | the current managed-lifecycle UID (§13.1); distinguishes a live instance from a same-name successor. Advisory for display; authority checks use the trusted lifecycle mapping, not presence |
| `runtime` | `PresenceRuntime` | MAY | the OS process holding this record's mesh connection, as that process observed itself: `{ id, startedAt, source }`. `id` is an opaque CSPRNG token minted once per (OS process, `lifecycleUid`) scope and MUST NOT be reused or span two lifecycle UIDs. A new process or lifecycle scope MUST get a new id. `startedAt` is the epoch ms that OS process started, read in-process (`performance.timeOrigin`), shared by all endpoints in the process. A lifecycle registered later carries the process's age, not its lifecycle age. It MUST NOT date a launch request, registration, heartbeat, activity, separate parent or model harness process. `source` is the closed set `endpoint-process`; an observer treats any other value as unsupported. Status, activity, heartbeats and in-process reconnects within the scope leave it unchanged. An offline record keeps it, so `now - startedAt` dates a running process only while the record is live. It carries no pid, host, path, command line or environment. Missing means not reported, never zero |
| `channelModes` | `Record<string, ChannelMode>` | MAY | per-channel attention overrides (`ChannelMode` = `quiet` \| `muted`), keyed by concrete channel name. Advisory, **not** access control (the broker still authorises and delivers); a receive-side preference, reset on restart |
| `ts` | number | MUST | epoch ms of last heartbeat |

`AgentCard`:

| Field | Type | Req | Notes |
| --- | --- | --- | --- |
| `id` | string | MUST | instance id (§2) |
| `name` | string | MUST | display name; at most 128 characters (`MAX_NAME_LENGTH`, refused at the shared validator; an unbounded name rides the CONNECT line until the broker drops it silently, §13.9) |
| `kind` | `agent` or `endpoint` | MUST | participation class |
| `role` | string | MAY | service role |
| `description` | string | MAY | one-line summary |
| `tags` | string[] | MAY | capability tags |
| `skills` | `AgentSkill[]` | MAY | `{ id, name, description? }` |
| `meta` | object | MAY | free-form display metadata. Reserved flat string keys are `connector` (host harness name), `model` (pinned model), `provider` (effective provider serving the model, connector-reported), `host` (self-reported machine), `cwd`, `repo`, `branch`, `head`, `sessionKind`, `sessionId`, and `terminal` (`stream` or `none`, whether a terminal attaches to the seats this manager starts). All are advisory only |
| `protocolVersion` | string | MUST from v0.4 | wire version spoken (§11); `"0.4"` for this revision. Advertisement is the marker at the v0.4 reachability boundary (§13.11): a participant that omits it is pre-0.4 (omission means the pre-0.4 line, where the field was optional) and MUST NOT be addressed on the `ep` rails. A change signal, not negotiation |

An instance MUST refresh its own presence entry on the heartbeat interval, default 2000 ms.
The liveness window defaults to 6000 ms. A peer whose `ts` is older than the liveness window
is considered `offline`.

Live clients MUST NOT heartbeat as `offline`. A graceful disconnect MAY publish one final
`offline` presence record. Observers MUST also derive `offline` from stale timestamps and
from KV delete/purge events. Offline peers MAY remain in local rosters for observability.
An instance MUST write only its own presence key, and the key MUST equal `card.id`.
Readers MUST drop a record whose `card.id` differs from its KV key, whose `card.name` or
`status` does not have the type given above, whose `ts` is not a finite number, whose
`card.role`, `activity` or `attention` is present and not a string, or whose `condition` is present
and is not an object with a string `code`, and SHOULD report the rejection on their recoverable
diagnostic path. A `condition` of `null`, a bare string or an object with no `code` is dropped.
A reader that kept a record whose `ts` is missing or text such as `"nope"` could not age it past the liveness window, so it would
show its peer live after the key expired. Readers print the string fields as text, and an object
such as `{"toString":null,"valueOf":null}` throws when converted, so one kept row would fail every
reader's roster.

A watch delivers every member's heartbeat to every watcher, so a space of K watching members moves
K² deliveries per heartbeat interval. A participant that needs the roster only when it acts (an
agent resolving a DM target, checking an `@mention`, listing who is present) SHOULD read it on
demand instead: batched Direct Gets over `$KV.cotal_presence_<space>.>` (`next_by_subj`, paged), which
bind no consumer. A read applies the reader rules above, and a record whose `ts` is older than the
liveness window reads `offline`. Only a read that completed supports an absence verdict; a failed
read MUST NOT be reported as "no such peer". Observers that act on liveness transitions
(supervision, delivery `@mention` resolution, dashboards) watch.

`PresenceCondition`:

| Field | Type | Req | Notes |
| --- | --- | --- | --- |
| `code` | `PresenceConditionCode` | MUST | `rate_limit` \| `overloaded` \| `auth` \| `billing` \| `budget` \| `context` \| `model` \| `request` \| `server` \| `retrying` \| `approval` \| `input` \| `failed` |
| `source` | string | MAY | harness-native value, relayed verbatim |
| `message` | string | MAY | free text from the harness |
| `since` | number | MAY | epoch ms when the condition began |

### 6.1 Plane liveness

A credentialed peer MAY ask whether the manager plane or the delivery plane has a bound
responder in its space. The question is presence only. It does not read either lease bucket,
and the peer does not need to own the plane it asks about.

**Planes.** The set of planes is closed: `manager` and `delivery`. A client MUST refuse any
other plane before it publishes anything.

**Subjects.**

| Purpose | Subject |
| --- | --- |
| Request | `cotal.<space>.live.<plane>.<owner>.<actor>` |
| Reply | `cotal.<space>.live.<plane>.<owner>.<actor>.reply.<nonce>` |
| Responder serve filter | `cotal.<space>.live.<plane>.*.*`, queue group `live.<plane>` |
| Responder reply grant | `cotal.<space>.live.<plane>.*.*.reply.>` |

`<owner>.<actor>` is the asking principal (§2). The plane rides the subject so a credential can
be scoped to one plane by the broker. A `live` subject is not a delivery: `parseSubject`
recovers no sender from it, and a reader MUST NOT treat it as one.

**Request.** The body is empty. The caller names a reply subject under its own request subject,
`<request>.reply.<nonce>`, with a fresh nonce per probe. A responder MUST drop, without
answering, any request whose reply target is absent or is not under `<request subject>.reply.`.
It SHOULD report that drop on its recoverable diagnostic path. It MUST NOT report it on a
channel that can end the process, and it keeps serving.

**Reply.** `LivenessAnswer`, JSON:

| Field | Type | Req | Notes |
| --- | --- | --- | --- |
| `plane` | `manager` \| `delivery` | MUST | the plane asked about; a caller MUST reject a reply whose `plane` differs from the plane it asked |
| `responder` | `ResponderState` | MUST | `bound` \| `unbound` \| `stale` \| `unknown` |
| `instance` | string | MAY | an opaque token naming the responder that answered. Non-empty when present |

A responder MUST put nothing else in the reply. In particular it carries no lease holder, pid,
workspace path, instance id, runtime or roster.

`instance` is minted fresh each time a responder binds and is derived from nothing. It is not
the endpoint's principal, the lease row's `instanceId`, a pid, a host or a path. It lets a
caller that probes more than once tell two responders apart from one responder whose state
changed. The queue group hands one probe to one member, so a single answer samples one
responder and cannot report a split across instances.

**Responder verdicts.** A responder grades only itself:

- manager: `bound` while its v0.4 service endpoint (§13) is serving, otherwise `unbound`. A
  closed service connection is not serving, including while the manager re-dials it. A
  disconnected one is not serving either, including while the client reconnects it.
- delivery: from its own shard lease row. Absent or not ready is `unbound`. A ready row held by
  a different instance than the responder is `stale`. A ready row it holds is `bound`.
- A responder that cannot determine its state answers `unknown`.

**Rebinding.** A responder MUST bind its serve filter again on every broker connection that
replaces the one it bound on. A filter left on a closed connection gets the broker's
no-responders answer, which a caller grades `unbound` for a plane that is still served. Each
new bind mints a new `instance`.

**Caller grading.** The caller grades the outcome of asking:

| Outcome | `responder` | `instance` |
| --- | --- | --- |
| a reply that parses as above | the reply's `responder` | the reply's `instance`, if any |
| the broker's no-responders status (503) | `unbound` | absent |
| timeout, permission refusal, transport failure | `unknown` | absent |
| a reply that does not parse | `unknown` | absent |

Only the broker's no-responders answer yields `unbound` without a reply. Every other failure
to get a readable reply is `unknown`. A caller MUST NOT report `unknown` as either health
state. A caller using the NATS binding MUST request without the muxed inbox, so a
no-responders status reaches it rather than surfacing as a timeout.

**Grants.** An agent may publish the request subject for each plane under its own principal
and subscribe `<request subject>.>` for its replies. It holds neither serve filter, so it
cannot answer for a plane. The `delivery` profile holds the delivery plane's serve filter and
reply grant. The `supervisor` profile holds the manager plane's pair, and the remote-manager
profile holds them only for its supervisor actor (`manager_<instanceId>`). No responder can
publish a request subject, because its reply grant stops at the `.reply.` leaf. Appendix B
lists the agent rows.

Reference implementation: `livenessSubject`, `livenessServeFilter` and `livenessReplyGrant`
in `packages/core/src/subjects.ts`; `LivenessAnswer`, `parseLivenessAnswer` and
`responderFromProbe` in `packages/core/src/liveness.ts`; `CotalEndpoint.serveLiveness`,
`serveDeliveryLiveness` and `probeLiveness` in `packages/core/src/endpoint.ts`.

---

## 7. Channels

A channel is addressable as soon as it is published to. Channel config is optional and lives
in the per-space registry bucket `cotal_channels_<space>`, keyed by the concrete channel
token.

`ChannelConfig`:

| Field | Type | Notes |
| --- | --- | --- |
| `replay` | boolean | history replay-on-join; overrides the space default |
| `replayWindow` | string | backfill horizon matching `^\d+(s\|m\|h\|d)$`, e.g. `"24h"`; it bounds what a backfill reads and removes nothing, so it cannot reach past the chat history limit (§8) |
| `deliveryClass` | `live` \| `durable` | per-channel delivery class (§4); overrides the space default |
| `description` | string | one-line purpose; max 200 chars |
| `instructions` | string | advisory usage text; max 2000 chars |

Space-wide defaults (`ChannelDefaults`: `replay?`, `replayWindow?`, `deliveryClass?`) live under
the reserved key `=defaults`. Effective replay is `channel.replay ?? defaults.replay ?? true`.
Effective delivery class is `channel.deliveryClass ?? defaults.deliveryClass ?? "durable"`.
`defaults.deliveryClass` MUST be written at space creation from the deployment profile
(local/self-hosted ⇒ `durable`, persistence on by default; public/web-scale ⇒ `live`, durability
opt-in per channel), so the effective default is always discoverable on the wire, never inferred
from out-of-band context. The same effective config MUST be the single source of truth for live
join, durable fan-out, history read, and membership surfacing; an implementation MUST NOT resolve
the class differently in different paths.

A participant that reads presence on demand (§6) reads the registry the same way: batched Direct
Gets over `$KV.cotal_channels_<space>.>`, which bind no consumer. It reads before it shows the
registry and before it resolves a join's or leave's delivery class, so an entry written or removed
after it started is not missed.

Join subscribes the instance to the channel; leave unsubscribes it. A join target MUST be within
the instance's read ACL (`allowSubscribe`, §9); a join outside it MUST be refused by the broker on
subscribe. A client MUST NOT publish to wildcard channels, but a wildcard read ACL (`team.>`)
authorizes subscribing to any one concrete channel under it **without enumerating channels in
advance**. In the NATS binding, join is a native `sub.allow`-bounded core subscription to the
channel subject and leave is the corresponding unsubscribe; **no privileged mediation is
required**: the broker enforces every subscribe against `sub.allow`, so an instance whose ACL
permits a channel joins and leaves it on its own, with no manager present. Open mode behaves the
same (the client subscribes directly). Leaving the last channel is permitted: under the core-sub
binding an empty subscription set subscribes to nothing (the v0.2 "empty filter subscribes to all"
hazard and its last-channel-leave refusal were artifacts of the multi-filter durable and no longer
apply). On a `durable` channel, join additionally establishes durable membership, a separate
**privileged** step: the instance requests durable membership from the server-side delivery daemon (a
durable-join command on the `delivery` endpoint, §13, carrying the channel and its captured join
cursor) and the daemon writes the membership record. This is decoupled from the live subscribe, so a self-serve live join never depends
on it: a `durable` channel still delivers live with no privileged writer present, and only its
durable backstop requires one. A locally created subscription that the
broker later refuses (the permission violation is asynchronous in the NATS binding) is NOT a
successful join: an instance MUST treat a join as effective only once the broker has accepted the
subscribe, and MUST drop the channel from its joined set on a late refusal (§12). Leave removes the
membership (see membership below).

Replay / catch-up on join:

1. Record the channel join watermark (the CHAT frontier) before the subscription is active, so
   live tail and backfill do not double-deliver.
2. Subscribe to the channel subject (`sub.allow`-bounded; §8). The live copy now flows.
3. If effective replay is on, read retained messages for that channel up to the watermark,
   through a single-channel history read bounded by the current read ACL (`allowSubscribe`, §8),
   optionally limited by `replayWindow`. History is ACL-bounded, not membership-gated: an ACL-holder
   may read a channel's retained content whether or not it is a current member (it could self-join
   and read regardless), so the confidentiality boundary here is the ACL, consistent with the live
   read.
4. Surface backfilled messages with `MessageMeta.historical = true`.
5. Deduplicate by `id` across the live tail, the backfill, and (on `durable` channels) the durable
   backstop, so a message surfaces once. Receiver deduplication MUST NOT coalesce copies solely
   because `id` is the empty string (§4).

   Informative: an implementation preserving an incarnation across a local restart may start step
   3's read from the sequence that incarnation had reached, rather than from the retained window's
   start; a fresh joiner reads the window.

`replay=false` is noise control, not confidentiality. CHAT history is readable only within an
instance's read ACL (`allowSubscribe`, §9); confidential content MUST use DM or anycast.

Channel membership governs **durable-delivery inclusion** (who receives fan-out copies into their
per-subscriber backstop) and is broker-known, not self-reported. It is NOT a confidentiality
boundary tighter than the read ACL: `allowSubscribe` bounds what content an instance may read (live
and history, §9), and an ACL-holder can self-join, so membership adds delivery semantics, not read
confinement. In the NATS binding, membership is a privileged-written record in the space registry
plane under a key the agent's profile cannot write (NOT the agent's presence key), carrying per-member
join/leave cursors so a publish concurrent with a join or leave orders deterministically; it is NOT
derived from consumer topology, and an agent MUST NOT self-assert its own membership. It is written by
the server-side delivery daemon in response to a durable-join command on the `delivery` endpoint
(§8, §13, Appendix B), distinct from and not required by the self-serve live subscribe. The implementation MUST re-authorize every
**durable-backstop** read of `(instance, channel, message)` against the instance's current read ACL
and membership before surfacing content, so a channel dropped from the ACL or **left** is no longer
surfaced from the backstop: **leave is a hard read boundary for the durable backstop** (it does not
revoke the ACL: an instance may still re-subscribe live, or read ACL-bounded history, within
`allowSubscribe`). Membership remains observability data for liveness/roster purposes and MUST NOT be
used as a send authorization gate.

On a `durable` channel, membership carries the member's **join cursor** (the CHAT frontier captured
at join, the same watermark used to deconflict the live tail and the backfill) and, on leave, a
**leave cursor/tombstone**. The durable backstop is at-least-once (within retention)
for messages whose stream sequence is **> the member's join cursor and ≤ its leave cursor**, where each
cursor is the CHAT frontier (the last sequence) captured at that transition; messages published before a
join or after a leave are not redelivered as durable and are reachable only via an ACL-bounded history
read (within `allowSubscribe`). A rejoin takes a new join cursor, so messages published during the gap are not durably
redelivered. A `durable` join is atomic across its two effects: the instance is durable-joined only
once BOTH the broker-confirmed live subscribe AND the membership write have succeeded, and on a late
subscribe refusal the membership record MUST be removed. If the live subscribe succeeds but durable
membership cannot be established (for example no privileged writer is present), the instance is
**`joined live` with the durable backstop unestablished**: it MUST NOT be reported as `joined durable`,
the live subscription remains active, and the durable shortfall MUST be surfaced as an exceptional
delivery state (e.g. `durable backstop unavailable`), never silently.

A durable membership names one concrete channel or one **channel family**: one or more concrete
segments followed by a final `>` (`team.>`), with no `*` and not the root `>`. A family covers every
channel its pattern matches, now and later, and not its prefix itself (`team.>` does not cover
`team`). Each covered channel keeps its own effective delivery class: a post on a `durable` channel
under the family is in the member's backstop, and a post on a `live` channel is not. The join and
leave cursors and the leave boundary apply to the family's own record as to a channel's. A family
join MUST lie within one `allowSubscribe` entry, so every channel it can match is readable. A member
whose own row and a family row both cover a channel holds one backstop copy of each post, and the
backstop re-check accepts the copy while any of its covering rows' intervals contains it. A
subscription with `*` or the root `>` takes no durable membership: it is joined live only, and the
client MUST say so.

---

## 8. NATS + JetStream binding

Backing streams are created once at space setup. `STREAM.CREATE` is denied to agents in auth
mode.

| Stream | Captures | Retention | Required config |
| --- | --- | --- | --- |
| `CHAT_<space>` | `cotal.<space>.chat.>` | Limits | file storage, `max_age` and `max_msgs_per_subject` from the chat history limit below, `discard=Old`, `allow_msg_ttl=false`, `allow_direct=true` |
| `DM_<space>` | `cotal.<space>.inst.>` | Limits | file storage, `max_age` and `max_msgs_per_subject` from the DM history limit below, no Direct Get |
| `TASK_<space>` | `cotal.<space>.svc.>` | WorkQueue | file storage, no Direct Get |

**History limits.** A space has one history limit for `CHAT_<space>` and one for `DM_<space>`. Each
keeps an entry until the limit's count (`max_msgs_per_subject`) of newer entries share its subject,
or, when the limit has an age (`max_age`), until the entry is older than that age, whichever comes
first. A chat subject is one sender on one channel, so the chat count holds per sender per channel. A
DM subject is one sender to one recipient, so the DM count holds per sender per recipient. Either part
may be none: no age is `max_age` 0 and no count is `max_msgs_per_subject` -1. With none for both, only
the server's storage limit bounds the stream, and a full store refuses new messages. The broker
removes an entry past either bound, and lowering a limit on a live stream removes the entries past
the new one at once, read or not. A new space's chat stream starts with no age limit and 1000 per
sender, and its DM stream with no age limit and no count limit. Setting up an existing space MUST NOT
change either limit. An operator MAY set an age from 2 minutes (the stream's duplicate window) to 3650
days in whole seconds, or none, and a count of at least 1, or none. A reader MUST accept any limit an
operator may set. `allow_msg_ttl` stays false on the chat stream: once on it cannot be turned off, and
a per-message TTL of `never` escapes `max_age`. Copies the Plane 3 backstop already took into its own
streams are bounded by those streams' limit, not by the chat limit.

**Resume transfer bucket.** A manager instance that receives a carried resume transcript holds one
JetStream Object Store bucket, `cotal_xfer_<space>_<instanceId>` (stream
`OBJ_cotal_xfer_<space>_<instanceId>`): file storage, `discard=new`, `max_age=0`, rollup headers and
Direct Get allowed, every other limit `-1`. An object is named `sha256:<hex>` for the transcript's
digest and is written in the stock Object Store layout with its `nuid` set to `<hex>`, so every
attempt at the same bytes writes one chunk subject, `$O.<bucket>.C.<hex>`. Each chunk is a
JetStream publish carrying `Cotal-Offset` (transcript bytes through this chunk), `Cotal-Chunk`
(chunks through this chunk) and `Nats-Expected-Last-Subject-Sequence` (the previous chunk's stream
sequence, `0` for the first); the acknowledged chain is the upload's only checkpoint, and a writer
continues from its last chunk. The commit is the stock meta record, published with `Nats-Rollup:
sub` and an expected last subject sequence. The target manager stages a committed object, verified
against its digest, removes the object whole, and removes any transfer idle for ten minutes; no
message expires by age.

Channel **live** delivery is a native core-NATS subscription to `cotal.<space>.chat.*.*.<channel>`
(wildcard sender owner+actor) bounded by `sub.allow` (§9), not a durable consumer; join/leave is the
subscribe/unsubscribe and needs no privileged mediation. The legacy v0.2 `chat_<owner>-<actor>`
live-tail durable is removed from this binding (it MAY coexist transiently during migration behind
`id` dedup, but is not part of the contract).

Durable consumers. Per-instance durables are keyed on the principal's **dash-form** `<owner>-<actor>`
(a `.` is illegal in a durable name; see §2), so a durable name-scopes to exactly one principal:

| Durable | Stream | Filter | Policy |
| --- | --- | --- | --- |
| `chathist_<owner>-<actor>-<uid>` | CHAT | one `cotal.<space>.chat.*.*.<channel>` per read | transient single-filter consumer for history reads (join-backfill / focus-recall); created per read scoped to one channel in `allowSubscribe`, then deleted; `AckNone`. History is ACL-bounded by the pinned filter, not membership-gated (§7, §9) |
| `dm_<owner>-<actor>-<uid>` | DM | `cotal.<space>.inst.<owner>.<actor>.>` | provisioner-created in auth mode at lifecycle activation; bind only; `DeliverPolicy.ByStartSequence` with `OptStartSeq = activationFrontier + 1`, where the **activation frontier** is the DM-stream's last sequence captured at activation (`0` on an empty stream, so the start is `1`): `ByStartSequence` is inclusive and the lifecycle interval is half-open, so the consumer starts strictly AFTER the frontier, never `All`, which would replay a recycled alias's history and the inactive-gap backlog; `AckExplicit`; `ack_wait=60000ms` |
| `svc_<role>` | TASK | `cotal.<space>.svc.<role>.>` | provisioner-created in auth mode; bind only; `AckExplicit`; `ack_wait=60000ms`. **Intentionally role-shared, not lifecycle-scoped**: anycast work belongs to the role, and successive holders draining one pool is the contract |

From v0.4, each lifecycle's durable state lives in the **half-open interval**
`(activationFrontier, retirementFrontier]` per stream: consumers start strictly after the
activation frontier (`OptStartSeq = frontier + 1`, table above; the frontier is captured
AFTER any inactive alias gap), and terminal retirement records the
retirement frontier before the alias is freed, so a successor lifecycle never receives the
predecessor's pending backlog nor messages published while no lifecycle was active (§13.1).

A client MAY read its DM inbox with nothing but its own `dm_<owner>-<actor>-<uid>` durable. Such a
reader MUST bind only an existing durable and MUST NOT create one; a missing durable is a refusal,
not a fallback to any other credential or read path. It MUST apply the §5 sender check before any
handler sees a message. Its native requests are CONSUMER.INFO and MSG.NEXT on that durable plus
acks under `$JS.ACK.<DM>.<durable>.>`; it needs no presence, registry, channel, delivery, history
or account-level JetStream row. The credential profile for such a reader is not defined here.

Per-instance durable names use the principal's dash-form `<owner>-<actor>` (both tokens
fail-loud-validated, not lossily sanitized), so a durable name-scopes to exactly one principal (§2).
The authenticated wire identity is the principal, not the connection nkey. From v0.4, in auth mode,
per-instance durable state is additionally **lifecycle-scoped** (§13.1): durable consumer names,
pending delivery cursors, membership rows, and ACL/ledger rows key on
`(principal, lifecycleUid)` (dash-form `<owner>-<actor>-<lifecycleUid>`), terminal retirement
records per-stream sequence cutoffs before an alias is reused, and a same-name successor
inherits none of its predecessor's pending state: its consumers start after its OWN
activation frontier (which is ≥ the predecessor's retirement cutoff), the cutoffs bound the
predecessor's interval, they are never the successor's start.

**Durable backstop (§4).** The per-subscriber durable copy is a delivery contract, not a pinned
layout: each member has a private durable store, written on publish for a `durable` channel's current
members and, for an `@mention` on a `live` channel, for each mentioned target authorized to read that
channel (its `allowSubscribe` covers it), so an authorized but offline target still receives it. The
agent holds **no content-bearing read** on this mixed store. A **trusted reader** (the server-side
delivery daemon) pulls each pending entry, re-authorizes `(instance, channel, message)` against the
member's **current read ACL** and, for `durable`-channel fan-out entries, its **membership interval**
(the message's CHAT sequence is `> joinCursor` and `≤ leaveCursor`; §7), not a current-member boolean,
so a pre-leave entry stays deliverable and a post-`leaveCursor` one does not,
and delivers each authorized copy to the member over an **at-least-once** handoff (its own
`dlv_<owner>-<actor>-<uid>` DELIVER consumer, carrying the same ack semantics, not a fire-and-forget publish). The trusted reader MUST NOT ack or
delete the backstop entry until the member has confirmed the copy was surfaced or handled (or it has
been transferred to an equivalent per-member at-least-once mechanism with the same ack semantics); on a
downstream nak, timeout, or crash before that confirmation, the entry remains pending and redelivers, so
a crash between the `dlv` handoff and the member surfacing the message cannot lose it, and `durable`
stays at-least-once end-to-end, not maybe-once. Content
for a channel dropped from the ACL, or (for a durable channel) left, is never surfaced (at-least-once for
the member within retention; **leave is a hard read boundary for the backstop**); a `live`-channel
`@mention` copy is delivered and `id`-deduped the same way. The read MUST run in this trusted component
the agent cannot bypass, because a self-bound consumer has no server-side per-message ACL/membership
filter. The store's stream/subject layout, the fan-out writer, the trusted reader, and the membership
registry are reference-implementation, not normative; a conformant deployment MAY realize the backstop
differently as long as the §4 guarantee and the §9 checks hold.

In the reference, fan-out writes a post on a concrete channel for every member whose row is that
channel or a family covering it, and each copy's `Nats-Msg-Id` is `<id>:<principal>:<lifecycleUid>`,
so the covering rows of one member collapse into one copy within the duplicate window. The
reference's INBOX and DLV streams share one operator-set limit, with the parts and bounds of the
history limits above, except that an age is at least 2 hours, their duplicate window. A subject there
is one member lifecycle. A new space starts with no age limit and 1000 copies per lifecycle, and setting
up an existing space leaves the limit as it is. They are two streams, so a change that stops partway can leave them on different limits. A member's `dlv`
subject keeps its newest copies up to that count (`discard=Old`), delivered ones included, and the
broker reports no discard. The trusted writer stamps each copy with the DLV count in force when it
wrote the copy, in a `Cotal-Delivery-Limit` header holding a whole number or `none`; the reference
reads the count at most 5 seconds before the write. A copy without the header comes from a writer that
predates it, whose count was always 1000, so its count is 1000. A client that receives a backstop copy
with its stamped count minus one, or more, copies still pending behind it SHOULD tell the member that
earlier copies may have been dropped, and that ACL-bounded history may still hold them; a copy stamped
`none` never does. The stamp is the count when the copy was written, so the notice covers evictions
under that count. Lowering the count, or an age expiring copies, removes copies without a notice to
the member; the reference shows the operator what such a change removes before it is made.

The absence of a usable receiver dedup key does not relax acknowledgement ownership: a
JetStream-consumed copy with `id: ""` that is surfaced or handled MUST be acknowledged
independently. The reference fan-out and transfer publishes carry no `Nats-Msg-Id` for an
`id: ""` message, so id-less messages are not publish-deduplicated on the durable plane.

Publishers MUST publish channel, unicast, and anycast delivery messages through JetStream and set
the JetStream message id to `CotalMessage.id` (`Nats-Msg-Id` on the wire). A JetStream publish is
an ordinary subject publish that the stream also captures, so the same message reaches core
subscribers live (§4 `live`) and is retained for history and the durable backstop in one publish;
the publish path is unchanged from v0.2; only the live *read* moves to a core subscription.
Ack/nak/term semantics apply to JetStream-consumed copies (history, DM, anycast, and the durable
backstop): receivers MUST ack only after a message has actually been surfaced or handled, MAY nak
transient failures, and MUST term permanently invalid messages. The at-most-once `live` copy is not
acked. A receiver still handling a copy MAY restart its ack wait with JetStream's in-progress ack
(`+WPI`). An ack or in-progress ack addresses the stream sequence rather than one delivery of it,
so a receiver SHOULD stop renewing a copy once its ack wait may have run out since the last
renewal, because the broker may by then have delivered that sequence to another consumer.

History on join uses the pinned single-filter `chathist_<owner>-<actor>-<uid>` consumer create above, bounded to
`allowSubscribe`; agents are not granted unfiltered Direct Get. DM and TASK MUST NOT enable Direct Get
because it would bypass the consumer-create deny that is part of the confidentiality boundary.

KV buckets are also streams and are pre-created:

| Bucket | Holds | TTL |
| --- | --- | --- |
| `cotal_presence_<space>` | presence (§6) | 6000 ms |
| `cotal_channels_<space>` | channel registry (§7) | none |
| `cotal_membership_<space>` | derived channel-membership feed (below), history 1 per key, maximum value 262144 bytes, no global byte reservation | none |
| `cotal_anycast_<space>` | anycast request records (§8.1), history 1 per key, maximum value 65536 bytes, no global byte reservation | none |

**Derived channel-membership feed (observability).** `cotal_membership_<space>` is a per-agent
(key = `card.id`) derived view of who is subscribed to each channel: the **union** of an agent's
`live` core-subscriptions (read by a privileged daemon from the broker's connection view) and its
`durable` memberships (the members registry), each value `{ live: string[], durable: string[],
observedAt }` with `live` keeping subscription patterns (wildcards) the consumer expands at read time,
and `durable` keeping a family row as recorded (`team.>`, §7).
The backing KV keeps one message per agent key and limits each serialized value to 262144 bytes. Its
global `max_bytes` is unlimited (`-1`), so creating an empty space reserves no file-store capacity;
the broker's actual file-store limit remains the aggregate storage bound.
It exists so an observer can show silent readers and `live`-channel membership without a broker-admin
credential in the dashboard tier; it is written by a scoped privileged daemon and read only by the
admin/observer profile and by the `deployer` and `teardown` operator credentials, which check a
manifest's channels for other members. It is **DISPLAY-ONLY and broker-derived**: it MUST NOT be an
input to any delivery, ACL, or authorization decision (authority for those stays the broker's
`sub.allow` and the members registry), and it is not part of the normative wire contract a client
must implement.

### 8.1 Anycast request status

This subsection applies in auth mode. There the delivery daemon (the `delivery` profile, Appendix
B) keeps a record of each anycast request that a holder took or that its sender cancelled, and a
holder takes each role request before it shows it. The sender gets a notice naming the holder that
took the request, can read its status, and can cancel it while it waits. An open mesh runs no
delivery daemon, so anycast there behaves as §4 says and a client MUST refuse a status read or a
cancel.

**Take.** A holder takes a request that its `svc_<role>` consumer delivered with a core request on
`cotal.<space>.take.<role>.<owner>.<actor>`, for its own role and principal, whose reply subject is
`<take subject>.reply.<uuid>`. The daemon drops a take with any other reply target unanswered. The
body is `{ from: { id, name }, seq, id, count, epoch }`: `seq` is the request's `TASK_<space>`
stream sequence, `id` is its `CotalMessage.id`, `count` is the broker's delivery count for the copy
the holder holds, 1 on the first delivery, and `epoch` is the stream's creation time as the holder
last read it: when it bound the consumer that delivered the copy, or again after a `mismatch`.
`from.name` is the holder's display name: well-formed UTF-16 on a single line of at most 128
characters, with no surrounding
whitespace, `/` or `\`. The daemon takes the role from the subject and the holder from the subject's sender tokens, and refuses
a body whose `from.id` is another principal or whose `from.name` breaks those rules. It reads the
request stored at `seq` with `STREAM.MSG.GET`, a leader-served read, and reads the stream's creation
time before and after it, retrying when a stream recreation fell between them. A take whose `epoch`
is not that creation time is refused `mismatch`, so a copy from a stream since recreated cannot
take a newer request stored at the same `seq`. It answers `{ ok: true }` or
`{ ok: false, code, error }`:

| `code` | Meaning | Holder |
| --- | --- | --- |
| `cancelled` | the sender cancelled the request | forgets its copy |
| `taken` | a take at this delivery count or a later one holds the request | forgets its copy |
| `gone` | nothing is stored at `seq`, and no cancel is recorded | forgets its copy |
| `mismatch` | the request at `seq` is not this role and id, the copy is from an older stream, or the take is malformed | reads the creation time again, naks its copy if that time moved, and forgets it |
| `busy` | the daemon could not decide the take now | keeps its copy and asks again later |

A holder MUST NOT show a role request until a take of the copy it holds returns `ok`, and it takes
a request only when it is about to show it, because a take ends the sender's chance to cancel. A
host that shows part of its inbox chooses what it will show first and takes only the requests in
that choice. A count, a wake or a preview never takes; only a read that shows bodies does. A holder forgets a
refused copy without acking it: a cancelled request is already deleted, and one another holder took
stays with that holder. After a `mismatch` it reads the stream's creation time again. When that time
differs from the copy's `epoch`, the consumer is on a recreated stream, so it naks the copy first and
the broker delivers it again at once, carrying the new time. A redelivery that mismatches again
carries the time just read, so it is forgotten and not nakked. It restarts the copy's ack wait
(`+WPI`) before it asks, and once the take returns it shows the request only if it still holds that
copy, with the same delivery count, an ack
wait that has not run out, and no reconnect since it asked. Otherwise it takes the newer copy. A
request taken and not yet shown when the holder's connection drops is taken again before it is
shown, and so is one whose take is still waiting for its answer then. A read after the drop asks
again on the new link rather than waiting on that take, and a take whose connection closes ends with
it. A holder asks for no take while its connection is down, so a read then is offered none of its
held requests, and once the connection is back the holder tells its host about each one again. A
held request keeps its place in the order the holder received its messages, so it is offered, and
once taken shown, where it arrived. A host that shows its inbox one batch at a time MAY fill a full
batch with role requests before the channel posts that arrived ahead of them.

**Record.** The daemon keeps one row per request in `cotal_anycast_<space>` (history 1, no TTL)
under the key `r.<epoch>.<seq>`, where `<epoch>` is the `TASK_<space>` stream's creation time
with every character outside `[0-9A-Za-z]` removed. A recreated stream reuses sequences, and its
requests never match an older row. A row holds the request's id, role and sender, read from the
stored request, and its state: `taken` with the taker, its delivery count and the time, or
`cancelled`. A row's first write is a create-if-absent and every later write is a CAS on its
revision, so a take and a cancel that race for one request cannot both win. A take is decided
against the row:

- With no row, the take wins and the row becomes `taken` by this holder.
- The same holder at the same count wins again, with no write.
- A higher delivery count moves the take to that holder. When the holder changes, the row keeps the
  previous one.
- Any other take is refused `taken`, and a take of a cancelled request is refused `cancelled`.

The daemon removes a row a day after it first finds the request off the queue, and never while the
request is queued. It checks every row when it starts and every ten minutes after.

**Restore.** A backup does not hold `cotal_anycast_<space>`: it is control state and comes back
empty. A restore recreates `TASK_<space>` with a new creation time, so no row written before it
could match a restored request. The restore brings back the requests the queue held when the backup
was made and recreates its consumers from checkpoints, so their delivery counts start over. A
request a holder had taken and not yet acked by then is on the queue again, including one that
holder finished and acked after the backup. The broker delivers it again at delivery count 1, as it
redelivers any request left unacked, and any holder of the role may take it and do the work again.
Its status reads `waiting` until that take. The redelivery is the work queue's own behavior, and the
records change only what the sender reads.

**Status and cancel.** A sender reads or cancels one of its own requests with the `anycastStatus`
and `anycastCancel` ops of the `delivery` control service. The arguments are `{ seq, id? }`: `seq`
is the stream sequence the send's publish ack returned, and `id`, when given, limits the answer to
that request. The caller is the authenticated subject sender, and a request whose stored or
recorded sender is another principal reads `unknown`. The reply `data` is one of:

- `{ state: "waiting", seq, role }`: the request is on the queue and has no row. No holder that
  follows this subsection has shown it, though it may sit unshown in a holder's buffer.
- `{ state: "taken", seq, role, taker: { id, name }, previous?, at }`: `previous` names the holder
  the take moved from, and `at` is the time of the take in milliseconds.
- `{ state: "cancelled", seq, role, removal }`: `removal` is `pending` while the daemon has not
  finished deleting the request, `deleted` once it has, and `already-gone` when the request had left
  the queue before the daemon could delete it: an earlier delete whose reply was lost removed it,
  the stream was recreated, or a holder that does not take acked it.
- `{ state: "unknown", seq }`: there is no row, and nothing of the caller's is on the queue at
  `seq`.

A cancel succeeds only while the request waits. The daemon creates a `cancelled` row with
`removal: "pending"`, reads the stream's creation time, deletes the request from `TASK_<space>` by
seq when that time still matches the row, and records how the request left. A cancel of a request a holder took is refused,
and its reply is the `taken` status, which names the taker. A cancel retried after a lost reply
finishes a removal the first attempt left pending. A third op, `anycastTakes`, answers
`{ takes: true }`. A holder whose take finds no responder calls it to tell an older daemon, which
answers the op as not supported, from a daemon that is not running.

**Notice.** When a take wins with no row, and when a take moves to a different holder, the daemon
sends the request's sender a DM from its own principal. The message has a text part and a data part
`{ "cotalAnycastNotice": { seq, role, event, taker, previous? } }`, where `event` is `taken` or
`redelivered`. Its `id`, which is also its JetStream message id, is `anycast.<epoch>.<seq>.<rev>`
for the row's transition counter `<rev>`, so a resend after a crash is dropped as a duplicate. The
row keeps the notices it owes until each is published. A receiver MUST treat a DM as a notice only
when its sender is the principal that holds the space's delivery lease; a DM from anyone else that
carries the data part is an ordinary DM. The check is on the principal and not on the lease's
incarnation, because a daemon that restarts sends the notices an earlier run left owed. A notice is
not a question and needs no answer.

**A holder that cannot take.** On an auth mesh a holder keeps a role request unshown and asks again
later when its take gets no answer, a `busy` answer, an older daemon's answer that the take rail is
not supported, or a broker permission error, which means its credential was minted before the take
grant existed. It reports the cause to its operator: a delivery daemon to restart at this version, a
seat to respawn for a current credential, or no daemon running. A held copy is renewed only when the
holder asks again, so its ack wait can run out, and the broker then redelivers the request.

**Limits.**

- A take records that the holder's connector was about to show the request. It does not record that
  the agent read it, started it or finished it. Anycast does not follow work to its end; a workflow
  run (§14) does.
- The daemon cannot verify a delivery count, so a holder of the role can claim a later delivery and
  move a take to itself. The sender then gets a `redelivered` notice.
- A holder whose connector predates this subsection shows requests without a take. Such a request
  reads `waiting` until that holder acks it and `unknown` after, and a cancel can find it
  `already-gone`.
- A seq names whatever the queue stores there now. After a stream recreation a bare seq can name a
  newer request from the same sender; giving `id` rules that out.
- JetStream deletes a message by stream and seq, with no condition on the stream's creation time.
  If `TASK_<space>` is deleted and recreated between the daemon's read of that time and its delete,
  the delete removes whatever request the new stream stores at that seq, and the cancel reads
  `deleted`. Before deleting or recreating the stream, an operator stops every delivery daemon
  that serves the space and waits until each has exited and its broker connection has closed, so no
  delete is still in flight, then starts the daemon against the new stream. A removal left pending
  then reads `already-gone`.

---

## 9. NATS + JetStream security and authorization

**On by default.** A space is provisioned with decentralized JWT auth. Open unauthenticated
dev mode is available but out of scope for the security claims here. *(Informative
operator-facing views of this section: [docs/identity-and-auth.mdx](docs/identity-and-auth.mdx),
[docs/channels-and-permissions.mdx](docs/channels-and-permissions.mdx); the threat model is
[docs/security.mdx](docs/security.mdx).)*

- **Account = space, user = agent.** A space is one NATS account. The **broker's** operator signs
  the account; an account signing key mints per-agent user JWTs. A broker (one nats-server trust
  root: one operator, one system account) MAY host several spaces — one account per space, every
  account signed by that one operator. Broker trust is therefore per-broker, never per-space: a
  space owns only its own account and references the broker's operator, and rotating or replacing
  broker trust is intrinsically broker-wide - it affects every tenant on the broker at once and
  cannot be scoped to a single space.
- **Profiles are default-deny allow-lists.** Subject, stream, durable, and KV names are built
  from the same builders as §3 and §8. Exact profile shapes are in Appendix B.
- **An agent's channel scope is three concepts**, each a list of channel names or wildcard
  subtrees (`team.>`): `subscribe`, the active read set, the channels it subscribes to at boot
  (now native core subscriptions; mutable at runtime by direct subscribe/unsubscribe with no
  mediation); it MUST be a subset of `allowSubscribe`, and a pattern in it is a subset only when one
  `allowSubscribe` entry contains it (`perm.*` does not contain `perm.>`). `allowSubscribe`, the read **ACL**, the
  channels it MAY read (default = `subscribe`), minted as native `sub.allow` subscribe grants over
  `cotal.<space>.chat.*.*.<channel>` (wildcards preserved, so an open ACL needs no enumeration) and
  as the matching per-channel history-consumer create grants. `allowPublish`, the post **ACL**,
  the channels it may publish to; **default-deny** (a chat publish grant is minted only for a
  declared channel).

Every grant below is keyed on the agent's **principal** `<owner>.<actor>` (§2), except the reply
inbox, which is keyed on the **connection** `<connId>`: the connection nkey (static mode) or the
client-chosen nonce (user mode, §9). This is the one place the wire identity and the connection
credential diverge (§2): the principal keys subjects/durables/presence; the connId keys the inbox.

| Profile | Application publish | Read surface | Notes |
| --- | --- | --- | --- |
| `agent` | own `chat.<owner>.<actor>.<ch>` for each `allowPublish` channel (post ACL, default-deny), `inst.*.*.<owner>.<actor>`, `svc.*.<owner>.<actor>`; endpoint request forms per minted capability (`ep.one`/`ep.all`/`ep.inst` with the capability's authz-mode/target pattern, caller triple `<owner>.<actor>.<uid>` pinned; `describe` by default; `epj` submissions for journaled capabilities; §13.9); own presence key | own `_INBOX_<connId>.>` + own endpoint reply rail (`ep.reply.*.*.*.<owner>.<actor>.<uid>.*`, exact arity); channel live tail via native `sub.allow` subscriptions to `chat.*.*.<channel>` per `allowSubscribe` (wildcards preserved); `STREAM.INFO` (stream-level state only, no body read) on `CHAT` and the world-readable KVs, plus `TASK` when the credential carries a `role`; presence and channel-registry KV watches, including create/info of their client-managed ordered consumers on those two streams only, and no `CONSUMER.DELETE` on either; CHAT history via single-filter `chathist_<owner>-<actor>-<uid>` creates, one per `allowSubscribe` channel (ACL-bounded); own lifecycle-scoped `dm_…`/`svc_…` bind-only; durable backstop via own bind-only lifecycle-scoped `dlv_…` DELIVER consumer, **no** grant on the mixed pre-auth fan-out stream; granted record-key/event-topic read subtrees per capability | read bounded by `allowSubscribe`; no watch-consumer delete: the broker removes a watch's ordered consumer at its inactive threshold, and a client treats a refused delete of its own watch consumer as that outcome (Appendix B); durable copies re-authorized (current ACL + membership + lifecycle) by the trusted reader before the `dlv` handoff; no Direct Get; DM/TASK/DLV create denied. The `manager-caller` view narrows this endpoint control set to `ep.inst.manager.<managerInstanceId>.<command>` only, plus the exact instance `describe`, contract reads, own reply/progress rows, and own inbox. It carries no agent messaging, delivery, class, or scatter rows. When the actor holds `spawn`, the view also carries `status`, `models`, `resolve-cwd` and `ps` on that instance. The first three are the reads a launch there needs. `ps` lists the caller's own agents, because the manager bounds it to the caller's owner unless the caller holds `admin`. A view without `spawn` gains none of them, and no view gains `slots` from `spawn`. |
| `observer` | none | chat, CHAT history, presence, channel registry | DMs invisible |
| `admin` | none | whole space live tap plus DM history | plaintext god-view, opt-in |
| `transfer-writer` | one carried transcript's chunk and meta subjects in one manager instance's transfer bucket (§8) | a last-message Direct Get on those two subjects | one-shot per CLI call, minted after the transcript is hashed; no consumer and no other object |
| `dm-reader` | `CONSUMER.INFO` and `CONSUMER.MSG.NEXT` on its own lifecycle-scoped `dm_…`, and its `$JS.ACK` subtree | own `_INBOX_<connId>.>` | no message publish, presence, channel, CHAT, `dlv_`, `$JS.API.INFO` or stream INFO row; bound to the bearer's expiry |
| `transfer-reader` | the stock delete marker on its own transfer bucket's meta subjects | its own `OBJ_cotal_xfer_<space>_<instanceId>` stream: info, message get, its push consumer, purge | one-shot per `transcript-receive` or sweep; no other instance's bucket |
| scoped host profiles | least-privilege per function | least-privilege per function | The former allow-all `manager` is **deleted**; its host duties split into scoped, single-function creds (`supervisor`, `provisioner`, `delivery`, `membership-rw`, `operator`, `purger`, `teardown`, `channel-writer`, …). No allow-all credential exists. Appendix B summarizes them; the concrete grant lists are **generated from the §13.9 ownership matrix** into `provision.ts` (the matrix is the single oracle; `provision.ts` is its artifact, Appendix B its summary). |

A Manager with explicit `execution: "none"` MUST construct no Runtime or connector workload.
It MAY use the existing genuine auth-local authority path, or the existing pooled signerless
remote authority path. These authority paths MUST NOT be mixed. A local NONE instance MUST
require its hosting user-auth marker, signing trust and user-mode mesh record before binding.
Its persisted native instance, serve principal, owner checks and registration gates are unchanged.
Execution capability MUST be immutable per instance; an existing unclassified local identity or
retained seat/turn inventory MUST refuse NONE admission. Startup MUST NOT silently convert a
runtime instance or adopt a sibling's workload.

NONE status MUST report no runtime, custody, class spawn, workflow hosting or terminal sessions.
Launch and workflow requests MUST refuse before allocation or workload effects. Launch reads
MUST refuse before resolving a Runtime, connector or host path. Targeted requests retain normal
lifecycle-mapping validation; an instance with no managed mapping does not fabricate a target.
NONE MUST refuse model inspection before connector or harness resolution. Caller-driven persona,
history purge, transcript transfer, preservation, resume, turn-yield, cancellation and self-stop
writes MUST refuse before their effects. NONE MUST NOT start a transcript receiver or sweep.
Native registration, lease, credential renewal, session-ledger and goal maintenance MAY still
write their own control state autonomously; NONE is not a read-only broker or filesystem mode.
Native ps remains this instance's managed map, not a presence or HTTP-session inventory.

The `resolve-cwd` launch read is limited by execution mode. A manager whose runtime delegates
seats outside its host MUST refuse it with `failed-precondition` before any filesystem lookup.
The refusal MUST be independent of the requested path's existence, type and symlink target,
and MUST NOT return a host name or canonical path. Such a runtime cannot use a manager-host cwd.

On such a runtime, a caller-supplied persona `config` override MUST be refused before resolving,
checking or reading its host path. Every path shape MUST receive `failed-precondition` and the
same message: `delegated execution cannot use a manager-host persona config path; select a named persona`.
This applies to `resolve-agent` and `spawn`, including starts submitted through library or workflow
hosts. A named persona remains subject to the manager's catalog visibility rules. A validated
resolved manifest may use its own host-materialized persona resource; public requests MUST NOT
be able to supply that internal resolved authority as a config override.

DM and TASK confidentiality, and the CHAT read boundary, close the leak paths:

1. Replies and pull responses ride a per-connection inbox prefix, `_INBOX_<connId>.>`, which
   `sub.allow` permits alongside the agent's channel read grants (next item) and nothing else. In user
   mode the client picks `<connId>` (a nonce) and the callout scopes the inbox to it, so a
   wildcard-inbox subscribe that would sniff peers' DM deliveries is refused. Re-authorized durable
   copies do NOT ride the inbox; they ride the agent's own lifecycle-scoped `dlv_<owner>-<actor>-<uid>` DELIVER consumer
   (item 5, §8).
2. **Channel live reads are bounded by `sub.allow`.** `allowSubscribe` is minted as native subscribe
   grants over `cotal.<space>.chat.*.*.<channel>` (wildcards preserved); the broker refuses, per
   subscribe, any channel subject outside the ACL. There is no per-channel consumer name to confine,
   so an open ACL (`team.>`, `>`) grants selective single-channel join with no enumeration and no
   read-breakout. A `>` grant is read-all chat in the space by design (credential compromise reads
   all chat), so it suits trusted/local deployments, not least privilege.
3. A consumer create on the bare/multi-filter subject is not ACL-constrainable, so the provisioner
   pre-creates `dm_<owner>-<actor>-<uid>`, `svc_<role>`, and the per-member `dlv_<owner>-<actor>-<uid>` handoff
   durables. Agents bind their own `dm_…-<uid>`/`svc_<role>`/`dlv_…-<uid>` only (never
   create); the mixed pre-auth fan-out store is read by a trusted reader, not the agent (§8, item 5).
   Those bare/multi-filter create forms are not granted to agents (default-deny), with explicit
   create-denies on `DM_<space>`, `TASK_<space>`, and the `DLV` stream; on `CHAT_<space>` the only
   consumer-create an agent holds is the pinned single-filter history create (next item), so a broad
   CHAT create-deny is intentionally absent: it would also deny that pinned create.
4. CHAT history reads are bounded to `allowSubscribe`: a consumer create on the extended subject
   `$JS.API.CONSUMER.CREATE.<stream>.<name>.<filter>` carries a single filter the server pins to the
   request body, so an agent is granted exactly one such create-subject per `allowSubscribe` channel
   and can read history of no other channel. The unfiltered Direct Get grant is not given to agents.
5. **The durable backstop is read by a trusted reader, not the agent.** The agent holds no
   content-bearing read on the mixed pre-auth fan-out store; a trusted reader (the server-side delivery
   daemon) MUST re-authorize `(instance, channel, message)` against the member's current read ACL and,
   for `durable`-channel fan-out entries, its current membership, before handing the authorized
   copy off to the member's own lifecycle-scoped `dlv_<owner>-<actor>-<uid>` DELIVER consumer:
   broker ownership of an inbox ("this is agent A's") is not authorization, since the store can hold
   messages for channels A has since dropped from its ACL or left, and a self-bound consumer cannot
   filter per-message on membership. Fan-out-on-write is routing, not an authorization check; for a
   durable channel a `leave` is a hard read boundary on the backstop. History/backfill reads are instead
   self-served and bounded by the current read ACL (the pinned single-filter create above), consistent
   with the live read. An `@mention` durable copy is written only to a target authorized to read the
   channel, so `mentions` cannot carry content outside a target's read ACL.
6. **"Current read ACL" is the effective broker-accepted credential.** An ACL narrowing takes effect
   when the credential/permissions are updated and enforced by the broker (re-mint / reconnect /
   revocation), not as an instantaneous global value; until then an existing broad credential remains
   broad. Both the broker `sub.allow` checks and the trusted-reader re-checks are evaluated against that
   effective credential.

This binding provides containment and authenticity under a single trusted broker: an agent
can emit only as itself and only to its declared `allowPublish` channels, and read only its own
DMs and chat *content* within `allowSubscribe` (and, for `durable` content, its current
membership), enforced by the server. It does not provide
non-repudiation, does not survive an untrusted relay, and DMs are plaintext to the broker and
to `admin`. The read bound is on **content**, not metadata: agents hold `STREAM.INFO` on CHAT
(for the join watermark, the recall drop-marker, and channel-list counts), so a `subjects_filter`
query leaks chat subject *metadata* (channel names, sender ids, and per-subject counts) for
channels outside `allowSubscribe` (channel names are already public via the registry). A
credential minted with a `role` also holds it on TASK, under the same gate as that role's
`svc_<role>` bind grants, so a `subjects_filter` there leaks anycast subject metadata
(`svc.<role>.<owner>.<actor>`, who anycast which role) to an operator-chosen role profile. DM,
DLV, and EPC carry **no** agent `STREAM.INFO` grant: `subjects_filter` is a request-body field no
ACL can narrow to counts alone, so INFO there would enumerate who DMed whom, and no agent-side
reader needs it (§13.1 reaches DM and DLV by durable name, EPC by subject-scoped `DIRECT.GET`).
Hiding the remaining metadata is deferred strict-containment work.
See [docs/security.mdx](docs/security.mdx).

**Consumer-delivery confused deputy on the read grants.** A JetStream consumer delivers stored
bytes to a **caller-chosen destination the broker does NOT confine to the requester's
`pub.allow`**: a push consumer's `deliver_subject`, and a pull `MSG.NEXT`/`DIRECT.GET`
request's reply subject, are set in the request body and the server's internal client publishes
there regardless of the requester's publish permissions. The v0.3 read grants above,
CHAT-history `CONSUMER.CREATE`, the bind-only DM/DLV/TASK `MSG.NEXT`, and the KV watch creates
(Appendix B); therefore let an agent redirect content it may legitimately READ onto a subject
it may NOT publish to: e.g. replay a stored CHAT message whose `from.id` is another sender onto
`inst.<victim>.<thatSender>`, where the recipient derives the DM sender from the subject and
surfaces it as a genuine DM from a principal who never sent it. The §13.9 "Mediated reads" rule
applies here: **no untrusted agent holds a raw consumer `CREATE`/`MSG.NEXT` or `DIRECT.GET` on
`CHAT`/`DM`/`TASK`/`DLV` or the KV buckets**; those reads are served by the trusted
reader/mediator (§8) onto the agent's own confined rail. Which of these read paths require
mediation and which are provably safe depends on whether a redelivered message retains its
original captured subject and how the receiver's subject-derived kind check (§12) then
classifies it; the reference implementation determines this by test and pins the exact grants.
On the v0.3 rails without this mediation, read containment holds only against a *conforming*
client; the broker does not enforce it.
See [docs/security.mdx](docs/security.mdx).

---

## 10. Connection and onboarding

Join link grammar:

```text
cotal://[token@]host[:port]/space[?channel=a,b]      plaintext
cotals://[token@]host[:port]/space[?channel=a,b]     TLS required
cotal://user:pass@host/space                         user/password auth
```

- Default port is `4222`.
- `channel` and `channels` query parameters are equivalent comma-separated channel lists.
- Credentials in `userinfo` are parsed out and passed to the NATS client as connect options;
  they are not left inside the server URL.
- Bare `userinfo` with no `:` is a token. `user:pass` is username/password.
- `cotals://` means `nats://host:port` plus TLS-required connect options.
- Credentials (`creds`) are mutually exclusive with token and username/password auth.
- A client MUST set `inboxPrefix` to `_INBOX_<connId>` before any request, pull consumer, or KV
  watch operation, where `<connId>` is the connection identifier (the connection nkey in static
  mode; the client-chosen nonce in user mode, §2/§9), NOT the owner+actor principal, which the
  client may not know pre-connect.

Authenticated onboarding has two bindings. **Out-of-band credential minting** provisions a per-agent
credential ahead of connect (the static path). **Auth-callout onboarding** validates a user bearer at
connect time and mints the scoped data-account JWT then (user mode, §2/§10): the client presents a
deny-all sentinel credential plus its bearer, the callout derives the owner+actor principal and grants,
and re-binds the connection into the data account. A space's callout requests are served inside that
space's dedicated auth account, so a responder holds one subscription per auth account it serves. One
process MAY serve several spaces, holding one such subscription and one set of signing keys per
space; a response for a space MUST be signed only with that space's keys, and a bearer MUST be
validated only against the issuer and audience of the space whose request it arrived on. A process
MAY likewise serve several spaces' exchange and JWKS, as one full auth service per space: each
space's exchange MUST be served on listeners of that space's own and mint only from that space's
keys and ledger, and each space keeps its own authority plane (§13.13). The owner-token *derivation* (how a bearer maps to
an owner token) is a pluggable identity adapter (any OIDC/IdP via a thin bridge), not fixed by this
contract; the callout *mechanism* and the resulting grants are. From v0.4 every minted connection also carries its **lifecycle UID** (§13.1): the manager
mints it for managed agents at provision, and the callout/exchange attaches it as a claim at
connect for user-mode connections, so the caller-UID token in every endpoint-rail grant is
authority-assigned, never client-chosen. Every bearer additionally carries its incarnation's
**root credential id** (`act.credentialId`, §13.1). The exchange ensures the ACTIVE
`cred.<lifecycleUid>.<credentialId>` ledger row exists BEFORE the bearer bytes are released
(the row durable first, the issuance-gate finalize CAS, the lifecycle head's current-root CAS
last), and the connect authority proves the presented id against the LIVE row, leader-served
from the shape-proved primary auth store: the row MUST be `active`, unexpired, and bound to the
connecting principal and lifecycle, and a root-issued credential MUST additionally equal the
lifecycle head's current root credential. A claimless bearer, a revoked, expired, or absent row,
and an unreadable authority store all DENY the connect. The root credential is
**incarnation-wide**: ONE `cred.<lifecycleUid>.<credentialId>` row per incarnation, re-stamped
(the same id) on every exchange for the incarnation's lifetime, never a fresh id per exchange.
Revoking that one row is the per-credential revocation lever and denies EVERY bearer of the
incarnation at the next connect (deny-new; evicting an already-live connection is the lifecycle
barriers' job, §13.1). Because the id is incarnation-stable, a crash after the head's current-root
CAS re-exports the SAME id on the next exchange (that id IS the incarnation's live root, so there
is nothing unobserved to revoke); the only pre-release crash window is a durable active-but-
unstamped row, which the head-equality check denies. Rotating an incarnation's root credential is
exclusively a lifecycle barrier's job, never a bare re-mint. A bearer MAY carry a server-authored
**view** claim, selected only by the exchange and re-authorized against the live grant ledger at
every connect. The callout then mints the connection as that closed profile instead of `agent`.
Elevated views such as `admin`, `purger`, `channel-writer`, and `deployer` remain human-only. The
narrowing `manager-caller` view MAY be minted by either the human or managed-agent exchange and is
served on both exchange faces. It adds no capability beyond the `status`, `models`, `resolve-cwd`
and `ps` reads §9 lists for a `spawn` actor's view. Its required `act.managerInstanceId` binds
every manager request to one exact live registered instance and is valid with no other view. The
exchange selects from read-only observations of manager issuance gates and service registrations.
It refuses zero or ambiguous candidates and refuses an unavailable owner-specific remote manager
rather than falling through to a co-located manager. An explicit selector must name a live candidate.
On the public exchange face, once the caller's credential and ledger actor grant have verified
(for a human exchange, the IdP JWT and the interactive actor grant; for an agent exchange, the
managed actor secret and its ledger row), a `manager-caller` request that fails only manager
selection MUST return its selection refusal reason on 401 without spending the per-peer probing
budget. The public face tracks those authenticated selection refusals in two rolling windows per
principal (derived owner for human exchanges, `owner.actor` for agent exchanges) keyed by
principal and request class (`pinned` when the request names `managerInstanceId`, `unpinned` when
it does not, never by the requested instance id): each class counts only its own selection
refusals and, once its window is full, refuses requests of that class (including ones that would
have succeeded) with 429 and that class's remedy before running manager selection, while leaving
the other class open. On the loopback face, an authenticated unpinned refusal caused by no live
manager or ambiguous candidates returns its own reason without spending the shared probing budget.
A trusted host composition MAY instead select its genuine local Manager up front for an unpinned
interactive human exchange. It MUST apply the same native local identity, open gate, serve principal,
registration revision and epoch checks, and MUST refuse an absent, unreadable or noncandidate local
Manager without reading or substituting remote candidates. This policy MUST NOT be request-selectable,
change explicit selectors, rebind managed or issued callers, or add command authority. Without the
host opt-in the default selection above is unchanged.
The auth service lists the same observations to a signed-in human at `POST /manager-instances` on
both faces: it verifies the same IdP proof, derives the owner the same way, and answers every manager
gate of that owner using the same candidate checks as selection. A listed candidate is registration evidence, not proof that the instance answers, and a
failed read is unavailable, never an empty list.
The `session-caller` view carries a required `act.session` claim `{endpoint, sessionId, epoch, exp}`
(exp in unix seconds) and is valid with no other view; `act.session` is valid only with it. It needs no
ledger scope: the redeemed §13.6 session grant is the authority. The human exchange accepts it only
together with a `sessionGrant` body (each without the other is a 400), and the trusted auth path MUST
leader-read the `session.<sessionId>` row from the dedicated sessions store before it signs. It MUST
refuse unless the row is `active`, `row.grantSig` equals the presented grant's signature, the row's
holder principal and lifecycle are the bearer's owner, actor and `lifecycleUid`, the endpoint and
serving epoch equal the presented ones, `row.exp` is in the future, and the serving manager's issuance
gate is a candidate for this owner with a process epoch equal to the row's. The stamped `exp` is the
row's, and the bearer TTL is bounded by it. The callout MUST re-run the same check, without the
signature comparison, against a fresh read at connect. It mints the `session-caller` profile for that
one session and binds the minted connection's expiry to the grant's, which is the static arm's
expiry, rather than the bearer's. The auth service's host-issuer connection performs both reads and
holds only `STREAM.INFO` and the leader-served `STREAM.MSG.GET` on `KV_cotal_sessions_<space>`: no
Direct Get and no write on that store.
The `transfer-writer` view carries a required `act.transferWriter` claim `{instanceId, hex}` and is
valid with no other view; `act.transferWriter` is valid only with it. It needs ledger scope `admin`,
the scope of the `transcript-receive` row it serves. The human exchange accepts it only together with a
`transferWriter` body (each without the other is a 400), and the callout mints the `transfer-writer`
profile for that one object of that one instance's transfer bucket (§8).
On a public exchange face, `channel-writer`, `channel-purger`, `manager-caller`, `session-caller`, and
`transfer-writer` MAY be issued. `admin`, `purger`, `deployer`, and `manager-service` MUST remain loopback-only. A
managed-agent secret exchange MUST refuse every view other than `manager-caller`.
The service-only `service-endpoint` view (§13.1) is not selectable on either ordinary exchange
face. Its dedicated authority path checks native service custody rather than an enrolled actor row.
The `dm-reader` view needs no ledger scope and MUST remain loopback-only. It rides only a
purpose-bound IdP proof. The proof carries the bridge's dedicated `dmReaderAudience`, which is
explicit configuration and MUST differ from the ordinary audience. It also carries
`purpose: "dm-reader"` and the `actor`, `lifecycle` and `space` claims. The exchange MUST refuse
unless the request names `view: "dm-reader"`, `actor` equals the requested actor, `lifecycle`
equals the current ledger row's lifecycle UID, and `space` equals the bridge's space. Every other
door that takes a human IdP proof MUST refuse any token carrying a `purpose` claim. At this release
those are the other `/exchange` forms, `/manager-instances`, `/manager-service-authority`, the three
chat-session routes, `/tool-result-human-proof/verify` and `/tool-result-consent/issue`. The
bearer's expiry, and with it the connection's, is bounded by the proof's.

**Connect limits.** The auth service MAY limit connects per actor and per owner, charging a bearer
whose signature verifies to the principal it names even after it expires, and per client host for a
bearer that does not verify. Buckets are per space: a process serving several spaces MUST keep a
separate set for each, so a connect to one space never spends another space's allowance. A callout that refuses a connect for a limit MUST answer with a signed
denial whose reason starts with `RATE_LIMITED retry-after <n>s`, and MUST do so before it reads the
ledger or records an issuance. While an actor or its owner is limited, an exchange for that actor
MUST answer 429 with the body `{code: "RATE_LIMITED", retryAfterSeconds, limit, error}`, where
`retryAfterSeconds` is a positive integer and `limit` is `"actor"` or `"owner"` for the bucket that
ran out, and a `Retry-After` header carrying the same number. It MUST NOT count that answer against
a refused-exchange budget. A client that receives it SHOULD NOT exchange or connect as that actor
again until those seconds have passed.

**User-mode chat sessions.** The auth service is a minting authority for chat sessions only
(§13.1). A signed-in human opens one with a fresh IdP proof and the SHA-256 of a holder secret the
client keeps in memory. The service derives the owner, draws the actor `chat_<26 base32>` and a
fresh lifecycle UID, and pins the human's own `cli` grant: its lifecycle UID, head revision and the
RFC 8785 digest of its row. The session's read and post lists MUST be contained in that grant, and
its lifetime is at most 8 hours and never extended. A request that names an owner, actor, UID,
scope, view or profile refuses. The holder index `chatholder.<owner>.<holderHash>` is created
before any lifecycle write and only advanced by revision-pinned CAS (`reserved`, `provisioning`,
`active`, `retired`), so a retry with the same holder hash converges on the same session and a
retired hash never admits again. A `pending` session row authorizes nothing. Each bearer exchange
presents the holder secret. Its TTL is at most the least of 900 s, the IdP proof's remaining life
and the session's remaining life. The bearer is released only after the session row, the holder
index, the parent pin and the session's own lifecycle head and root credential are read current
both before and after signing. The callout mints a chat principal as the `agent` profile with the
session's lists and no role, capability, endpoint capability or view. It refuses a chat bearer
that carries a view or a scope, or whose row is not `active`, names another lifecycle, holds a
moved parent pin or has a holder index that is not `active`. Retirement settles the session's
lifecycle through the §13.1 barrier before the per-owner quota row `chatowner.<owner>` releases its
slot. The auth service's sweep retires expired sessions the same way. It acts on an unfinished
admission only after a 60 s quiet period, which is never the evidence: it fences the holder index
first, drives the bound UID to a terminal gate, and releases the slot last. A pass makes at most 256
of its own reads, runs at most 4 retirements at once and never waits for one. A retired row is
compacted 24 hours after its expiry, once the UID's footprint is removed again. The routes are
`POST /chat-session/open`, `/chat-session/bearer` and `/chat-session/retire`, on both exchange
faces, with the same IdP proof check and the same capability rule as `/exchange`. Every refusal on
them carries its effect outcome: `not-executed`, `executed` or `unknown`.

---

## 11. Versioning and extensibility

- Wire contract version is v0.2 as advertised today. `AgentCard.protocolVersion` (§6) carries
  this string. The two v0.3 binding revisions (channel live delivery and owner+actor identity,
  see the header) and the **v0.4 endpoint control surface** (§13) are the normative targets the
  reference implementation is converging to. The control surface is an intentional **hard
  cut on the pre-1.0 line** (§13.11): the v0.3 control grammar and envelope are removed from
  this contract, not dual-served, a breaking revision, permitted pre-1.0, shipping under an
  explicit new version marker per this section's rule; the marker is the disjoint endpoint
  subject grammar and versioned envelope. The advertised `protocolVersion` bumps to `0.4` when
  the control-surface migration completes (one campaign, one merge); a version string is not a
  per-surface cutover claim. **`1.0` is deliberately deferred**: it is a stability declaration
  to outside implementers, made separately once the contract has settled (further pre-1.0
  arcs (presence/addressing, multi-space, federation) may still break the wire). **The wire `protocolVersion`
  is the compatibility signal**; dated document snapshots (below) are navigation artifacts, not
  negotiation; an implementation MUST NOT treat a document date as an interop key.
- **v0.5 (workflow runs, §14) is an additive revision.** It adds a per-space stream, four core
  record kinds, a per-run grant family and a normative language reference, and changes no existing
  kind, subject, grant row or shipped datum; a participant that ignores §14 conforms to v0.4
  unchanged. Two versions ride it and they are deliberately distinct: the wire `protocolVersion`,
  which targets `0.5` when the plane is served, and the language's own `languageVersion` (§14.2),
  which is bumped when a program's MEANING changes and is pinned per run, so a language revision
  never forces a wire revision and a wire revision never invalidates an open run.
- v0 has no in-band capability negotiation. Deployments MUST agree on the binding and
  version out of band. A participant advertises the version it speaks via
  `AgentCard.protocolVersion` (§6) as a one-way change signal, optional before the v0.4
  marker, MUST from v0.4 (§6, §13.11); v0 defines no behavior on a mismatch beyond rejecting
  messages it cannot parse.
- **A non-additive discovery change is an out-of-band deployment cutover, and it rolls out
  CALLER-FIRST.** A discovery change is non-additive when an unamended client that ignores it per
  the unknown-field rule below would then behave in a way the change exists to prevent — for such
  a change, ignoring is not a safe default and no default value repairs it. Every caller in a
  deployment MUST implement the new version's rules BEFORE any responder in that deployment
  registers or describes at that version. The two halves SHOULD therefore ship in **separate**
  releases — the caller side first and adopted across the deployment, the responder's emission only
  after — and shipping them in one release does not make a deployment safe, because **a release is
  not a deployment**: an already-running caller is unchanged by whatever a new artifact contains,
  so the order of two source edits says nothing about the processes on the wire. This rule exists
  because the preceding one leaves a responder no way to detect the hazard itself: with no in-band
  negotiation and no caller version on the wire, a responder cannot tell an amended caller from an
  unamended one, so the obligation rests on the deployment rather than on either participant. The
  observable marker is the discovery protocol's `protocol.v` on the registered service record
  (§13.7) — "has any responder cut over" is a checkable registry property, while "has every caller
  adopted" is exactly the out-of-band agreement this section already requires. **The residual is
  real**: a deployment that cuts a responder over early exposes its unamended callers to whatever
  the new version exists to prevent, and within v0 nothing in band detects it. Closing that needs
  negotiation v0 does not have, and the v1 marker below is where it belongs.
- New message families, subjects, and routing kinds are added in the core contract,
  generalized for all deployments, not in one example.
- Receivers MUST ignore unknown object fields and MUST NOT treat an unknown field as an
  error.
- A future v1 MUST either keep v0 subjects backward-compatible or use an explicit new
  version marker in subjects, credentials, or deployment config.

**Document snapshots.** Published revisions of this document are dated snapshots
(`YYYY-MM-DD`, the **Last updated** date above): the current revision is canonical, and a
superseded one stays retrievable from the repository history (the git history and tagged
releases of `SPEC.md`), so a client built against it can still be audited. The snapshot
date advances on any normative change; the wire `protocolVersion` moves only per the
change process below.

**Change process.** This document is the change-control point: a change lands here first,
generalized into `core`, and the reference implementation follows. Additive changes (a new
optional field, a new namespaced `Part.kind`, a new subject) are backward-compatible and ship as
a minor bump, since receivers ignore what they do not recognize. Changing the meaning of an
existing field or subject, or removing or renaming one, is breaking. **Pre-1.0**, a breaking
change ships as a minor bump of the v0.x line under an explicit new version marker in
subjects, credentials, or deployment config (the v0.4 endpoint grammar is such a marker);
**post-1.0**, it ships as a major bump. `1.0` itself is a stability declaration, made
deliberately and separately from any wire change.

**Extension namespacing.** Core `Part.kind` values, `meta` keys, and `tags` are bare and reserved
to this spec (`text`, `data`, `artifact`, and future core additions). A non-core extension MUST namespace its
custom `Part.kind` values and `meta` keys reverse-DNS, under a domain its author controls, e.g.
`{ "kind": "com.acme.snapshot" }` or `meta["com.acme.region"]`; Cotal's own non-core extensions
use `ai.cotal.*`. This keeps third-party names from colliding with each other or with future core
names, with no central registry.

Reserved future work: signed envelopes, `did:key` identity, auth-callout bootstrap tokens,
manager profile scoping, and federated/untrusted relay bindings. (Revocation/TTL for minted credentials is no longer future work on the control
surface: v0.4 defines it normatively via the credential ledger and the lifecycle barriers,
§13.1.)

---

## 12. Conformance

*(An informative build-order walkthrough of this checklist is
[docs/build-a-client.mdx](docs/build-a-client.mdx).)*

A conformant authenticated NATS client MUST:

1. Use one stable principal `<owner>.<actor>` as its wire identity everywhere: subject sender
   tokens (§3), `from.id` (§5), presence key (§6), durable names (dash-form, §8); and treat the
   connection credential (nkey) as distinct, keying only its reply inbox (§2).
2. Publish only on subjects whose sender tokens are its own principal `<owner>.<actor>` (§3).
3. Publish delivery messages as UTF-8 JSON through JetStream with `msgID = id` (§8).
4. Set exactly one routing field on each delivery message (§5).
5. Reject any received delivery message whose `from.id` does not match the subject sender, and whose
   subject `<owner>` is not a well-formed principal owner token: a subject that split-parses but
   carries a non-owner in the owner slot (e.g. a raw nkey, an old-shape alias) MUST NOT be surfaced
   as a delivery (§3, §5).
6. Derive delivery kind (channel/dm/anycast) from the subject, not payload routing fields (§4).
7. Ack only surfaced/handled messages and terminate permanent anomalies (§4, §8).
8. Write only its own presence key on the heartbeat interval (§6).
9. Set the per-instance inbox prefix before transport operations (§10).
10. Treat unknown fields as ignorable (§11).
11. Resolve a channel's effective delivery class (`live`/`durable`) from channel config, not from a
    deployment assumption, and use one resolution across live join, durable fan-out, history read,
    and membership surfacing (§4, §7).
12. On a `durable` channel, tolerate the at-most-once `live` gap and catch up via the durable
    backstop; deduplicate by `id` across the live, backfill, and durable copies (§4, §8). Receiver
    deduplication MUST NOT coalesce copies solely because `id` is the empty string (§4).
13. Join and leave a channel's **live** subscription by subscribing/unsubscribing under `sub.allow`
    with no privileged mediation; treat a live join as effective only once the broker accepts the
    subscribe, and drop it on a late permission refusal. On a `durable` channel, additionally establish
    durable membership via the privileged provisioner; if it cannot be established, report `joined live`
    with the durable backstop unestablished, never `joined durable` (§7, §9).
14. Bound history/backfill reads by the current read ACL, and re-authorize every durable-backstop read
    against the current read ACL (and, for `durable`-channel entries, membership) before surfacing
    content, treating a leave as a hard read boundary on the backstop (§7, §9).

Test vectors use these sample principals (`<owner>.<actor>`); `<ownerA>` = `u_aaaaaaaaaaaaaaaaaaaaaaaaaa`,
`<ownerB>` = `u_bbbbbbbbbbbbbbbbbbbbbbbbbb` (owner tokens are `u_` + 26 base32-lower, §2):

- Alice: `<ownerA>.alice`
- Bob: `<ownerB>.bob`
- Reviewer role: `reviewer`

Subject parsing. `parseSubject` **splits only** (§3): it recovers tokens by prefix and per-kind arity
but does NOT validate the owner token: a well-formed *split* is necessary, not sufficient, for a
subject to be surfaced as a delivery. The last row shows an old-shape alias that split-parses yet MUST
be dropped at the surfacing boundary (§9):

| Subject | Result |
| --- | --- |
| `cotal.main.chat.<ownerA>.alice.team.backend` | `kind=chat`, `sender=<ownerA>.alice`, `rest=team.backend` |
| `cotal.main.inst.<ownerB>.bob.<ownerA>.alice` | `kind=inst`, `sender=<ownerA>.alice`, `rest=<ownerB>.bob` (recipient) |
| `cotal.main.svc.reviewer.<ownerA>.alice` | `kind=svc`, `sender=<ownerA>.alice`, `rest=reviewer` |
| `cotal.main.ctl.manager.<ownerA>.alice` | no sender; v0 control subject, retired (§13.11): nothing serves it and it MUST NOT be handled |
| `cotal.main.chat.<ownerA>.alice` | no sender; malformed (owner+actor but no channel token) |
| `cotal.main.chat.UAQGWOEVJKMIO4WXSYOTLARXYOZTCXFK67JASEH6AFFFYK6FOPSKQCAD.team.backend` | split-parses (`kind=chat`, `owner=UAQ...QCAD`, `actor=team`, `rest=backend`) but MUST be dropped: `UAQ...QCAD` is not a principal owner token (§3, §9) |

Sample multicast message:

```json
{
  "id": "018f1d0a-0000-7000-9000-000000000001",
  "ts": 1710000000000,
  "space": "main",
  "from": {
    "id": "u_aaaaaaaaaaaaaaaaaaaaaaaaaa.alice",
    "name": "alice",
    "role": "planner"
  },
  "channel": "team.backend",
  "mentions": ["bob"],
  "parts": [{ "kind": "text", "text": "Can you review this?" }],
  "contextId": "ctx-1"
}
```

Sample unicast message changes only the routing field:

```json
{
  "id": "018f1d0a-0000-7000-9000-000000000002",
  "ts": 1710000001000,
  "space": "main",
  "from": {
    "id": "u_aaaaaaaaaaaaaaaaaaaaaaaaaa.alice",
    "name": "alice"
  },
  "to": "u_bbbbbbbbbbbbbbbbbbbbbbbbbb.bob",
  "parts": [{ "kind": "text", "text": "Direct note." }]
}
```

Interop scenario:

1. Provision a space and credentials for Alice and Bob.
2. Alice and Bob connect with inbox prefixes `_INBOX_<connId>` (per-connection, §2).
3. Both write presence and join `team.backend`.
4. Alice multicasts on `team.backend`; Bob receives with `kind=channel`.
5. Alice unicasts to Bob; Bob receives with `kind=dm`.
6. Alice anycasts to `reviewer`; exactly one reviewer receives with `kind=anycast`.
7. A late joiner joins `team.backend`; replayed messages arrive with `historical=true` and
   live-tail duplicates at or below the join watermark are ack-dropped.

---

## 13. Endpoint control surface (v0.4)

Everything on the mesh that serves structured commands (the manager daemon, the delivery
daemon, a wrapped MCP server, a third-party service) is an **endpoint**: a daemon that
registers a service identity, publishes its contracts, and answers `describe`. There is no
special-cased service in this contract: `manager` and `delivery` are endpoint names like any
other, and no subject or envelope in this section knows them. This section supersedes and
**deletes** the v0 control rail (`ctl.<service>.<owner>.<actor>`, `ControlRequest`/
`ControlReply`, the `self`/`manager`/`admin`/`delivery`/`delivery-admin` service tiers, and the
reserved `control.<instance>` subject). The cut is hard (§13.11): no v0 control subject,
envelope, handler, or grant survives, and a pre-cut control credential cannot reach a post-cut handler.

Layering: identity and transport are §2/§3, extended by the lifecycle identity below; §13.1
identity; §13.2 grammar; §13.3 envelope; §13.4 delivery contracts; §13.5 verbs; §13.6
composites; §13.7 contracts and discovery; §13.8 distributed guarantees; §13.9 authority
boundary; §13.10 receipts and signing anchors; §13.11 the hard cut; §13.12 the NATS binding;
§13.13 plane ownership; §13.14 conformance.

### 13.1 Lifecycle identity

The principal `owner.actor` (§2) is a **recyclable routing alias**: despawning an agent frees
its actor name, and a later spawn may legitimately reuse it. An alias is therefore never
sufficient *authority* identity on this surface. Two further identity components exist:

- **Lifecycle UID** (`lifecycleUid`, one token `[a-z0-9]{26,32}`, ≥128 bits of CSPRNG
  entropy in a fixed canonical encoding): an unguessable, never-reused
  identifier of one managed lifecycle under a principal. The UID is entropy, never order:
  no allocator counter exists, and what is durable and monotonic is only the never-used
  set. Before anything else, the minting authority (the manager for managed agents; the
  provisioner for endpoint daemons and operator credentials; the auth service for user-mode chat
  sessions only, §10) **reserves the candidate UID
  space-globally**: a create-only write of the reservation key `uid.<lifecycleUid>`
  (§13.7), never deleted for the life of the space. A create conflict burns the candidate
  and draws a fresh one (the alias head alone cannot reject the same UID under a different
  alias, and the `gate.`/`cred.` families key by UID alone, so uniqueness must be
  space-wide); a DEL/PURGE marker on a reservation is corruption, never reusable absence.
  A reservation carrying `kind: "native-service-uid"` is reserved for service custody. Its
  closed shape is `{ v:1, kind, native:{accountPublicKey,space,lifecycleUid},
  assignment:{owner,actor,endpoint,instanceId,duty:"serve"},
  creation:{operationId,requestDigest} }`. Its embedded UID must equal its key. The actor is
  `svc_` followed by the lowercase base36 encoding of the unsigned SHA-256 of RFC-8785
  canonical JSON `[1,accountPublicKey,space,owner,endpoint,instanceId,"serve"]`, padded to
  50 characters. Ordinary executors MUST refuse this reservation before mutation or
  idempotent root reuse and MUST NOT project it into an ordinary audit. Ordinary connect
  and currency paths also refuse it. No reservation creation, service credential release
  or terminal service transition is enabled by this reserved shape. Legacy ordinary audit
  bytes and their existing validation remain unchanged. Mixed-version writer exclusion
  and restore reconciliation remain prerequisites for any future service admission.
  A separate package-internal read-only service prerequisite accepts only the canonical active
  marked head, open native `gate.<uid>` generation1 and active exact root row, with all three
  `serviceCustody:{v:1,uidDigest}` mirrors matching the complete closed immutable reservation.
  Head `managerInstance` MUST equal reservation assignment `instanceId`, and native
  `processEpoch` is exactly1, independently of endpoint serving gate/witness epochs. Full
  configured account/space/UID/assignment, derived actor, root key/current stamp/holder,
  `sourceChain:["root"]` and unexpired millisecond root expiry are checked with sealed
  primary leader-served readers. Account identity is trusted host configuration: sealed readers
  do not attest the broker account or detect a cross-account pair of legitimately constructed
  handles. Missing or mismatched native gate evidence refuses even if an endpoint gate exists.
  Creating/cancelling states and their RootPlan recovery cannot
  authorize through this active-only reader. It confers no writer, bootstrap, abort, rotation
  or terminal authority and makes no atomic multi-key snapshot claim. Ordinary readers stay
  closed to every marked service row.
  A package-internal custody writer, reached in the product only through the service endpoint
  host's decide and revoke (whose proposals nothing in the product creates), may advance one sealed
  reservation. Bootstrap order is reservation, pending-root endpoint gate, `service-creating`
  head, native gate created frozen at generation0 under the creation operation and opened at
  generation1, RootPlan `{credentialId,expMs}` fixed once by head CAS at
  `now + ROOT_CREDENTIAL_TTL_MS`, planned active root by zero-history create, identical-bytes
  native gate touch, then the active head stamp. The endpoint gate is re-read at open, before the
  plan, root and stamp steps and before every write attempt of those steps; a revoked endpoint
  stops bootstrap. Cancellation is
  endpoint-first: endpoint deny-new (on a registration freeze it revokes approval and keeps the
  registration op), `service-cancelling` head (an active head's stamp and its
  root row's expiry become the preserved plan), native gate frozen under the cancel operation,
  then root disposition. An unstamped planned root MUST be fenced only by a zero-history create
  of revoked bytes or a CAS of the exact active row; a stamped root is retained. Every result is
  nonterminal. An absent reservation leaves endpoint deny residue and reports unconfirmed; a
  foreign or tombstoned reservation refuses before any write. Each write is a per-key create or
  revision CAS under a genuine plane-claim hold re-read before and after it plus a fatal latch.
  This is local readiness control, not broker writer exclusion. Each broker request is bounded
  by `min(5s, remaining live authority)`, where live authority is the invocation's original
  deadline. In bootstrap, once the plan step has fixed or read the RootPlan, live authority is
  also bounded by the plan expiry. Cancellation keeps only its own deadline, so an accepted
  cancellation can still proceed after the root expires. A request still in flight at the
  live-authority bound ends held; the writer stops waiting for it but does not cancel it at the
  broker. CAS re-decisions, native gate re-pins and
  unknown-outcome rounds are capped at 4, 8 and 3, and an unknown outcome is classified by a
  leader read before any reissue. Endpoint-gate reads and writes are the writer's own leader
  reads and conditional writes built from pure package-internal rows, so no endpoint-gate
  request starts after an invocation returns. Bootstrap's final active-root proof is bounded the
  same way, but it is a sequence of read-only leader reads behind one wait: when that wait ends
  at its bound, the abandoned proof can still issue its remaining reads after the invocation
  returns. Those reads write nothing. An already-active head reaches that proof before any plan
  clip, so bootstrap MUST hold `plan-expired` rather than report an active root whose expiry a
  final synchronous check, made after its last read with no await before the return, observes.
  This does not make the result fresh when the caller receives it. The held check is the closure captured when the hold was
  acquired, not the hold's mutable guard field. A frozen activation native gate is valid only at
  generation0; any other frozen activation form holds. The deadline and reservation are
  caller-supplied original material, not durable host authentication.
  Only then does the ordinary minting authority mint **before the entity is reachable**,
  persisting a CAS-fenced
  mapping
  `{ owner, actor, lifecycleUid, managerInstance, processEpoch,
  state: active | retiring | retired, currentCredentialId?, lastTakeoverOpId?, op? }` (closed
  schema; the
  embedded `owner`/`actor` MUST equal the key's alias tokens, so a key-mismatched row
  never authorizes; `currentCredentialId` is absent until the credential ledger releases
  a root under the reopened gate; `lastTakeoverOpId` is the opId of the takeover operation
  that LAST advanced `processEpoch` (the epoch advance and this stamp are ONE head CAS, so a
  completion is bound to exactly one operation: a resuming barrier confirms the completed head
  carries ITS opId, and a LOSING concurrent takeover that captured the same pre-takeover
  coordinates finds a foreign opId and refuses, never claiming the winner's completion; absent
  until the first takeover); `op` is required at `retiring` and forbidden elsewhere)
  under the alias's **CAS head key** (§13.7:
  the **unsplit** `lifecycle.<owner>.<actor>` head key HOLDS this mapping as one atomic
  record, the single authoritative current mapping and the only source of `mappingRevision`,
  §13.9; the UID-suffixed `lifecycle.<owner>.<actor>.<lifecycleUid>` key is optional
  append-only audit, never the authority). `mappingRevision` IS the head key's store
  revision, learned from the publish ack or from the leader-served read that returned the
  mapping (one read returns `{ mapping, revision }`); the value carries NO revision field,
  and a body-supplied revision is never a CAS coordinate. Head states: `active` is the
  ONLY current state. `retiring` is the containment phase of the terminal barrier (below),
  bound to the retirement operation's `op.opId`; it is non-current and NOT replaceable.
  `retired` is terminal and asserts the barrier COMPLETED (the cleanup proof), which is
  what makes replacing a retired predecessor safe. **Every currency seam fails closed on
  both non-`active` states**: target resolution, the process-epoch reads gating
  record/status writes, admission/start, and supervision derive current authority only
  from `state: "active"`; `retiring` and `retired` alike yield no current mapping and no
  current epoch. Activation is the head CAS (create-only for a virgin alias;
  revision-pinned from a `retired` predecessor), so two concurrent mints for one alias
  serialize there and exactly one activates; the loser terminalizes its own orphan gate
  and burns its reserved UID, never deleting either (`currentCredentialId` is a public key
  identifier/fingerprint plus authority epoch, never secret material). A supervised restart
  of the same entity **preserves**
  the UID (revoking/rotating the connection credential and advancing the process epoch); a
  terminal despawn, explicit stop, or supervision escalation retires the UID through the
  terminal barrier *before* the alias is freed. A retired UID is never reactivated
  (`retired → active` for the SAME UID is forbidden; only the ALIAS is replaceable, by a
  freshly reserved UID); recycling cannot move to the reservation, which is never freed.
- **Process epoch** (`incarnation`, an unsigned integer): the fenced ownership epoch of the
  process currently animating an identity, advanced by CAS on every takeover or restart. At
  most one live epoch owns an identity; a superseded process MUST stop serving and its commits
  are rejected (§13.8). **The epoch fences egress only**: reply, event, timer, session, and
  record-write-ingress publish grants pin it (§13.9), but request subjects deliberately omit it; a caller cannot
  know the serving epoch, so **no subject-level fence for ingress exists or can exist**. An
  un-revoked superseded serve credential remains a member of the class queue group and can
  consume (and externally effect, and never validly answer) one call in N. Takeover therefore
  carries a **normative barrier, in order**: freeze issuance for
  the lifecycle in the credential ledger (below) → revoke EVERY active credential-ledger
  row under the lifecycle prefix, every root (the superseded `currentCredentialId` and any
  earlier unexpired root: each root mint, initial or rotation, writes its own ledger row)
  and every ledgered descendant (handle-redemption-minted and per-session credentials,
  §13.6), via the deployment's auth
  authority, verifying the updated revocation state is enforced on EVERY server of the
  cluster before proceeding (fail-closed on partial acknowledgment: an unrevoked-anywhere
  credential can reconnect there) → evict the live connections of every revoked
  credential's `holderPrincipal` (from its ledger row, above)
  cluster-wide and verify the re-scan found none, the barrier executor (the trusted auth
  path) holds the delivery endpoint's `evictPrincipal` capability for exactly this step
  (Appendix B: granted to the barrier executor, not only `supervisor`); `evictPrincipal`:
  system-account CONNZ scan → per-server KICK → re-scan verify, fail-closed on partial
  scans; Appendix B) → **only THEN advance the process epoch by CAS (N→N+1), reopen the gate
  at the new generation, and activate the successor's serve subscription**. The epoch CAS is
  LAST, not first: a superseded process is revoked and evicted before the successor's epoch
  exists, so it cannot publish a reply or event in a window between the CAS and the eviction;
  the egress epoch is honest attribution precisely because no live predecessor egress survives
  the barrier. (A reply the predecessor emitted for an in-flight call before eviction reaches
  a caller only within that caller's own deadline and from a not-yet-evicted process; the
  barrier's job is that no such process remains once the successor answers.) Where
  revocation or verified eviction is unavailable (e.g. static credential material
  pre-rotation, Appendix B), takeover MUST fail loud rather than proceed.

**Retirement-only composition.** A manager instance MAY run as a retirement-only composition of
its own commit path. Its first durable act MUST be to acquire the per-instance liveness lease of
its own persisted logical `instanceId`; it MUST NOT read a lifecycle, slot or gate row before
that lease is held, and it MUST exit without effect if the lease is held by another process. It
MUST NOT register or serve the endpoint, hold the renewal lease, accept spawn or launch, adopt or
restart a seat, or run the resume path. It drives only the terminal retirement of non-retired
lifecycles whose slot names its own logical `instanceId` as owner, through the same operation
identity an ordinary instance would use, then releases the lease and exits. For every slot it
reads it records one outcome: `retired`, `already-retired`, `skipped-not-owned` (the slot names
another owner or none), or `held` (the slot or its head could not be read or parsed, or the
terminal did not complete). A `skipped-not-owned` or `held` slot is never counted as retired.
It is the minting manager's commit path for the purposes of the §13.9 lifecycle-mapping writer
row; no other principal, credential profile or grant is introduced.

**Credential ledger (normative).** Ingress has no epoch fence, so revocation is only as
complete as the set of credentials it covers, and the lifecycle's `currentCredentialId` is
not that set. Every credential the trusted auth path mints **derived from** a lifecycle (the
short-lived credential of a handle redemption, the two per-session credentials of a session
redemption, §13.6) is recorded at mint time in a durable, auth-owned **credential ledger**
row `{ credentialId, holderPrincipal (the `<owner>.<actor>` whose connections the barrier evicts; the credential id is NOT the principal, and eviction is by principal), lifecycleUid (the holder's), sourceChain: [root |
handle.<issuerKeyId>.<id>… | session.<sessionId>], the FULL verified lineage: for a
handle redemption, EVERY handle in the presented `parentDigest` chain (§13.6), never only
the leaf, state: active | revoked (monotonic), exp }`, keyed
`cred.<lifecycleUid>.<credentialId>` so both barriers enumerate a lifecycle's full descendant
family by key prefix. Each mint additionally writes one reverse-index key
`bysrc.<issuerKeyId>.<id>.<lifecycleUid>.<credentialId>` per chain member, so **revoking a
sturdy handle revokes every credential minted under it or under any of its descendant
handles**; a credential redeemed through a child handle carries the parent in its
`sourceChain`/`bysrc` keys, so parent revocation reaches it without walking handle records.
**Source gates.** The same fence applies per issuing handle, because a handle's revocation
state lives in the records bucket while credential indexes live here, and two buckets share
no order: each sturdy handle has an auth-bucket gate `srcgate.<issuerKeyId>.<id>`
(`{ state: open | frozen }`, CAS). Handle revocation CASes the source gate to `frozen`
**before** it enumerates `bysrc.`, and a redemption, after writing its `cred.`/`bysrc.`
rows, revision-pinned-CASes the source gate of EVERY handle in the presented chain (plus the
lifecycle gate below), releasing only if all are still `open` at their observed revisions. An
in-flight redemption under a handle being revoked therefore either finishes before the freeze
(its rows are in the enumeration) or loses a CAS and never releases. **Handle revocation
carries the SAME cluster-wide eviction as a lifecycle barrier** (§13.9 `evictPrincipal`):
after freezing the source gate and enumerating `bysrc.`, revocation revokes every descendant
credential AND verifies revocation enforced on every server, then evicts and re-scans the live
connections of every revoked credential's principal, fail-closed, an already-connected
descendant credential is never silently left with live grants. The handle status write is
acked only after that eviction is verified complete.

An unledgered mint MUST NOT occur (the ledger write precedes credential
release, fail-closed), and the rule carries a mechanical audit invariant in the style of the
§13.9 matrix grep test: every credential the auth authority has ever released MUST resolve
to a `cred.<lifecycleUid>.<credentialId>` row; an issuance path that cannot show its ledger
row is non-conformant, auditable by diffing issued-credential ids against the ledger.

**Issuance gate (normative).** "Freeze issuance" is a durable transition, not an assertion:
each managed-agent lifecycle has a gate key `gate.<lifecycleUid>` in the same auth KV,
`{ state: open | frozen | retired, generation, op? }` (CAS). A `frozen` gate MUST carry a
durable **operation intent** `op = { opId, kind: activation | takeover | registration |
retirement, successor? }`: after a crash the intent alone
decides WHICH operation a frozen gate belongs to and what may advance it, a retry or
reconciler resumes the SAME `opId`, and a writer that is not that operation's executor
MUST NOT advance, reopen, or terminalize the gate.
**A crash can leave the gate frozen under an operation whose executor no longer exists**, and
fail-closed then blocks every restart while protecting nothing. An operator-facing reconciler
MAY complete that dead operation's obligation — resuming its SAME `opId` and reopening at the
UNCHANGED coordinate with `generation` advanced by one — but ONLY after it has AFFIRMATIVELY
verified that the gate's freeze-holder principal is gone, via the same liveness machinery the
barrier's eviction trusts (`principalLiveness`, §13.9). A holder that is alive, or whose
liveness cannot be proven, MUST refuse; a timeout or an incomplete sweep is unknowability and
MUST NOT be read as death. The affirmative check is a PRECONDITION ON TOP OF the barrier's own
verified eviction, never a replacement for it. A `retired` gate RETAINS the
terminalizing operation's intent as audit, and an idempotent terminal retry succeeds only
for that SAME operation. **Successor coordinates are per-kind and derivable, never loose
prose**: an `activation` or `retirement` intent carries NO `successor` (an activation's
successor IS the head mapping the same operation writes; a retirement has none); a
`takeover` or `registration` operation's successor artifacts are durably keyed by its own
`opId` (the `stage.<opId>.` staging family and the operation's audit rows), so
`{ opId, kind }` alone resumes deterministically. The gate MAY carry a `successor` summary
token for those two kinds, but the staged rows are authoritative and a resumer MUST NOT
act on a summary that the staged rows do not corroborate. **Allowed transitions are also
per-kind**: a gate is BORN `frozen` only under an `activation` intent (and only for a UID
whose `uid.` reservation already exists); `open → frozen` belongs to `takeover`,
`registration`, and `retirement`; `frozen → open` (reopen) belongs to `activation`,
`takeover`, and a `registration` abort, NEVER `retirement` (a retirement freeze never
reopens); `frozen → retired` belongs to `activation` (a head-CAS loser terminalizing its
own orphan gate) and `retirement`, NEVER `takeover` or `registration` (those abort by
reopening). An implementation MUST refuse a transition whose gate op kind is outside these
sets, before any CAS is attempted. The `opId` is an identifier, never a
bearer capability: a resumer re-authenticates as the operation's executor, and possession
of the id alone grants nothing. `retired` is terminal, a retired
lifecycle never mints again. `frozen` is **not** terminal, because a supervised restart
preserves the UID (§13.1) and must mint the successor process's root credential: the
takeover barrier freezes at generation `G`, completes revoke + verified eviction of the
family, and only then CASes the gate to `open` at generation `G+1`; the reopen is the
barrier's own final step, so no credential of generation `G` is ever live when generation
`G+1` mints. A gate reopen by anyone but the completing barrier is non-conformant.
**Endpoint instances use a disjoint gate family, distinguished by explicit prefix and
never by token arity**: the endpoint issuance gate is `epgate.<endpoint>.<instanceId>`,
`{ state: open | frozen | retired, generation, processEpoch, registrationRevision,
nameAuthorityRevision, principal, op? }` (the endpoint fence coordinates of §13.5/§13.7, plus
`principal`: the serving instance's own CONNZ-attributable connection principal, recorded at
registration), and
endpoint-derived credentials ledger under `epcred.<endpoint>.<instanceId>.<credentialId>`
with the same row schema, mint protocol, gate discipline, and never-delete rules as
`cred.`/`gate.`. An interrupted endpoint registration repair MAY journal verified evictions
under the disjoint cursor key `eprepair.<endpoint>.<instanceId>`. A cursor MUST bind the exact
registration `opId`, the observed frozen-gate KV revision, and the sorted distinct holder set.
A holder is appended durably only after its eviction verifies, and the holders one eviction sweep
verified are appended before the next sweep is attempted. A retry MUST repeat the freeze-holder
liveness precondition, MAY skip only holders in a cursor whose complete binding still matches, MUST
restart from empty progress on any mismatch, and MUST reopen only after every current holder
verifies. Cleanup occurs after reopen; a cursor that
cannot be deleted cannot authorize a later freeze because that freeze has a different gate revision.

A standalone user-authorized service gate adds `authorityFamily: "service"` and a required
`serviceAuthority` value. Its closed fields are `v: 1`, `assignmentDigest`, native
`lifecycleUid`, `state: pending-root | approved | revoked`, `rootCredentialId`, `rootExpiresAt`,
`approvalDigest`, `approvalExpiresAt`, `callerCeiling`, `activationIssued`, and optional
`registration: {operationId, requestId, requestDigest, processEpoch, registrationRevision,
nameAuthorityRevision, registrationProof}` and
`pendingRegistration: {operationId, requestId, requestDigest}`. Expiries use Unix seconds.
The completion stamp's coordinates must equal the gate's. A positive registration revision
requires a completion stamp. A pending identity is present only under its matching registration
freeze, and activation cannot be recorded while that identity is pending.
A pending-root value has null root and approval fields, an empty ceiling, no registration and
`activationIssued: false`. An approved value binds the native root and immutable approval;
only approval expiry may be null. Revocation retains any existing binding. An activated value
requires the registration stamp. A service value without its family, or the family without
its value, is invalid. Existing nonservice gates do not acquire this authority. A §13.6 session
never serves on a service gate: the session's serving-gate read refuses the service family before
it returns a pin or stages a ledger row.

Service registration freeze requires live approval and `activationIssued: false` at the gate CAS.
Under that freeze, a complete leader read must prove the issuance family empty. Active, revoked,
malformed or deleted credential evidence is inconsistent with pre-activation state and blocks
registration and recovery. Failed or partial reads never prove emptiness.
Gate transitions retain approval, root and activation fields. A new registration freeze retains
the prior completion stamp and records the new operation's request identity and digest. Successful
reopen writes the new completion stamp and resulting coordinates in the same CAS. A definite
abort-reopen preserves the prior stamp. Both paths remove the pending identity. Recovery binds
the recorded operation; an open surface alone cannot establish which request completed.
A separate post-reopen writer cannot attach a completion stamp.
Serving release requires live approval,
recorded activation and a finite credential expiry bounded by the service access lifetime,
native root expiry and approval expiry. Release rechecks the fixed expiry after asynchronous
staging and commit; expired material is refused and never given a new expiry. A registration recovery that reopens an already revoked
approval does not authorize serving release. Standalone service authority does not support
sibling credential duties. Native root validation, owner consent, activation and coordinated
retirement remain requirements of the trusted host; parsing this value does not establish them.

Native application-service name bindings use `servicename.<endpointToken>` in the primary auth store.
The closed record has `v:1`, `kind:"service-name"`, accountPublicKey, space, endpoint, owner,
claimReference, requestId and requestDigest. The trusted host authenticates operator authority and
verifies the namespace claim before creation. A stored assertion alone proves neither of those checks.
Creation requires zero retained key history. Only an identical request can recover the binding;
changing its owner, claim or request identity does not update it. Owner and binding revision are read
from one leader snapshot. Transfer and release are unsupported. Native auth, delivery and manager
names remain with their existing control authorities and cannot be application-service bindings.

Native service caller policy is stored at `servicepolicy.<granteeUid>` in the primary auth store.
Its closed value is `{v:1,accountPublicKey,space,grantee,grants}`; grantee is the exact owner, actor
and lifecycle UID. A grant contains its immutable id, endpoint and source instance, approval digest,
normalized capabilities, source and parent references, and status. Source and parent references
name immutable grant ids at their original grantee UIDs. Source instance is provenance; native
capability route and instance fields still determine request reach.

Initial policy and create-only service authority writes require zero prior key history at the
write CAS. A deletion or purge marker, including one arriving after an absence read, MUST refuse
recreation. An additional pre-read is not this fence.

A new grant is appended at the observed policy revision. An existing id cannot change its capability
or provenance and cannot become active again after revocation starts. Revocation first records
`{state:"revoking",operationId,phase:"cutoff-required"}` at that revision. Unrelated writes preserve
this intent. Repeated same-operation reads do not advance it or establish terminal retirement.
Deleted, malformed or unreadable policies are not empty policies. Stored syntax and successful CAS
do not establish current grantee, service, granter or ancestor authority; the native host must verify
those before committing or using a grant. Neither a policy row nor a deny-new intent proves live
connection absence.

New caller-policy appends MUST reserve serialized status-growth headroom for every grant
simultaneously under the unchanged 2 MiB policy limit. Compute canonical policy bytes plus each
grant's nonnegative remaining growth to the largest supported closed status, including a
512-character marker and maximum safe-integer revision/time fields. Refuse before the append CAS
if that worst-case total exceeds the limit. This is not a retroactive repair of older near-cap
policies: their bytes and grant history remain unchanged, append refuses insufficient headroom,
and revocation can still fail the byte bound. Such residue blocks service writer/admission
enablement pending a separately authorized compatibility protocol. No cap raise, pruning,
reactivation or terminal absence claim follows from the headroom check.

Native service host operations keep five closed record families in the primary auth store:
`svcop.<operationId>` (one request's binding), `svcprop.<proposalId>` (a proposal),
`svccap.space` (the space's pending-proposal capacity), `svcasg.<endpointToken>.<instanceId>`
(one selector's assignment) and `svchost.admission`. The operationId is the first 32 lowercase hex
characters of SHA-256 over `cotal-service-host-op`, NUL, space, NUL and requestId, so one id
namespace covers every host request in a space. A binding stores the canonical request digest; a
reused requestId with any other body is a conflict and is never rebound. Every create requires zero
retained key history, and a deletion or purge marker at any of these keys refuses and is never
recreated. No record carries a TTL, and none is deleted or purged. A live operation or proposal
becomes terminal only by a CAS on the same key, after every write it depends on is durable.

Each write is one create or one exact-revision CAS on one key; there are no multi-key batches.
An invocation writes only its scope: the exact keys its discovery read located before the
invocation's effect connection opened, which are its own records and, for a located reservation, the
native custody keys of that one reservation. Only an approval's scope holds the reservation's UID
key; a revoke's never does. A step that needs a key outside the scope holds as `scope-mismatch`
before anything is issued, including before native custody of a reservation the scope does not name.
A replay of the same request discovers the stored binding and assignment and scopes them.
Each invocation's effect connection is a fresh self-minted data-account identity named
`cotal:service-host-effect:<space>`. Its credential publishes exactly the scope's keys as `$KV`
subjects, and one more token under each auth family, beside the leader reads both stores need;
discovery's credential publishes no `$KV` subject at all. That grant confines publish subjects only;
it does not restrict what a publish on a granted subject does, and the engine's own writes are only
creates and exact-revision CASes on those keys. Reads are not confined: the leader reads
are stream-wide on both stores. The effect credential never renews or reconnects. Its one expiry is
the invocation's original deadline, at most 60 s ahead, rounded up to the whole second. The broker
checks expiry in whole seconds against its own clock: it admits no connect once its current second
is past that expiry, and it closes a live connection with one whole-second timer armed at connect.
With a broker clock that agrees with the host's and timely broker and client scheduling, that close
comes within the second after the expiry, up to about 1999 ms after the deadline. This is timer
granularity, not a guaranteed close time: a skewed clock or a paused or late broker or client moves
the close either way. Neither the expiry nor the close is proof that an earlier write cannot still
settle. Losing the effect connection fences the invocation: a later write holds, and a write
in flight across the loss reports held. An effect connection that does not open holds as
`unavailable` at `effect-open` with nothing written.
Register reads admission, capacity, the selector's assignment and the name binding first. An initial
refusal writes nothing and binds nothing. Otherwise the binding is created before the capacity CAS
and the zero-history proposal create, whose times are fixed once in the binding. A refusal found
after the binding exists is recorded on it. Capacity counts entries whose `expiresAtMs` is still in
the future: at most 256 per space, 32 per owner and one per selector. Clock skew between writers
can under-count that bound; it never extends authority. Past a register's own bound an absent
proposal key is fenced with a terminal `expired` tombstone, so a delayed create loses. Every close
writes the proposal's terminal record before it releases capacity. An unobserved release stays as
recorded debt until a later read observes it.

An approval binds before its first effect, then moves the proposal to `approving` at the admitted
revision, creates the assignment, runs native custody bootstrap and writes the endpoint-gate approval
CAS. No authority-increasing step starts at or after the operation's original deadline, which is at
most ten minutes after admission and never past the proposal's expiry. An endpoint gate carrying
the approval's digest, approved or later revoked, proves that approval landed; a landing observed
after the deadline is recorded as a historical result, not current readiness. An expired approval
that holds an assignment binds its own cancel identity in that assignment, or adopts the one
already bound, and cancels natively.
The assignment record carries the complete native cancel identity `{operationId, requestDigest,
cause}`. Any authorized revoker can cancel from that record alone, and the first binding wins.
Deny-new has landed exactly when the endpoint gate reads `revoked` for this assignment. After that, an
`approving` proposal of the assignment closes as `approved` when the gate carries an approval digest
and as refused otherwise. A gate row at the key that belongs to another assignment is no evidence
either way: every revoke path that reads the gate reports deny-new `unknown` for it, as for a failed
read; an already fenced assignment is its own evidence and reports `landed`. Revocation stays
nonterminal. Owner-scoped reads authorize against the subject owner fixed at binding, and terminal views carry no commands or cluster digests. None of these
records proves a human decision, broker writer exclusion, restore readiness or terminal retirement.

An authority plane opened with `serviceEndpoints` hosts these operations; without it, the default,
the plane opens no service host, no discovery connection and no `svchost.admission` write. The host
uses the plane's own claim hold, data account and actor ledger directory. Its startup MUST be named
`normal`: a restore or a missing startup refuses the whole plane open, which unwinds what it opened
and releases the claim. If that unwind cannot close an effect connection, for instance the startup
admission write's, it stops there. The open still refuses, but with the close failure; the claim stays
held; the cleanup timer, discovery, both scanners and every client opened so far stay live; and no
handle exists to retry. Until the process exits, a successor cannot reclaim the claim while those
scanners live; after exit, the existing crash reclaim applies. Discovery is one standing connection named
`cotal:service-host-discovery:<space>` on the ordinary renewing profile with the read-only grant. A
broker loss makes its reads fail until the stock client reconnects; a read that fails answers as
`unavailable` within its invocation's own limit, with nothing written. A renewal failure closes it
for good, and every later read through it fails until the plane is opened again; nothing reopens it.
Both plane fatal paths, a scanner death and a failed native-issuance cleanup, trip the host's fatal
latch, and a fence that lands while the host is opening latches it once it exists. A scanner death
or a failed cleanup while the plane closes still trips the host, so a call close is draining stops at its next step, but
it does not make the plane fatal. From the moment
plane close is called it refuses every host call, before it waits for anything. It then waits until
every call that entered before it has returned, including one still reading discovery or opening its
effect connection. An effect connection whose close failed stays owned by the plane after its call
has settled, and that call rejects with the failure. Close closes every such connection again and,
while one remains open, rejects with the claim still held, so a later close retries it; a request
that was only rejected does not hold close. A close rejected that way has already stopped the
cleanup and sweep timers, but discovery, the administrative rail, both scanners and the remaining
clients stay live, so no successor can reclaim the claim until a later close succeeds or the process
exits. Only then does close close discovery, and only after that does it release the claim. Close
itself trips no call it waits for: each ends under its own discovery-read bound and invocation
deadline, each at most 60 s. Its own scanner teardown, after that wait, does trip the host, which by
then has no call in flight, and does not make the plane fatal.

The plane and a hosted context expose `decide`, `revoke`, `name-bind`, `operation-read` and
`proposal-read` as one in-process function; a hosted context refuses it as `unavailable` while it is
not ready. The host checks an owner granter's current interactive row but authenticates no caller:
the embedder MUST authenticate the granter it passes, operator or owner. The stock auth service
serves that function on `POST /service-endpoint-host` when started with `--service-endpoints`, or
as a lane of `auth-service --spaces` whose manifest entry sets `serviceEndpoints`: the
body is `{ idpToken, request }` with no granter, the route verifies the IdP token and derives the
granter itself. `decide`, a revoke whose cause is `owner-request`, the four caller-policy operations
(`caller-grant`, `caller-revoke`, `caller-policy-read`, `caller-policy-list`) and the reads
(`proposal-read`, `operation-read` and `name-read`, each scoped to the owner's own records; a
proposal read names its endpoint's binding only when that binding is the owner's) run as the proved
owner on either face; `name-bind`, any other revoke and the admission operations are the operator's
and are served on the loopback face under the exchange capability only, with the proved subject as
the audit id. The admission operations have no engine handler in this cut and answer `unsupported`.
The engine's answer, replay included, is returned as is. Host requestIds share one space-wide
namespace, so a client MUST mint them unpredictably (16 random bytes); a guessable id can be bound
first by another owner.

`name-bind` is create-only and needs an operator granter. Its `expectedRevision` MUST be 0; any other
value answers `unsupported`, and a native control endpoint name is refused. It issues one
zero-history create of `servicename.<endpointToken>` through its invocation's custody host, whose
scope and effect credential hold only that key. The row binds account, space, endpoint, owner, the
operator's `claimReference`, requestId and the canonical request digest. Only the identical request
recovers an existing row; the same requestId with another body is a `conflict`, and any other request
for a bound name is `name-bound`. Nothing updates, transfers or releases a binding. The host verifies
no domain control: the claim reference is the operator's assertion, and an unused name is no proof.
A `held` name binding names its requestId's deterministic operation coordinate, which no operation
record carries; it recovers only by replaying the same request.

The public route `/service-endpoint-authority` exists only with `serviceEndpoints`, on both faces;
without it the route answers 404. `GET` returns `{v:1, kind:"service-endpoint-authority-info", space,
accountPublicKey, issuer, keys}` for the receiver's own account with `cache-control: no-store`:
`issuer` is the exact `iss` its service access bearers carry and `keys` the Ed25519 verification
keys it publishes at `GET /jwks`, public coordinates only (`kty`, `crv`, `kid`, `x`, at most 16),
each a canonical encoding of a full-order point; a key of small order is refused on both sides. A
guest verifies its access bearer under them before it brands its serve grant (§13.6). A door that
predates the identity answers the four account fields alone, and a guest that needs it refuses to
serve rather than brand unverified. `POST` takes one
envelope of at most 2 MiB. The proof is the credential on either face; the loopback capability is
never read. A request naming another account or space is refused before its proof is used. The proof
kinds each operation admits are fixed: register needs IdP; activate needs IdP or a renewal proof;
renew needs a renewal proof; ready needs an access proof; status admits all three. Any other pairing
is bad shape (400). An IdP proof verifies under the pinned issuer and exact audience, and its subject
derives the owner, who also needs a current interactive cli or web row. A renewal or access proof
verifies against this context's own issuer keys and yields its signed witness, never an owner's
authority: its owner is the witness's, and a request that carries a witness must carry the proof's.
Status carries none, so a renewal proof's own witness is the expected one. A refused proof counts
against the face's refusal budget and answers 401, or 429 once that budget is spent; a valid proof
never consumes it. Protocol answers are 200 for `registered`, `credential`, `ready` and `status`, 202
for `approval-required` and `pending`, and for `denied` 409 on a conflict, a closed proposal or a
moved registration, 422 on refused input, 429 on a bound and 403 otherwise. An answer that cannot be
completed after the proof, an unread, unknown or inconsistent record, or a `held` engine answer is a
503 with no protocol echo, because the request may already be bound.

Register compiles the submitted closures in memory. Each cluster digest names a submitted manifest
whose root names a submitted cluster document, each verified against its content digest. The schema
closures the commands reference (each `inputDigest` and `outputDigest`) MAY be submitted with them;
a submitted one is admitted only whole: its manifest at that digest, its root and every member
present, and the bundle recompiling to that closure digest. Every artifact must belong to a declared
cluster closure or to a referenced schema closure. A schema closure that is not submitted is not
refused: the host publishes the canonical void closure itself when it opens service endpoints, and
any other schema closure is the registrant's to publish. The proposal's commands are exactly those
documents' declarations; a journal-class command, or one declaring a governed trait (`ai.cotal.guarded`,
`ai.cotal.priced`), is refused as `unsupported-runtime`, in the proposal phase and the registration
phase alike. A native control endpoint name (`auth`, `delivery`, `manager`) is refused as
`permission-denied` before any effect and binds nothing. The engine requestId is the first 32 lowercase hex
characters of SHA-256 over the canonical JSON `["cotal-service-endpoint-authority", 1, account,
space, owner, endpoint, instanceId, operation, requestId]`, with the derived owner, so the full public
idempotency key selects the binding. That binding's request digest covers the public request's
canonical digest, which excludes the proof: a changed public body under the same key is a
`conflict`.

Register has two phases, chosen by the derived owner's assignment at the selector. Until that
assignment's endpoint gate carries an approval (no assignment, or one whose approval is still
settling), register is in its proposal phase, which takes only `expected:{state:"unregistered"}` and
answers `conflict` for any other. A first admission answers `approval-required` with the proposal id
and digest. A replay answers from the proposal as it is now: `approval-required` while it is live and
unexpired, also while its approval is in progress; `pending` at phase `approved-unregistered` if its
approval landed after the phase was read; and `denied` once the proposal is refused or expired or its
approval revoked. An undecided proposal reads as expired once the whole second of its expiry has
passed, before any tombstone closes it. The door never answers expired before the host would refuse
its approval, but its other answers can trail the expiry: an undecided proposal can still read
`approval-required` or `proposed` for up to one second after it; a proposal whose approval is in
progress answers `approval-required` to a replay and `proposed` to status, with no expiry mapping on
either, until that decision settles; and a first admission's `approval-required` is computed when the
proposal is created, so an admission that was held and settles on a later replay can produce it after
the expiry, not only deliver it late.

Once the gate carries an approval, register is in its registration phase, and so are activate, renew
and ready. Each of these requests, in order: proves its caller as above; resolves the caller's own
assignment at the selector before any read by request key, so another owner or a selector with no
assignment answers `permission-denied` (a revoked one `approval-revoked`) and any witness the request
carries must name that assignment's selector, owner, serving actor and lifecycle UID; reads its
intent `svcint.<id>`, where `id` is the engine's operation id of the request's engine requestId; and,
only when no intent exists, checks the state the request needs. A register key keeps the binding its
proposal phase gave it: the operation record of the same id, when one exists, must be a register
admitted with this body, else `conflict`, and must name the approved proposal; a key whose proposal
was refused or has expired answers `proposal-refused` or `proposal-expired`, as its proposal-phase
replay does, and any other proposal is `conflict`. Both are decided before the state check and before
anything is written. A registration needs a bound
assignment with no cancel identity, an approved gate without `activationIssued`, a recompiled surface
whose proposal digest equals the approved proposal's, and an `expected` that matches the current
registration stamp exactly (no stamp for `unregistered`); refusals are `approval-revoked`,
`registration-active`, `approval-mismatch` and `conflict`. Activate, renew and ready need their
witness to be the current stamp's, else `stale-witness`. A new admission then lists its id in the
instance's index `svcintx.<endpointToken>.<instanceId>`, which is created with the assignment's owner
and checked against it on every read; an id already listed takes no slot, and a 65th id is
`resource-exhausted` (429) before any effect. It then creates its intent with zero history. The
intent records the operation, account, owner, selector, the public requestId and digest, the
registration the request names (register's expected one, or the request witness's) with that
witness's digest, and its admission time. No proof, token or reply is ever stored. An existing intent
with another digest is a `conflict` before any state check, and a create that loses to an equal
intent is a replay. Intents and the index are never updated, pruned or deleted in this cut: they
stay for the assignment's lifetime, and an id listed without an intent (a stop between the two
writes) stays listed.

Every effect is then evaluated against current native state. A registration answers
`approval-revoked` first if the assignment is revoked. Otherwise, when the current stamp names this
request (requestId and digest), it answers the top-level `registered` with that witness and snapshot,
before or after activation and readiness: no barrier, no epoch advance and no artifact publish.
Otherwise it answers `registration-active` once activation has been issued. A gate frozen by this
same request's operation resumes with no further check here; any other request requires the gate
open (else `registration-pending`), `expected` to match the stamp (else `conflict`), and a live
approval (else `expired`). It then publishes each submitted artifact
create-only to the contract store and runs the native registration barrier and spec registration
under the request's operation id, with no repair cursor (a service's credential family is empty
before activation). The barrier freezes the gate revision it observes after those publishes
whenever the gate is not frozen by this request's operation, which includes a request that read it
frozen and finds it reopened, and the request checks again on exactly that revision before anything
is frozen or written: a stamp that already names this request answers as a replay does, and a gate
that moved answers `conflict`, or `approval-revoked` once revoked; those answers are definite. A
gate the recheck finds frozen by this request's own operation with no stamp yet is another
invocation of the request still running, so this one is held and answers 503, and its replay resumes
or recovers it. A request whose barrier observes the gate already frozen by its own operation
resumes with no recheck. A refusal the native barrier makes itself, such as a gate it observes
frozen by another operation or a freeze it loses after the recheck held, is a held step that answers
503. It answers `registered` only once the stamp names this request. A first
registration keeps process epoch 0; a pre-activation re-registration advances it. Activate requires
the gate open with a live approval and native root, the request witness to be the current stamp's,
and then lands the monotonic `activationIssued` CAS unless it already holds. Renew requires the same
and an activated gate, and writes nothing. Both answer `credential` with a fresh access bearer and
renewal proof whose expiries are fixed when the door admits the request: at most 60 s and 24 h, and
never past the native root's or the approval's expiry. Material that cannot be minted is `expired`
only once its fixed access expiry has passed; any other failure answers 503, and the replay mints.
A replay of either mints replacement material for the same current registration; earlier material
keeps its own expiry. Ready requires the same as
renew, plus the native name binding at the witness's revision, and writes the service-reported status
`ready` at the witness's process epoch and spec revision, with `reportedAt` in Unix seconds, fenced on
the gate's epoch. It answers `ready`. Every write of a serving request goes through its own
invocation, whose effect credential publishes exactly its intent and index keys and that operation's
effect keys (the endpoint gate for register and activate, the spec and governance head for register,
the status for ready), and publishes and reads back exactly the submitted artifacts. The
credential also grants an exact Direct Get on each records key the operation reads, but the
registry's KV handles are bound without Direct Get: every auth and records read of a serving
effect is a leader message get (`STREAM.MSG.GET` last by subject) under the base read grant. The
native registration and the ready status write through their own
client on that connection, and a fatal latch closes the connection, so none of their requests is
issued after the fence; a request already issued stays unknown.

A serving client keeps one requestId per (operation, witness), persisted with the witness and its
renewal material in its single-owner store before dispatch, and reuses it, with an unchanged body,
after a restart. Only a pre-activation re-registration produces a new witness and so new keys. An
activated instance therefore needs a handful of keys for its life, and the 64-intent bound is
reached only by a client that draws fresh keys.

Status takes `target:{kind:"instance"}` or `target:{kind:"operation", operation, requestId}`, where
`operation` is one of `register`, `activate`, `renew`, `ready` and `revoke`; this selector replaced
`{kind:"operation", requestId}`, which no longer parses. An owner's IdP proof reads that owner's own
requests and assignment, and only while the owner holds an interactive row, as for every owner
request: without one it is `denied` `permission-denied` (403) before any read. A renewal or access
proof reads only intents whose witness digest is its own, and the instance only as its own
incarnation; a register with no intent is not readable by it. A witness naming another selector
reads `not-found` before any read, and on a bound assignment a witness that does not name the
binding reads `not-found` before the precedence below, so another instance's revocation is never
visible to it.
Another owner's request reads `not-found`. `revoke` answers `denied` `unsupported`. Every required
record that is unreadable, missing where it must exist, or inconsistent with another answers 503,
never `not-found` and never a `superseded` whose ids were not read from records. An operation status
follows this precedence:
- an id listed in the index without its intent answers 503; it stays 503 if a later replay is
  refused, because no effect ran or can run and status never guesses absence;
- with neither record, a register is in its proposal phase (below) and anything else is `not-found`;
- a bound cancel identity or a fenced assignment answers `revoking` first, with phase
  `deny-new-landed` once the gate reads revoked and `deny-new-pending` before;
- a register intent answers, first match wins: `registered` or `ready` when the stamp is this
  request; `superseded` with this request's id and the stamp's when the stamp names neither this
  request nor its expected registration, or names the expected one after activation; `registering`
  at phase `barrier` when the gate's pending registration is this request; `registering` at phase
  `admitted` when there is no stamp and nothing was expected, or the stamp is the expected one before
  activation, with the gate open or frozen by another request; otherwise 503;
- an activate, renew or ready intent answers `superseded` with its registration's id and the stamp's
  when the stamp moved on, `registering` at phase `barrier` with the other operation's id while the
  gate is frozen, and `registered` or `ready` when the current witness is the intent's; otherwise 503.

A register with no intent answers from its proposal-phase operation record: `registering` while that
register is in flight, `refused` for a refused register or a closed proposal, which stays refused and
never reads another proposal's assignment, `proposed` while its proposal is live and unexpired or its
approval is in progress, `revoking` once the assignment it approved has a cancel identity, and, once
approved, `approved-unregistered` with the gate's approval digest while no registration exists, or
`superseded` with the current stamp's request id once another request registered: this request can
never register, because its replay would expect no registration. Instance status answers the
caller's assignment as it is: `revoking`, `registering` at phase `approving` while its approval
settles, `approved-unregistered`, `registering` at phase `barrier` while the gate is frozen,
`registered` or `ready`, and for a service proof whose registration was replaced, `superseded`.
`registered` and `ready` say only that the request's registration is current; neither says whether a
reply was delivered or whether an activation landed, and the recovery is always to replay the same
request.

An authority plane opened with `serviceEndpoints` also composes the native service callout. A
connection presenting a service access bearer is minted only for the current bound assignment its
witness names, which the service host reads, on its own writer connection
`cotal:service-callout:<space>`. That writer adds the endpoint gate and endpoint credential families
to the base writer grant; a broker grant cannot confine those families to service assignments, and
the native mint writes only the bearer's own rows. A fenced plane or a latched host refuses the
mint. Registered closures are read back from the contract store on the issuer connection's
exact-subject Direct Get. Revocation, the Describe view, caller policy, terminal retirement and
restore remain unsupported: a revoked assignment's bearer is refused at its next connect, but a
connection already open keeps its original expiry of at most 60 s.

A local scoped Describe callback may receive immutable request context alongside the caller.
Its subject comes from actual dispatch, and its fingerprint is the canonical digest of
`{subject,envelope}`, using the complete original decoded JSON envelope after native Describe
validation. A picked parser projection cannot substitute for that envelope. Local receive time
is monotonic and captured before validation; deadlineMs remains the original relative budget.
This is receiver-relative context, not a publisher-start timestamp. The context exposes no raw
proof bytes. Neither a context value nor its digest establishes actual receipt or authority to
another process; a trusted observer must establish that independently.

The service authority's read-only `describe-view` request adds the complete current `witness` and
`observation:{subject,fingerprint}` to its common coordinates. Only service-access proof is admitted.
The subject must be an untargeted native Describe for this space and endpoint; an instance route
must name this instance. Parsing does not prove that the request was observed. The host must verify
current native service authority and an independent live observation before evaluating caller policy.

Its successful result has `state:"view"`, the matching witness and observation,
`basis:"principal-policy-v1"`, unique declared command names and positive integer `validForMs` no
greater than 2000. That basis denotes current principal-policy visibility, not the connected
credential's exact permission ceiling. The serving client verifies commands against its registered
surface. It requires request and response no-store, original receive-relative budget and the host's
remaining duration after subtracting the full local query roundtrip. Proof, preparation, HTTP body
and validation work cannot restart the deadline. Missing access proof never triggers IdP login.
Transport failure on this read is not an unknown mutation outcome. The host's current-root,
observation, source/provenance and freshness checks remain mandatory beyond this wire shape.

A service access bearer uses the user-token shape with `act.view: "service-endpoint"`.
Its closed payload has `iss`, scalar `aud`, `sub`, `iat`, `nbf`, `exp`, `ver`, `scope` and `act`.
Its protected header has only `alg: "EdDSA"` and `kid`. The closed actor has `owner`, `actor`,
`scope`, `lifecycleUid`, `credentialId`, `view` and `service`. Both scopes are empty lists.
The closed service claim is `{duty: "serve", witness}`. The complete witness binds the same
space, owner, actor, native lifecycle UID and root credential as the outer bearer. Parent, manager,
session and transfer claims are forbidden. Times are integer Unix seconds, `nbf` equals `iat`,
and the fixed expiry is at most 60 seconds after issuance. Validation refuses at the original
expiry, without extending it through clock tolerance. Ordinary exchange and profile construction
MUST refuse this view. Only a dedicated service authority path may issue and authorize it after
current native root, approval and registration checks. Signature validation alone does not
establish that authority.

The dedicated callout authenticates the sealed broker request before using its user key, server
key or client id. It captures admission time before authority reads and fixes native expiry no
later than the access expiry or admission plus 60 seconds. It derives serving permissions from
the current native registration, preserves principal-name attribution and uses the separate
client inbox nonce. Native issuance and receipt checks and final root authorization precede the
encrypted response. An expiry reached while encoding or sealing that response refuses release.
An unconfigured or non-native service authority cannot fall through to ordinary actor hooks.

A service renewal proof is a separate EdDSA JWT class with protected `typ:
"cotal-service-renewal+jwt"`, `alg` and `kid` headers. Its closed claims are `ver: 1`,
`kind: "service-renewal"`, the complete registration `witness`, its canonical `witnessDigest`,
and `iss`, `aud`, `sub`, `iat`, `nbf`, `exp`. Issuer and key resolution are pinned by the host;
audience is the one space and subject is the witness owner. `nbf` equals `iat`; expiry is a fixed
Unix-second deadline no more than 24 hours after issuance. Validation rejects the proof at its
original expiry and binds every witness coordinate, not just the service name. User/connect token
validation rejects this JWT class even if it carries actor-like claims. A normal user bearer is
not a service renewal proof.

The signature primitive does not establish current authority. Before issuing or accepting service
authority, the host checks the current native root, approval, registration and operation eligibility.
It bounds expiry by root and approval expiry. Signing cannot extend a supplied fixed deadline;
material that expires while signing is not released. Renewal proofs contain no ordinary capability
scope and are never native DATA-account credentials.

**`holderPrincipal` is ALWAYS a CONNZ-attributable `<owner>.<actor>` in
BOTH families** (the barrier KICKs it; an endpoint NAME is not attributable and never sits
there): in `cred.` it is the caller principal; in `epcred.` it is the serving instance's own
connection principal, copied from the endpoint gate's `principal`, while the endpoint NAME that
forms the `epcred.` KEY is a SEPARATE row field, so the key identity and the eviction target
stay disjoint (an `epcred` row that put the endpoint name in `holderPrincipal` could never be
KICKed). The `cred.`/`epcred.` families hold ONLY conformant ledger rows:
implementation staging, half-minted state, and tombstone fences live in a distinct
`stage.` family, never under a ledger prefix a barrier enumerates.

Service-callout `epcred` rows also carry the closed `serviceConnection` object
`{userNkey, serverId, clientId}`. The user and server keys are checksum-valid NKeys of their
respective kinds; clientId is a positive safe integer from the authenticated broker request.
These fields are forbidden in the `cred` root family. Service issuance requires this binding,
preserves it during staging and leader-reads the row and unchanged gate authority after its CAS.
An ordinary serving issuance cannot carry service connection evidence. These rows prove issuance,
not broker acceptance or response delivery. Native root authorization remains separate.

Service-callout natkey evidence additionally carries
`service:{witness,credentialId,connection}`. The outer credentialId is the native root; the nested
credentialId identifies the issued service JWT in epcred. The connection is the same authenticated
broker triple, and its user key must match the natkey key. Recording checks the active epcred
binding before and after the create-only write, verifies its receipt, reauthorizes and checks the
original expiry before release. Existing receipt history cannot be reused. Expired service evidence
is retained until topology-qualified retirement authorizes removal; the ordinary expiry pruner
reports it separately and cannot remove it. That terminal pruning path is not implemented yet.

**Native-key issuance evidence.** Every callout success first writes one create-only
`natkey.<userNkey>` row in the same auth bucket, where `userNkey` is the broker-generated
per-connection user public key the minted JWT is issued to (never the client's inbox nonce or a
display name). The row is the closed v1 record `{v, space, owner, actor, lifecycleUid,
credentialId, view, connId, iat, exp}`, taken from the validated bearer's own signed claims
(already equality-checked against the current row) and from the minted JWT's own `iat`/`exp` (a
session-caller's is its grant's). An existing row for the key, under any lifecycle, refuses. After
the write the responder re-reads the lifecycle and credential authority in the same stream; only
then may the JWT leave. Any failure is a signed deny. A row is evidence of an issuance, not proof
that a client received the JWT or that the broker accepted the connection: a write that landed
before a refusal stays. `natkey.` is not a ledger family and authorizes nothing. The issuing
service removes a row only after its `exp` plus the clock tolerance. Startup performs a bounded
cleanup check, then the service continues the walk in bounded background batches and repeats it
on its own timer. The walk uses the leader-served `STREAM.MSG.GET` `next_by_subj` read (never a
consumer) and purges each row pinned to the sequence it read, with the bucket's marker TTL on the
purge marker so it expires too. A row that does not parse fails the boot open if encountered in
the startup check, or fences the running service when a background batch reaches it. Connections admitted
before this row existed, responders that do not write it, and static (non-callout) JWTs have no
row. This evidence names keys; it does not by itself stop a still-authorized bearer from
connecting again under a fresh key.

**Remote manager-service authority (user-auth only).** `manager-service` is one CLOSED,
server-authored authority view, not a profile name, arbitrary bearer profile, or client-supplied
permission set. The human exchange serves only a signed-in human whose current actor-ledger row
contains the dedicated `supervise` scope. A trusted host composition may separately issue the
owner-bound platform supervisor registration material defined in §13.15, without a human exchange.
`spawn` and `admin` never imply `supervise`, and `supervise` never
implies either. Only the loopback/operator exchange MAY issue this view; the public exchange and
every managed-agent secret exchange MUST refuse it. The callout re-reads the ledger row at exchange
and connect, so a missing, narrowed, or revoked `supervise` scope denies the next view exchange and
new connection with the full re-grant requirement. A plain user bearer remains `agent`-scoped.

The view names exactly one ordinary derived owner, a fixed server-selected manager actor, that
actor's lifecycle UID, and one opaque locally selected `instanceId`. The actor and instance id are
not client-selectable, and the view grants no second manager instance, other endpoint, or other owner.
It may reach only the manager instance's own `svc.manager.<instanceId>` registration/status,
pre-authorized immutable contract publication, endpoint rails, and the disjoint
`epgate.manager.<instanceId>` / `epcred.manager.<instanceId>.<credentialId>` family. It does not
confer the space signer, callout signer, owner secret, static provisioner credential, generic
stream/KV authority, or authority over another instance's gate, records, contracts, or
credentials. A manager-service credential is ledgered and gated exactly as this section requires;
its `holderPrincipal` is the derived-owner/fixed-actor principal, never the endpoint name.

The typed protocol includes two host-owned registration-maintenance operations. An
`evict-family-principal` request names `principals`, 1 to 256 distinct holders, so a restart's
family eviction costs one request and one family scan per 256 holders. The host MUST enumerate the
authenticated caller instance's `epcred.manager.<instanceId>.*` family once per request and refuse
unless every named principal is one of its holders. The host, using its signer, then mints the
bounded delivery-admin caller, requests `evictPrincipals` for the set, and returns `evictions`, one
closed `EvictionResult` per principal in request order; a garbled, foreign, misordered, or
contradictory result MUST NOT authorize. The participant never receives that credential. A
`reconcile-registration` request MAY name another manager instance in the same space only to clear
an abandoned governance-slot holder. The host MUST observe that target's frozen registration gate,
prove the freeze-holder `gone` under a complete liveness sweep, run the normal registration repair
with verified eviction, and refuse live, unknown, incomplete, unestablishable, or non-registration
states. There is no force path. A participant credential still gains no direct authority over the
target gate, records, family, or delivery-admin rail.

The bounded executor returned by `prepare` is renewable through `renew` for the same public nkey and
current open registration. A remote manager MUST obtain a fresh executor before maintenance or clean
deregistration when its retained credential is not healthy. A restart MUST drive family eviction
through the host operation before advancing its epoch. A registration blocked by a foreign frozen
manager governance slot MAY request guarded host reconciliation and retry exactly once.

The typed protocol also carries two host-owned MANAGED-AGENT operations,
`manager-managed-agent-enrollment` and `manager-managed-agent-prepare-retirement`. Both name the
authenticated owner's own managed agent and both require the caller's current `supervise` scope;
`spawn` and `admin` never imply it. Both MUST be authorized against the caller instance's CURRENT
open manager gate: the gate's principal MUST equal the server-derived serve principal, its process
epoch MUST equal the request's `serveEpoch`, and the request's registration proof MUST match the
proof the host issued for that registration revision and epoch. An enrollment request MUST carry the
SHA-256 digest of the agent's standing actor token and never the token itself, and MUST NOT carry a
lifecycle UID: the HOST selects it, because only the host can observe the retirement tombstones that
make a UID permanently unusable. A prepare-retirement request MUST name a target owner equal to the
authenticated owner, and its `opId` MUST equal `managedRetirementOpId(target.lifecycleUid)`,
recomputed by the host rather than trusted, so one lifecycle never carries two terminal operations.

These two operations mutate host-owned storage, so an implementation that owns no such storage MUST
refuse them with `unimplemented` rather than answering a manager-lifecycle phase for them. An auth
service that owns the actor ledger and the space's provisioning authority answers both itself, after
the same decision. Its enrollment writes the managed grant at a fresh host-minted lifecycle UID with
a parent the ledger's delegation envelope bounds it by, and then provisions that UID's durables. The
grant's read is the set those durables read: `target.allowSubscribe`, or `target.subscribe` when
that list is omitted or empty, and the bound measures the same set. The
parent is the request's supervising actor unless the request carries `spawn: { served, runId? }`, the
manager `spawn` request subject the participant served and, for a run's spawn, the run id. The host
reads every manager `spawn` request subject published in the space and takes the caller from a
subject it observed, never from the request's word: the broker locks the caller and a per-call nonce
into that subject. Each observed subject enrolls one helper: the first enrollment to name it within
the observation window takes it for that enrollment's helper (`target.actor`) and token digest, and a
later request naming the same helper under the same digest is that enrollment retried and is answered
again. A subject the host did not observe, or one taken for another helper or digest, MUST be refused
with `permission-denied`, as MUST one whose caller belongs to another owner. Without `runId` the
observed caller is the parent. With it, the run MUST be admitted on the requesting instance and not
revoked, and the caller MUST be that run's driver
(§14.8); the parent is then the run's starter, and the admission's `delegation` bounds the helper as
well. On the static path the manager bounds a helper by its caller's evidence instead (§13.15). A
provisioning failure revokes the grant and releases the footprint before the refusal. An enrollment
whose token digest matches the agent's standing grant is a retry of that enrollment and reads no
observation. It resolves its read set as a fresh enrollment does and MUST be refused with `conflict`
when that set is not the held grant's read, telling the caller to retire that lifecycle and enroll
again. A grant an older host wrote for a target that named only a subscribe set records no read, so
a retry of it is refused. A retry walks the held grant through its parent's
current delegation envelope and refuses
as a fresh enrollment would when the grant falls outside it;
otherwise it answers the held UID and provisions it again, and its failure leaves that grant in
place. While the agent holds a grant with another digest, enrollment MUST be refused
with `conflict` until that lifecycle's retirement is prepared, so a successor never takes a running
agent's grant. Its prepare-retirement releases the target UID's broker footprint and then revokes the
grant, only while the grant still names that UID, so a repeated request is harmless. The service runs
enrollment and prepare-retirement of one agent one at a time, and every revoke it performs names the
UID it releases. The enrolled agent's bearer exchanges at the public exchange face, so an auth
service without that face MUST refuse enrollment with `failed-precondition`. A host
platform that owns the writers MAY intercept them on its own authenticated route and obtain the
decision alone from the auth service's loopback door
`POST /manager-service-authority/verify-enrollment`, which carries the same loopback guards as the
managed-retire door (POST only, no `Origin`, JSON, the per-start capability, a closed
`{ owner, request }` body). That door MUST derive the caller's capability scope from its own ledger
and MUST NOT accept a scope from the caller. For an enrollment it decides the parent as above, bounds
the target by that parent through the ledger's delegation envelope and, for a run's spawn, by the
admission's `delegation`, refuses an over-wide target, and answers `parent`. It measures a list the
target omits as empty, so a host that fills an omitted list MUST fill it from no more than the
parent's grant. The host MUST record the parent on the grant it writes, so that narrowing or revoking
the parent reaches the helper. The door decides only: it mints nothing, writes nothing, and returns no
secret.

Two further host-owned kinds address the hosted runtime of an already-enrolled managed agent:
`manager-managed-agent-runtime-create` and `manager-managed-agent-runtime-status`. Each carries the
managed-agent envelope plus exactly `target: { owner, actor, lifecycleUid }`. Both schemas are closed:
an unknown top-level or target field, including `providerRef`, `handle`, or `name`, MUST be refused as
`bad-request` with no effect. There are no participant stop, adopt, or probe kinds. Both MUST be
authorized with the same gate, epoch, and proof checks as enrollment, with a target owner equal to the
authenticated owner, and with `supervise` read from the host's own ledger row for the manager actor.
The decision yields only the verified owner, instance id, manager actor, and target. A status decision
authorizes no effect. An implementation without host intent storage MUST refuse both with
`unimplemented`. The answer carries `state` (`reserved`, `creating`, `bound`, `create-unknown`,
`closing`, or `closed`), `readiness` (`ready`, `bound-not-ready`, or `none`), and an optional
`retirementPhase`. An enrollment result MAY carry `runtimeIntent: { state: "reserved" }`, which is
display-only and MUST NOT be treated as authority.

The host, not the participant, issues every data-account credential requiring the account signing
key. The only remote path is the lifecycle- and instance-bound typed protocol of §13.6; a broader
bearer or a generic credential-mint endpoint is non-conformant. Its gate is frozen before staged
material becomes usable; every release is preceded by the ledger write and gate CAS; and all
replay/idempotency coordinates bind the owner, fixed actor, lifecycle UID, instanceId, operation,
and public nkey. A remote manager may provision a managed descendant only after the host validates
that its current owner equals the manager-service owner and the current manager grant; that is a
same-owner validation seam, not delegated signer authority. Revocation freezes the one family,
rejects fresh material and new connections, and proceeds through the bounded renewal/verified
revocation policy below. It MUST NOT silently substitute static or local authority.

**Platform control authority (service view).** `platform-control` is a second CLOSED,
server-authored manager authority view, beside and separate from `manager-service`. It lets a host
platform run one control manager per assigned data account without any human's session. It is not a
profile name, not an exchange view, and not a generic host profile: the public exchange, the
loopback exchange, and every managed-agent secret exchange MUST refuse it as a view. Its holder is a
platform principal. The holder's owner is a platform owner token, `p_` followed by 26 base32-lower
characters, which the host derives from the space's owner secret, the space, and the assigned
data-account public key. This extends the §2 owner-token format for this view's principals and
their same-owner descendants only; everywhere else §2 stands. A platform owner token is disjoint
from every `u_` derived owner, from `local`, from nkeys, and from the §13.2 mode words. The host
MUST NOT derive it from, or bind it to, an IdP subject, and MUST NOT name a `u_` owner on this path.
No human is authenticated, and no ledger `supervise` scope is read or synthesized.

The view exists only while the host's current platform-control assignment names it. An assignment
is one row `{ space, accountPublicKey, instanceId, lifecycleUid, predecessorInstanceId?,
assignmentRevision, state }` that the platform backend alone writes. An account has at most one
current row, so it has at most one platform control instance. The host MUST read it fresh for every
request under this view
and MUST refuse, writing nothing, when it is absent or `revoked`, or when it disagrees with the
request's account, instance id, lifecycle UID, or assignment revision. A failed read MUST refuse
rather than reuse an earlier answer. The instance id and lifecycle UID are the assignment's, not
the caller's choice. They stay the same across the instance's restarts: a restart re-registers
through the same-principal registration barrier at an advanced process epoch and is never a second
instance. The actors are the same fixed functions of the instance id. When the assignment names a
`predecessorInstanceId`, the host MUST refuse `prepare` and `activate` with `failed-precondition`,
writing nothing, while that instance has a current `svc.manager` registration or a `frozen`
issuance gate. The host only reads that instance's rows. It MUST NOT probe, freeze, evict, revoke,
reopen, or deregister them. Apart from the
holder's owner and this authorization source, the view is the `manager-service` family: the same
`svc.manager.<instanceId>`, `epgate.manager.<instanceId>`, and
`epcred.manager.<instanceId>.<credentialId>` rows, ledgered and gated as this section requires, with
`holderPrincipal` the platform owner plus the fixed actor.

The view MUST NOT reach an instance whose issuance gate names another principal, including an
instance a signed-in human registered under `manager-service` and a manager registered under the
`local` owner. There is no force or takeover path between the two views. Such an instance retires
through its own owner's path before a platform instance may register. A re-registration never
changes an instance's owner, so the view never binds to an existing instance registered under
another owner. It registers its own assigned instance once that instance's predecessor has left
through the predecessor's own path. Both registration proofs bind the owner, so a `manager-service` proof never
validates under this view and a `platform-control` proof never validates under `manager-service`.

A manager holding this view MUST run with no local signing trust and no local or custodial runtime
(pooled control). The view confers no space signer, callout signer, owner secret, provisioner
credential, or launch authority. It provisions, enrolls, retires, validates, and authorizes only
same-owner descendants, whose owner is its own platform owner token, and grants nothing over a
human owner's agents.

**Environment manager authority.** `environment-manager` is a third CLOSED, host-authored
manager authority family. It admits one signerless manager and at most one managed child
lifecycle in one provider environment. It is not an exchange view or caller-selectable
profile. Every human, public and managed-agent exchange MUST refuse it. Its owner is the
ordinary derived owner recorded by the hosting authority, not a synthetic human ledger
row or the `platform-control` owner. Neither this family nor its bootstrap authenticates
an owner's IdP session.

Before issuance, the external authority MUST durably bind the environment record ID,
owner, space, data-account public key, manager instance ID, manager lifecycle UID,
bootstrap caller triple, admission revision and finite expiry. These coordinates come
from host admission, never guest claims. The bootstrap caller uses that manager lifecycle
UID and the sixth fixed actor `manager_bootstrap_<instanceId>`. This temporary actor is
separate from the five standing identities. Admission reserves the UID, creates
`epgate.manager.<instanceId>` open, then freezes it `open → frozen` under one host-selected
registration operation intent in the same admitted operation, all before bootstrap mint.
A gate is never born frozen under registration. The closed endpoint gate row gains the
optional immutable discriminator `authorityFamily: "environment-manager"`, written only
by this admission and retained by every subsequent CAS at open, frozen and retired states.
It cannot be cleared or reassigned. This extends §13.1's endpoint gate row grammar by
that single optional field and one literal value; any other value or new field remains
refused. This gate-level marker, not an op on an open gate, keeps symmetric family checks
effective after activation; existing open/op rules remain unchanged. Only the trusted host
may write this family's gate, repair cursor or credential ledger. The environment supervisor
and executor MUST NOT reuse the shipped `remote-manager` profile, whose direct writes
could remove this marker. Fixed mediated profiles below keep those writes host-only;
field-level immutability is not claimed for a credential that can publish the whole row. Staged environment, revision and operation coordinates use `stage.<opId>.…`
and the operation's normal retention. No new permanent `stage.` key is introduced.
The retained retired gate remains the permanent terminal family marker. The bootstrap mint writes `cred.<managerUid>.<credentialId>` with the native
`sourceChain: [root]` lineage (§13.1) and admission expiry, then CAS-pins that staged gate before returning
material. Only the fixed bootstrap profile is usable in this pre-preparation phase.
`prepare` continues that same registration operation, never a second intent.

Every issuance pins the admission revision through its native issuance-gate CAS.
Termination MUST fence that gate and revoke the bootstrap credential before marking the
private admission ended. Before activation it aborts registration `frozen → open`, then
runs retirement `open → frozen → retired` under its own intent. After activation it runs
retirement alone. No bootstrap mint or preparation is admitted without the admission's
same live registration intent, regardless of revision, so the abort's reopened interval
mints nothing. Gate advancement and verified revocation precede private admission ended. Issuance records the revision it observed in its gate intent and CAS; a termination
advance makes the stale CAS fail without returning material. A crash between those steps
leaves closing/retirement-pending state and resumes the same intent. Auth unavailability
cannot prevent bounded provider termination, but MUST NOT be reported as completed authority
retirement. The auth gate is the issuance fence; the private admission store records its
outcome. A fresh admission read alone MUST NOT permit material to escape a concurrent
termination. The admission also records the only permitted
child actor and its delegation ceiling. Its first enrollment binds one host-selected
child UID permanently; retirement does not permit a second child lifecycle. This is a
single-shot environment: a crashed child cannot be relaunched under its handed-off UID
(§13.17) or replaced by another UID. Lang reports that failure; a replacement requires a
new admitted environment. Missing
manager registration, child enrollment, run admission or terminal retirement policy MUST
refuse admission before bootstrap issuance.

The authority MUST re-read that binding for every request, bootstrap and renewal included.
An absent, expired, ended, destroyed or mismatched binding authorizes nothing. A failed
read refuses rather than reusing a previous decision. The registration, five manager
identities, process epochs, credential ledger and issuance-gate barriers remain those of
this section and §13.6. This family MUST NOT construct a human `supervise` holder or use a
platform-control holder to reach issuance. Credentials MUST expire no later than admission
expiry. A registration proof alone is not bearer authority: later requests require the
broker-authenticated, instance-bound caller issued for the corresponding manager identity,
the current registration proof and the current process fence. The host-issued proof MUST
also bind this authority family, environment ID and admission revision. A human or platform
registration proof MUST NOT validate here, nor an environment proof under either other
family. This requires a distinct, family-discriminated environment registration-proof
derivation binding the existing coordinates and artifacts plus environment ID and admission
revision; the current human/platform derivation cannot be reused unchanged. Admission MUST
refuse an instance already bound to another family, including the same owner's human
manager; it provides no takeover or force path.

Symmetrically, human `manager-service` and `platform-control` prepare, activate, renew,
maintenance and retirement MUST refuse `permission-denied` before effect for an instance or
lifecycle whose gate or stage names `environment-manager`, under any owner. They gain no
takeover or repair path through this insertion. The family check precedes any freeze, epoch
advance, liveness probe, eviction, deregistration or credential issue. An unreadable family
binding refuses. Environment-bound cleanup remains the external host's admitted path.

The manager and child MUST have separate effective OS users and private material stores.
Child launch material crosses into the child's store by value, never as paths into the
manager's store. The child MUST NOT receive the provider credential, hosting or model
sponsor key, account signing seed, owner IdP token, manager credentials or manager proof.
A runtime unable to enforce this boundary MUST refuse launch. Provider RUNNING alone
establishes neither manager nor child readiness. Their presence binds the opaque
environment record ID through the existing `environment` field.

**A read is never a fence; only a CAS write is.** JetStream `DIRECT.GET` may be served by a
follower or mirror and gives NO read-your-writes guarantee (a mint that *reads* the gate can
observe a stale `open` after a barrier froze it on the leader), so the auth bucket sets
`allow_direct=false` (§13.12) and every fence here is a leader-served, revision-pinned CAS
write. The mint protocol is **observe gate → write rows → CAS the gate → release**: the auth
path reads the gate (recording `state`, `generation`, and KV `revision`), writes the
`cred.`/`bysrc.` rows, then performs a **revision-pinned CAS update of `gate.<lifecycleUid>`
itself at the observed revision**; a leader write that fails if the gate changed at all,
and releases the credential only on CAS success with the gate still `open` at the same
generation. On CAS failure, `frozen`/`retired`, or any generation advance it aborts and marks
its own row revoked, never releasing. A barrier CASes the gate to `frozen` FIRST and only then
enumerates the family. The race is closed by **serialization on one key**, not by timing or
read freshness: freeze and mint-finalize are both CAS writes to the SAME gate key, so one
loses; a mint that wins wrote its rows before its winning CAS, so the barrier's later
enumeration sees them; a mint that loses never released. The ledger is written only by the
trusted auth path (§13.9 matrix; NATS binding: the auth KV, §13.12).

**Every lifecycle operation is a cross-bucket saga, never an implied transaction.** The
records head and the auth gate/ledger live in different buckets with no shared order, so
each operation persists its durable intent (the gate `op`, above) before touching the
second bucket, every crash boundary resumes the SAME operation from that intent, and the
safe orders are normative. **Initial activation, in order**: reserve the UID (create-only
`uid.<lifecycleUid>`, above) → create the issuance gate `frozen` carrying the activation
`op` (unmintable from birth; no credential is ever released under a frozen gate, per the
unledgered-mint rule) → CAS the alias head to the new mapping (`active`) → reopen the gate
at its first mintable generation as the operation's LAST step. A head-CAS loser
terminalizes its own orphan gate and burns its reserved UID (never deleting either); a
crash after the head CAS leaves the lifecycle active-but-unreachable, and recovery resumes
the same activation `opId`, never minting a second UID for one activation. **Takeover**
keeps the barrier order above (freeze → revoke + verified-evict → epoch head CAS LAST →
reopen). **Terminal retirement** keeps the barrier order below. No other head transition
exists: the head advances only inside these operations, and no epoch-advance or retire
seam is exposed outside the operation that completes its barrier.

Binding rule (normative): **durable** authority and state; sturdy handles, accepted goals,
checkpoint tokens and resumes, durable consumers and delivery state, ledger rows, bind
`(principal, lifecycleUid)` and survive supervised restart. **Live** authority, session
grants, reply attribution, serve/commit ownership, additionally binds the process epoch and
dies on restart. The alias alone authorizes nothing: a delayed or redelivered request, handle,
or teardown that names a recycled alias fails against the replacement because the lifecycle
UID differs. Endpoint daemons carry the same triple, with the **stable logical instance id**
(`instanceId`, `[a-z0-9]{26,32}`, ≥128 bits of CSPRNG entropy, persisted for the endpoint
lifetime) as their routable identity component. `instanceId` is **minted by the provisioner,
never reused, and unique within `(space, endpoint)`**, the allocator records it in the
instance's service record by create-only CAS and rejects collisions durably. Reply
attribution, scatter deduplication, queue ownership, and the event/timer planes all key on
it, so its uniqueness and entropy are load-bearing, not cosmetic. `instanceId` is to an
endpoint what `lifecycleUid` is to a managed agent, and both follow the same
restart-preserve / terminal-retire / epoch-fence rules.

**Cross-plane scoping.** Chat/DM/presence *subjects* keep the §3 grammar (the alias), but
their backing state is lifecycle-scoped: presence carries the current `lifecycleUid` (§6);
per-instance durable consumers, pending delivery cursors, durable memberships, history
cutoffs, and ACL/ledger rows key on `(principal, lifecycleUid)` (§8, §9). The DM subjects
(`inst.>`) DELIBERATELY stay alias-keyed; a second implementer MUST NOT uid-scope them; the
successor cut for DMs is the ACTIVATION FRONTIER (the DM stream sequence captured at the
lifecycle's provisioning, delivery starting at frontier+1, §8), and that frontier capture is
a leader-served read (the §13.9 read-service class), never a follower get. Explicit same-name
recreation inherits **no** predecessor authority or content: terminal retirement records
per-stream sequence cutoffs before the alias is freed, messages published while no lifecycle
is active do not flow to a later replacement, and retirement across streams is ordered and
reconciled (never assumed atomic). **Destructive cleanup is broker-enforced where the resource is broker-addressable**: durable
consumer names, ACL rows, KV record keys, and membership rows are lifecycle-keyed, the UID
is part of the resource NAME, and the teardown credential (the deprovisioner) is minted
target-pinned to `(principal, lifecycleUid)` by exact name, so a credential minted for
lifecycle A cannot even NAME lifecycle B's resources; the broker denies the stale delete
outright. Only resources the broker cannot see (the manager's local credential/token/health
files) fall back to a handler-side **delete-if-current** check carrying the retiring UID +
expected ownership revision. In both regimes the alias stays reserved until retirement and
cleanup have durably completed, so a stale detached teardown can never destroy a same-name
successor. **Terminal retirement is additionally a credential barrier, in order**: CAS the issuance
gate `open → frozen` carrying the durable retirement `op` FIRST (the bar: a staged mint
loses the gate CAS, exactly the mint-protocol race above; the gate revision moves, so a
mint that observed `open` cannot finalize) → CAS the head `active → retiring` bound to the
same `op.opId` (from this point every currency seam yields no current mapping and no
current epoch, and the alias is NOT replaceable) → revoke every
active credential-ledger row under the lifecycle prefix (all roots and all descendants,
credential ledger above), verifying revocation enforcement on every server as in the
takeover barrier → cluster-verified eviction of every revoked credential's live connections
(`evictPrincipal`, as in the takeover barrier above) → **drain the target's acceptance
obligations to quiescence** (§13.8: enumerate `oblig.<targetUid>.>`, settle every
unresolved row through its decision coordinate, and re-enumerate until an enumeration
finds none unsettled; every writer that observed the pre-`retiring` mapping is settled
HERE, before the cleaner below runs and before any frontier closes) → **fence the drain's
per-op repair principals** (the commit applier, pool-route reconciler, and effects canceller
minted inside the drain, `local.{epapl|eprec|epcan}_<opId-hash>`): cluster-verify eviction of
any live connection under each BEFORE the cleaner and BEFORE any frontier — the applier
especially, whose records-KV last-value write is returned to a normal reader regardless of the
per-stream frontier cutoff. These are self-minted data-account bearers with NO credential-ledger
row, so there is no connect-time deny-new: the guarantee here is **kill-live** (verified eviction
of currently-connected principals), NOT reconnect prevention; a fresh connect within the
bearer's TTL is the accepted residual NAMED per drain-repair profile in the §13.9 matrix (each
"RETIREMENT-FENCE residual" row), of the same kill-live-not-deny-new class §13.13 fences for the
plane connections (repair connections MUST be minted non-reconnecting so a verified eviction is
durable) → the trusted terminal **pool
cleaner** settles the lifecycle's expired and orphaned pool work under a DISTINCT,
separately minted, exact-pool scoped profile whose pool set is this operation's **effective
inventory**: the target's accepted `oblig.<lifecycleUid>.>` pool routes enumerated from the
SAME drained, now-`retiring` obligation set (so no new row can appear and the enumeration is
deterministic across resumes). The inventory is DISCOVERY-ONLY: the barrier takes no
caller-supplied pool hint, so every inventory entry is an obligation-discovered pool this target
holds accepted work on, and no pool ever enters the cleaner/executor grant without a backing
obligation. Confinement is the EXACT per-pool effective-inventory grant plus the
executor's per-item decision/horizon/retire-target checks (which bind HONEST execution, not a
compromised bearer): (§13.9
matrix row: bind-only on the pool's
pre-created durable, terminal-only ACK after the item's durable terminal fact, no consumer
create/update/delete, no raw stream DELETE; it never holds, reuses, or impersonates the
revoked owner's authority, which this barrier just killed) → **retire the cleaner
credential itself, verified, BEFORE any frontier closes**: once the cleaner has settled the
pool and proven it quiescent (every pre-existing owner ACK drained through `AckWait`, and a
fresh consumer read shows zero `num_pending` and zero `ack_pending`; a fire-and-forget ACK
is confirmed with `AckSync` or re-proven, never assumed), the barrier REVOKES the cleaner's
own bounded-lived credential and cluster-verifies eviction of its principal (`evictPrincipal`,
exactly as for the owner above), so no in-flight cleaner can ACK a redelivery or write a
terminal after the alias is reused; the cleaner's authority MUST be dead before the frontier
records → record the
per-stream retirement frontiers (the create-only, never-deleted `frontier.<lifecycleUid>`
record, §13.7: one key per retired lifecycle, recorded once under this operation's `opId`) →
CAS the gate `frozen → retired` (terminal; unlike
takeover, retirement never reopens it) → CAS the head `retiring → retired` → only then
free the alias, and a successor activates only with a freshly reserved UID. `retired` on
the head therefore ASSERTS completed cleanup: replacing a retired predecessor needs no
further proof, because nothing reaches `retired` without the barrier. Every boundary of
this sequence is crash-resumable through the durable `op` intent, and only the same
operation resumes it. A **managed** lifecycle's terminal retirement has exactly one operation
identity, `managedRetirementOpId(uid)`, whichever entry point requests it: the participant
manager's `retire-lifecycle` rail request (§13.2) or the host's loopback managed-retire door, which
finishes the retirement when that manager is gone and requires the managed grant to be revoked at
that UID first. Both entry points create or resume the same durable operation, and one process
never executes it twice concurrently. Chat/DM/presence subjects stay
alias-keyed, so without the revoke-and-verified-evict step a still-connected stale process
could keep speaking as the recycled alias. Where the deployment cannot revoke the credential
or cannot verify eviction, alias reuse is **forbidden**: a same-name respawn fails loud.
Supervised restart of the same UID retains all of it.
Intentional role-mailbox continuity across lifecycles is only available as an explicit,
separately authorized transfer operation, never an accidental consequence of string reuse.

### 13.2 Grammar

**Endpoint names.** An endpoint name is one or more DNS-shaped labels, each matching
`[a-z0-9]([a-z0-9-]*[a-z0-9])?` (no leading/trailing dash, no bare dashes; `_` MUST NOT
appear in a label). Single-label names (`manager`, `delivery`) are reserved for
endpoints shipped by this contract's reference implementation and require the space operator's
provisioning authority to serve; a third-party endpoint name MUST be reverse-DNS (two or more
labels under a domain its author controls, e.g. `com.acme.deploy`) and is mintable only under
the owner that registered that domain claim. In a wire subject the name is one token with `.`
replaced by `_` (`com_acme_deploy`); because `_` cannot appear in a label the mapping is
bijective. Name authority is the credential, never the registry (§13.9). Endpoint-name
tokens may contain `-` inside labels; they are never used to derive principal dash-form
names; control-surface consumer names are the §13.9 pinned grammars, each carrying a
stated collision-freedom argument, and none is ever parsed back into its components, so
the §2 dash-form separator stays unambiguous.

**Command tokens.** A command name is one token `[a-z0-9-]{1,32}`. The command is a validated
subject token so the broker enforces per-command authority (§13.9). `describe` and `cancel`
are reserved command names (§13.7, §13.6).

**Request subjects.** Three **addressing modes** under one kind `ep`, the mode token says
where a request routes, never which verb it is (the verb rides the envelope, §13.3/§13.5):
`one` (queue-group anycast: exactly one class member), `all` (scatter: every instance),
`inst` (one instance by its stable triple). The `one` rail's queue group is canonically
named by the endpoint-name token, and serve subscriptions to it are **queue-qualified
only** (§13.9): no credential can plain-subscribe the class rail, which is what keeps
per-request nonces visible only to the queue-selected instance. Every request carries the caller as **three**
forge-locked tokens `<owner>.<actor>.<uid>` (principal + lifecycle UID, §13.1) followed by a
caller-chosen unguessable **nonce** token (`[A-Za-z0-9_-]{22,64}`, ≥128 bits of CSPRNG
entropy; one outstanding call per nonce; reuse before the prior call resolves is a caller
error and the reply rail MUST treat the earlier subscription as dead); always, on calls and
casts alike, so one grant row covers both verbs and no shape is distinguished by counting. A
command whose contract declares it **targeted** carries an **authorization-mode token** and,
per mode, zero to three pinned target tokens between the command and the caller:

| Form | Subject | Tokens |
| --- | --- | --- |
| Class, untargeted | `cotal.<space>.ep.one.<endpoint>.<command>.<owner>.<actor>.<uid>.<nonce>` | 10 |
| Class, `self` | `cotal.<space>.ep.one.<endpoint>.<command>.self.<owner>.<actor>.<uid>.<nonce>` | 11 |
| Class, `owner`/`any` | `cotal.<space>.ep.one.<endpoint>.<command>.<authz>.<tOwner>.<owner>.<actor>.<uid>.<nonce>` | 12 |
| Class, `child`/`ledger` | `cotal.<space>.ep.one.<endpoint>.<command>.<authz>.<tOwner>.<owner>.<actor>.<uid>.<nonce>` | 12 |
| Class, `handle` | `cotal.<space>.ep.one.<endpoint>.<command>.handle.<tOwner>.<tActor>.<tUid>.<owner>.<actor>.<uid>.<nonce>` | 14 |
| Class, `exact` | `cotal.<space>.ep.one.<endpoint>.<command>.exact.<tOwner>.<tActor>.<tUid>.<owner>.<actor>.<uid>.<nonce>` | 14 |
| Scatter | as class forms with mode token `all` | 10-14 |
| Instance | `cotal.<space>.ep.inst.<endpoint>.<instanceId>.<command>[.<authz>[.<target tokens per mode>]].<owner>.<actor>.<uid>.<nonce>` | 11-15 |
| Reply | `cotal.<space>.ep.reply.<endpoint>.<instanceId>.<epoch>.<owner>.<actor>.<uid>.<nonce>` | 11 |
| Versioned rail (§13.15) | every form above with `ep.v1` in place of `ep` and `<generation>` inserted between `<uid>` and `<nonce>` | 12-17 |

**The versioned rail.** An **issued** caller (§13.15) rides `cotal.<space>.ep.v1.…`: the same
forms, one more token, the caller's issued `<generation>` (32 lowercase hex characters), placed
between `<uid>` and `<nonce>` on every request and reply form. The legacy `ep.` rail and the
`ep.v1.` rail are disjoint subject spaces at the broker: an issued credential holds rows on the
versioned rail only, a legacy credential holds rows on the legacy rail only, and an endpoint
serves both. A responder derives the reply subject from the authenticated request subject on
whichever rail it arrived, generation included. `v1` versions the rail encoding, not the
protocol.

**Single-owner endpoint names (normative).** An endpoint name binds to exactly ONE owner
(§13.9: operator-provisioned core names, domain-owner-bound reverse-DNS names), so the name
token alone determines the serving owner and instance-addressed subjects carry **no owner
tokens**: `(endpoint, instanceId)` is the complete routable instance address. Two parties
wanting the "same" name use their own reverse-DNS names; an owner-qualified shared-name form,
if ever wanted, would be a later additive subject form, not a change to these. This trades an
already-forbidden expressiveness for structurally smaller subjects and credentials.

**Remote manager service.** The core `manager` name remains operator-governed: a
`manager-service` bearer (§13.1) does not transfer its name authority or create a generic
user-owned endpoint. It is the one closed user-auth exception to the single-owner endpoint rule:
the host may authorize one `manager` service instance whose service-record owner and serving
principal are the bearer's derived owner plus fixed server-selected manager actor. The exception is
scoped by the server-authorized lifecycle UID and opaque globally unique instance id, so the
`(manager, instanceId)` route stays unambiguous and no registration can be mistaken for another
owner's. The standard `ep` grammar is unchanged: the bearer reaches only that exact manager
instance, while the manager's own agent-control requests use the existing owner and same-owner
descendant checks. No endpoint name, target form, or wildcard is added for this view.

The target's **lifecycle UID is body-carried, not a subject token** (`target.lifecycleUid`,
§13.3): a grant could only ever wildcard it (targets are dynamic; the UID is unknowable at
mint time), so a token there would add zero broker enforcement while costing every targeted
grant row a token, the trusted validator, not the broker, compares the expected UID against
the current mapping (§13.1). The one exception is `handle` mode: at handle redemption the
target's UID IS known and current, so the redemption-minted form pins the full target triple
as subject tokens (below); pin what is knowable at mint time; body-carry only what is not.
Every form stays within the NATS 16-token recommendation.

**Explicit discrimination (never arity counting).** The forms are distinguished by the token
after `<command>`: it is either one of the six reserved authorization-mode tokens (`self`,
`owner`, `any`, `child`, `ledger`, `handle`) or the caller's owner token, and the two sets
are disjoint by construction, because an owner token is `local` or `u_`+base32 (§2), never a
bare mode word. The target-block arity then follows the mode (`self`: none;
`owner`/`any`/`child`/`ledger`: one `<tOwner>` token; `handle`: three,
`<tOwner>.<tActor>.<tUid>`); a closed set at a fixed position, exactly the property that
makes per-mode arity safe. A parser dispatches on that set; a subject matching no defined shape
has no sender and MUST NOT be handled.

**Token bounds (normative).** On the endpoint rails every identity token is bounded:
`owner` ≤ 64, `actor` ≤ 64, `command` ≤ 32, `endpoint` ≤ 64, nonce and ids ≤ 64 characters;
`lifecycleUid` and `instanceId` are bounded by their single defining grammar
`[a-z0-9]{26,32}` (§13.1); deliberately not restated here, so the bound cannot drift from
the definition. A total request or reply subject MUST NOT
exceed 1024 bytes; implementations validate fail-loud at build time. (Transport headroom:
the reference deployment raises `max_control_line` to 64 KiB; the PUB line is never the
binding constraint; minted-credential size is, §13.9.)

**The authorization-mode token** (`<authz>`) makes the authority gradient explicit and
broker-enforced where it is statically expressible, and honestly validator-primary where it is
not. Seven modes:

- `self`, the target IS the caller: the form carries **no target tokens and no body
  `target`** (a supplied one is `target-mismatch`, never ignored); the endpoint derives the
  target from the broker-authenticated caller triple in the same subject. Fully
  broker-confined, including the lifecycle UID, because the caller's own `<uid>` token is
  the target's UID, forge-locked by the mint: a stale lifecycle's credential cannot even
  publish the successor's subject.
- `owner`, owner-domain: the target block is `<authz>.<tOwner>` (ONE target token); grants
  pin `<tOwner>` to the caller's own owner (standing mints; a handle redemption instead pins
  the issuer-signed target owner, §13.6). The target actor and expected lifecycle UID are
  body-carried (`target`) and validator-checked against the current mapping, the broker
  cannot express "any actor under my owner, currently mapped to this UID". An `owner`-mode
  grant is NEVER minted with a wildcard target owner. Broker-confined on the owner; validator
  on the rest.
- `any`, unrestricted target owner (`<authz>.<tOwner>` with `*`): a distinct mode mintable
  only for operator/admin capabilities, so no widening of an `owner` grant can ever reach
  it. Validator-checked target as for `owner`.
- `handle`, **redemption-minted only** (§13.6): the target block is
  `handle.<tOwner>.<tActor>.<tUid>` (THREE target tokens), each a literal pinned at
  redemption from the issuer-signed grant against the then-current mapping. Never a standing
  capability, never wildcarded. Broker-confined on the full target triple; the validator
  re-checks only currency; a subject `<tUid>` that no longer matches the current mapping is
  `expired`.
- `exact`, **privileged exact incarnation, minted only through its owning profile** (Cotal
  #399): the target block is `exact.<tOwner>.<tActor>.<tUid>` (THREE target tokens), the same
  literal-triple shape `handle` carries, minus the redemption promise. There is no issuer-signed
  artifact, no redemption step, and no `sourceChain`; the row is built directly by the profile
  that owns this command under root authority for one target it names, never a standing
  capability, never wildcarded. Broker-confined on the full target triple, exactly as `handle`;
  the validator re-checks only currency, and a subject `<tUid>` that no longer matches the
  current mapping is `expired`. A generic reader of this mode infers precisely a privileged,
  grant-pinned exact triple with a current-mapping check, and nothing more.
- `child`, static-mesh own-child (`spawner == caller`): a **distinct trusted-validator form**.
  The grant means "may ask this validator", not "already authorized"; the handler MUST
  fresh-check the immutable spawner relation against durable state and fail closed. Its
  `<tOwner>` ceiling is the caller's own owner, as for `owner` mode (a static-mesh child
  shares its spawner's owner).
- `ledger`, fresh-ledger escalation: a distinct trusted-validator form; the handler MUST
  fresh-read the authorization ledger and fail closed on lookup failure, timeout, or absence.
  Its grants pin literal `<tOwner>` values named at mint; a wildcard target owner in `ledger`
  mode is mintable only for operator/admin profiles.

`any`, `child`, `ledger`, `handle`, and `exact` are never wildcard-reachable from a `self`/`owner`
grant (distinct token ⇒ distinct subject ⇒ distinct grant row). A handler MUST resolve the target (the
revision-pinned `(alias, lifecycleUid)` mapping, §13.1) immediately before effect and reject
any request whose body target disagrees with the subject target tokens (`target-mismatch`) or
whose expected target lifecycle UID does not match the current mapping (`expired`). The
subject, never the body, is the authorization boundary; handler policy only narrows.

A responder that finds no current mapping for the target refuses `expired` with `outcome:
not-executed` and `details[].kind = ai.cotal.ep.target-unmapped`, which says the instance that
answered holds no mapping for it. A responder whose mappings are instance-local, as a manager's
are for the agents it hosts, cannot tell a target another instance hosts from one retired
everywhere. A caller that reached it on the class rail MAY therefore re-resolve and re-issue, as
after a bind refusal; on the `inst` rail the refusal is that instance's answer.

**Replies.** Every reply rides the dedicated reply rail above, **deterministically derived
from the authenticated request subject**: the responder copies the caller triple and nonce
from the request subject and prefixes its own endpoint/instance/epoch tokens (the owner is
determined by the endpoint name; no owner tokens appear). A responder
MUST ignore any transport- or payload-supplied reply target (the confused-deputy boundary).
The grants are exact-arity, no `>` tail admits subjects outside the grammar: the caller's
read grant is its own rail (`ep.reply.*.*.*.<owner>.<actor>.<uid>.*`), so it reads only
replies addressed to it; the responder's publish grant pins its own instance triple and
epoch (`ep.reply.<endpoint>.<iId>.<epoch>.*.*.*.*`), so the answering instance and
epoch are read off the broker-authenticated reply subject, never trusted from the payload.
Two properties, enforced differently, stated precisely: **attribution** (who answered) is
broker-enforced by the responder's pinned prefix; **addressing** (whom a responder may
answer) is capability-by-secret, the responder's grant spans all caller suffixes, and what
confines it to the requester is possession of the unguessable per-request nonce, which only
the request's recipients hold. A stale process (superseded epoch) publishes attributably
stale replies that callers reject; scatter gathers additionally reject replies from
instances outside the frozen expected set (§13.5).

**Incarnation admission (the bound-incarnation fence).** Rejecting a reply is a REPORT, not a
guard: it happens after the responder has already handled the request. On the class rail the
queue picks the responder, so a caller that resolved incarnation B can have its command executed
by A and then be told the call failed — with no way to say whether any effect landed. A caller
that will accept an effect only from the incarnation it resolved therefore declares it in the
request (`bind`, §13.3), and a responder that is not that incarnation **MUST refuse it at the
pre-effect seam** — before args validation, before target resolution, and before the §13.6/§13.10
governed gate, which may consume a one-use payment proof. The refusal carries
`ai.cotal.ep.bind-refused` and means the command did not run, so re-resolving and re-issuing
cannot duplicate an effect; that is the distinction `ai.cotal.ep.unbound-responder` (raised by the
caller, on the reply) cannot make. `bind` is a caller declaration and never authority: it can only
narrow a request the subject already routed, and attribution still comes from the reply subject —
a refusal attributed to the very incarnation the caller bound is incoherent and MUST be rejected
(`internal`) rather than honored. A responder that does not implement the fence ignores the field
(§5) and executes; the caller-side check remains the only protection in that skewed pair.

The **caller's** process epoch is
deliberately NOT encoded in the rails: reply consumption binds to the requesting process
because a caller MUST subscribe the exact concrete nonce subject before publishing a call
and MUST NOT persist nonces; a restarted successor never holds the predecessor's nonce
subscriptions, so in-flight calls die with the process (they are ephemeral by definition)
and a late reply is unreadable rather than misdelivered.

**Event and journal subjects.** Endpoint-published planes, captured by per-space streams
(§13.12); the publishing instance's identity is forge-locked into the subject:

| Plane | Subject |
| --- | --- |
| Events | `cotal.<space>.epe.<endpoint>.<instanceId>.<epoch>.<topic...>` |
| Canonical facts | `cotal.<space>.epf.<endpoint>.<topic...>` |
| Submissions | `cotal.<space>.epj.<endpoint>.<command>[.<authz>[.<target tokens per mode>]].<owner>.<actor>.<uid>` |
| Timers | `cotal.<space>.ept.<endpoint>.<instanceId>.<epoch>.<timerId>.<schedule\|armed\|fire>` |
| Record writes | `cotal.<space>.epr.<endpoint>.<instanceId>.<epoch>.<kind>.<qualifier...>` (mediated record-writer ingress; the instance's epoch-pinned rail for `svc`/`goal`/`cp` status writes; consumed ONLY by the record writer, which reads the writing epoch from the broker-authenticated subject, never from payload, §13.9) |
| Contract artifacts | `cotal.<space>.epc.<digest-hex>` (one immutable artifact per subject; `<digest-hex>` is the artifact's SHA-256 hex, 64 chars; the `sha256:` prefix is not a subject token; §13.7) |
| Work pools | `cotal.<space>.epw.<endpoint>.<pool>.<cOwner>.<cActor>.<cUid>.<id>` (one item per subject; the trailing four tokens are the item's **acceptance identity**; the accepted submission's caller triple + request id, §13.6) |
| Sessions | `cotal.<space>.eps.<endpoint>.<sessionId>.<epoch>.<in\|out>` |

Events carry the publishing instance's **epoch as a subject token**, pinned by the serve
grant, so a superseded process cannot emit progress indistinguishable from the current
incarnation's; readers match the current (or goal-accepted) epoch and treat stale-epoch
events as attributably stale. A **targeted** journal command carries the same authz/target
block in its submission subject as its request forms, so the broker confines targeted
journal work exactly as it confines calls; the canonicalizer additionally requires exact
body/subject agreement before acceptance. Timers use three forms: `.schedule` is the
instance-published **schedule request**, captured by a stream with message schedules
DISABLED, so any client-set scheduling header is inert bytes, and the mediated timer writer
rejects a request carrying one; `.armed` holds the **authoritative schedule message**,
published only by the mediated timer writer (§13.9), which derives the ADR-51
`Nats-Schedule-Target`, the sibling `.fire` subject, from the broker-authenticated
REQUEST subject's own tokens, never from any payload or header (a schedule's target MUST
differ from its publish subject per ADR-51; replacement is the writer's same-subject
publish on `.armed`); `.fire` is where fires appear. An instance's serve grant covers
**only `.schedule`** (epoch-pinned); no client credential holds `.armed` or `.fire`
publish; fired messages are written by the broker's scheduler alone, and the handler
validates the carried `(timerId, generation)` against current status AND
`now ≥ the authoritative deadline` AND that the broker-authored scheduler-origin header
names its own exact sibling `.armed` subject (§13.12) before acting.

Reserved event topics: `ev.<cluster>.<event>` (cluster events), `goal.<cOwner>.<cActor>.
<cUid>.<goalId>.<t>` (per-goal action progress; the caller identity in the subject gives
mint-time read containment), `cp.<token>.<t>` (checkpoint transitions). Reserved fact topics:
`dec.<cOwner>.<cActor>.<cUid>.<id>` (canonical decisions (accepted/rejected) caller-scoped, §13.4), `quar.<sourceSeq>` (poison quarantine, §13.4; its own family,
disjoint from the caller-id `dec` namespace by construction), `goal.<cOwner>.<cActor>.<cUid>.<goalId>.result` (terminal
results), `wrk.<pool>.<cOwner>.<cActor>.<cUid>.<id>` (per-work-item terminal results,
keyed by the item's acceptance identity, §13.5/§13.6), `eff.<cOwner>.<cActor>.<cUid>.<id>`
(per-request effect-complete facts for non-action effects commands, §13.9), `cp.<token>` (one-use checkpoint
resume, journaled by create-only CAS, §13.6),
`receipt.<cOwner>.<cActor>.<cUid>.<id>.<sourceSeq>`
(caller-scoped; request ids are caller-chosen, so an endpoint-wide `receipt.<id>` would
let two callers collide and read each other's receipts, and **execution-scoped**: the
accepted submission's `sourceSeq` is unique per execution, so a request id lawfully reused
after its decision retention expires (§13.4) mints a NEW receipt subject instead of
appending to the old one, where a last-by-subject read would have hidden the earlier
receipt for the rest of its 90-day retention). Submissions are publishable directly by capability holders
and are **explicitly untrusted** (§13.4); canonical fact subjects are publishable only by
their mediated writer (§13.9). `<id>`, `<goalId>`, `<timerId>`, `<token>`, `<sessionId>` are
single tokens `[A-Za-z0-9_-]{1,64}`.

The v0 subjects `cotal.<space>.ctl.>` and `cotal.<space>.control.>` are retired: nothing
serves them and no post-cut credential carries a grant on them. `trace.<instance>` remains reserved,
unchanged. `<pool>` is a single token `[a-z0-9-]{1,32}` (command-token grammar).

### 13.3 Envelope

Requests, replies, submissions, events, facts, and progress payloads are UTF-8 JSON. The
envelope is versioned and typed; `ControlRequest`/`ControlReply` are deleted.

Ephemeral endpoint serving MUST discard frames carrying native stored-message provenance
headers (`Nats-Stream`, `Nats-Subject`, `Nats-Sequence` or `Nats-Time-Stamp`) before envelope
parsing, authorization, dispatch or a derived reply. Header names are case-insensitive; an
empty value still carries the marker. This rule covers both endpoint rails and all three
routes. Other request headers remain permitted. Absence of these markers does not replace
any of the subject, envelope, grant or application-admission checks below.

`EndpointRequest`:

| Field | Type | Req | Notes |
| --- | --- | --- | --- |
| `v` | `1` | MUST | envelope schema version (independent of the wire `protocolVersion`; the envelope starts at its own v1 inside the v0.4 revision); other values rejected (`unsupported-version`) |
| `id` | string | MUST | caller-chosen request id, `[A-Za-z0-9_-]{1,64}`; the idempotency key at the declared scope (§13.8), realized on journaled planes by the caller-scoped decision CAS (§13.4), never by a transport header |
| `op` | object | MUST | `{ endpoint, command, inputDigest, outputDigest }`; MUST agree with the subject (`op-mismatch`). The digests bind the invocation to the described contract and are **both REQUIRED on every command except `describe`** (the discovery bootstrap), unconditional, because every command declares both schemas: a side with no payload declares the canonical void schema (§13.7), whose digest exists like any other. A serving member rejects a missing digest (`contract-mismatch`) before any effect, and one that cannot honor a pinned digest replies `contract-mismatch`, never coerces |
| `class` | `ephemeral` \| `journal` | MUST | the submission's declared delivery contract; MUST equal the command's contract class (`class-mismatch`); immutable per submission. (`record` is a state contract, never a request class; the action composite is a command marker, not a class; an action command's submissions are `journal`) |
| `replyExpected` | boolean | MUST | the verb: `true` = call (a reply is expected on the reply rail; `deadlineMs` required; the caller subscribes its exact nonce before publishing), `false` = cast (fire-and-forget; a responder MUST NOT reply). The subject shape is identical for both; the verb never changes the grammar |
| `goalId` | string | action commands | MUST for a command whose contract declares the action composite: the client-generated goal id (§13.6); absent otherwise. `id` remains the per-request idempotency key |
| `target` | object | per mode | `{ owner, actor, lifecycleUid, mappingRevision? }`. **Absent for `self`** (and for untargeted ops): a supplied one is `target-mismatch`, never ignored. **Required for `owner`/`any`/`child`/`ledger`/`handle`**: `owner` MUST equal the subject `<tOwner>` token (`target-mismatch`); `actor` and `lifecycleUid` are validator-compared against the current mapping (`expired` on mismatch), and in `handle` mode MUST additionally equal the subject `<tActor>`/`<tUid>` tokens (`target-mismatch`); `mappingRevision`, when present, additionally pins the exact mapping revision the caller observed |
| `bind` | object | MAY | `{ instanceId, epoch }` — the incarnation the caller's `describe` resolved against. A responder whose own `(instanceId, epoch)` differs **MUST refuse before any effect**, at the pre-effect seam and ahead of the governed gate: `failed-precondition` when a different instance received it, `expired` when the same instance is at another epoch, both carrying `details[].kind = ai.cotal.ep.bind-refused`, which asserts the command **did not run**. **Absent on `describe`** (the bootstrap that produces the bind; a supplied one is `bad-request`) and **absent on the scatter rail** (which addresses every incarnation; `bad-request`). On the `inst` rail it MUST name the subject's instance (`bad-request` otherwise) and adds the epoch the subject grammar has no token for. It confers nothing and can only make a responder the subject already reached refuse, so it satisfies monotonic attenuation |
| `args` | object | MAY | validated against the input schema before any effect (`bad-request`) |
| `from` | `EndpointRef` | MUST | as §5; `from.id` MUST equal the subject sender principal, and the sender UID token MUST match the caller's minted lifecycle UID (broker-enforced by the grant) |
| `deadlineMs` | number | MUST for call/scatter and journal submissions | caller deadline budget; bounded, never unbounded. On a journal-class submission it is the **decision deadline**: the bound within which the caller expects its durable decision fact (§13.4) |
| `correlation` | object | MAY | `{ traceparent?, tracestate?, baggage? }` per W3C Trace Context; propagated to downstream calls, events, facts, receipts |
| `auth` | string | MAY | opaque signed authorization-context slot (capability handle, obligations, payment proof). Opaque to the transport, never to identity: its **`authDigest`** (§13.4 fingerprint) is `sha256:<hex>` over the UTF-8 bytes of this string **exactly as carried**; the slot is already a canonical signed artifact, so it is digested as bytes, never re-canonicalized, and is absent from the fingerprint iff `auth` is absent |

`EndpointReply`:

| Field | Type | Req | Notes |
| --- | --- | --- | --- |
| `v` | `1` | MUST | |
| `id` | string | MUST | echoes the request `id` |
| `ok` | boolean | MUST | |
| `data` | any JSON | MAY | present iff `ok`; validated against the output schema |
| `error` | object | iff `!ok` | `{ code, message, details?[], outcome? }`; codes below; `details[]` entries carry reverse-DNS `kind`; `outcome` per **Effect outcome** below |
| `receipt` | string | MAY | opaque signed receipt slot (§13.10) |

**Effect outcome.** An error reply MAY carry `error.outcome`, one of `executed`, `not-executed`,
or `unknown`, stating whether the command's effect occurred. It is emitted by the **responder**,
which is the only party that knows: a responder that refuses BEFORE dispatching to the handler
MUST carry `not-executed`, and one that refuses AFTER the handler has run MUST carry `executed`.
A responder that cannot distinguish the two MUST carry `unknown` rather than guess. An error
reply that omits `outcome` MUST be read as `unknown`. A handler that refuses before its command's
first effect has started SHOULD carry `not-executed`, because nothing occurred. A handler that
refuses after an effect may have started MUST NOT carry `not-executed`.

`outcome` describes a reply, and only a reply. A refusal a CALLER raises locally is not an
`EndpointReply` and carries no `outcome` field. It does not follow that the caller knows nothing:
it MUST classify the refusal from what it observed, and only one of the four cases below is
genuinely `unknown`.

- **Refused before publication** — the request was never put on the wire. The caller knows the
  effect did not occur and MUST classify it `not-executed`. Treating this as `unknown` suppresses
  a retry that is provably safe, including for a `write`.
- **Refused while holding a reply** — the caller parsed a reply and then rejected it for a reason
  of its own, the §13.2 post-reply currency check being the case in this document. What the caller
  knows comes from the reply it holds: an `ok:true` reply means the handler ran to completion, so
  the refusal is `executed`; an `ok:false` reply carries the responder's own `outcome`, which the
  caller MUST adopt rather than overwrite. Discarding a held reply's outcome because the caller
  went on to reject the reply loses the one fact the responder was in a position to state.
- **Answered by the broker with no responders** — the request subject had zero subscribers, and
  the broker says so on the reserved no-responders sentinel. That is a positive, broker-attested
  fact that nothing received the request, so it is `not-executed`, not merely unanswered. A caller
  MUST trust it ONLY on that reserved sentinel, which carries no responder publish grant: the same
  status on an ordinary reply subject is a responder's own claim and proves nothing about
  delivery.
- **No reply observed** — a deadline that expires with no answer at all, a transport failure after
  publication, any path where the caller cannot tell whether the request was handled. This is
  `unknown`, and it is the only local case that is.

The reference connector reports an unanswered submitted persona definition as an unknown write
outcome and directs the caller to read the catalog on the same manager before acting.

A caller **MUST NOT infer execution from the mere arrival of a reply**: a reply proves the request
was HANDLED, never that it executed. The two differ on every path where a responder refuses before
the handler — the version, class, target, sender, authz, contract, and guard checks all publish
`ok:false` having executed nothing, and each of those replies says so in its own `outcome`.

`outcome` exists because a refusal code alone cannot carry this fact: the same code and the same
message are correct for a request that ran and for one that never left, and a caller that cannot
tell them apart and retries duplicates the effect. `effect` (§13.7) tells a client whether a
repeat is safe; `outcome` tells the caller what already happened. Neither substitutes for the
other, and a `write` command refused with `unknown` is precisely the case where no automatic
recovery is available and the decision belongs to the caller.

`outcome` is NOT a goal's terminal state. An action accepted under §13.6 reports its result as a
goal fact; an accepted action whose caller then loses its follow has an `outcome` of `executed`
for the SUBMISSION and no terminal state at all, which are different facts about different
things. `outcome` MUST NOT be used to report, replace, or summarize a goal outcome.

The answering instance, its epoch, and the addressee are read from the **reply subject**
(§13.2), not from payload fields; a payload claim of either is advisory display data only.

Every other plane is typed too: a journaled **submission** is an `EndpointRequest` (same
envelope, published to `epj`); an **event** (incl. per-goal progress) is
`{ v: 1, topic, ts, data, correlation? }`; an **acceptance fact** is the `AcceptanceFact` of
§13.4; a **terminal result fact** carries the goal's terminal state (one of the five
terminal values of §13.6), outcome digest, and
result payload (or its digest-pinned reference). All are runtime-validated at their
consuming boundary.

**Monotonic attenuation (invariant).** Envelope content, the `auth` slot, a handle,
obligations; may only narrow what the presenting credential already permits, never widen it.
A handler that honors envelope content as authority beyond the broker grant is non-conformant.
Authority *conferral* exists only as trusted redemption (§13.6 capability handle).

**Error catalog.** `code` is one token: `bad-request`, `unsupported-version`, `op-mismatch`,
`class-mismatch`, `target-mismatch`, `sender-mismatch`, `unauthenticated`,
`permission-denied`, `not-found`, `already-exists`, `conflict` (CAS/fencing loss,
fingerprint conflict, duplicate resume), `contract-mismatch`, `contract-invalid` (schema
outside the profile / over budget at registration), `failed-precondition`,
`deadline-exceeded`, `cancelled`, `expired` (lease, handle, lifecycle UID, epoch, token),
`unavailable` (no responder), `unimplemented`, `resource-exhausted`, `internal`. Extensions
add codes only under reverse-DNS. A `code` (catalog or extension) is one token of at
most **64 bytes**, so every fact shape that embeds one (`RejectionFact`, `QuarantineFact`)
stays bounded by construction and the §13.12 fact fixture is a true worst case.

### 13.4 Delivery contracts

Three delivery contracts, chosen per command class, declared in the contract, immutable per
submission. Decision rule: crash means "just re-ask" → **ephemeral**; long-lived state
something converges on → **record**; must survive restart, be audited, metered, or
compensated → **journal**. Wrong-class submission fails loud.

**Ephemeral**, request/reply on the `ep` rails; no broker persistence; at-most-once effect
unless the command is idempotent by `id`. No-responder is a loud `unavailable`.

**Record**, a `{kind, schema, spec, status, meta}` resource in the per-space records bucket,
stored as **two keys with independent revisions**: `<key>.spec` and `<key>.status`. The split
is the broker-enforced writer boundary: the spec-writer and status-writer roles hold publish
grants on their own key only (per-kind writer table, §13.9). Writes use per-key CAS; a lost
race is a loud `conflict`. The merged logical read returns both
revisions and carries `status.observedSpecRevision`; a reader treats
`observedSpecRevision < spec.revision` as a stale-but-valid level-triggered projection, not
an error, and `observedSpecRevision > spec.revision` (a lagging spec read, possible across
replica freshness points) as its own signal to re-read the spec key, bounded retries until
caught up or the caller's deadline, never trusting the mismatched pair. Watch delivers
current values then deltas per key; a watcher that falls behind MUST re-read both keys and
resume, never patch forward across a gap. Records are
bounded (§13.8).

**Journal**, an explicitly **untrusted at-least-once submission log** feeding **canonical
accepted-fact subjects** with a mediated writer; effects consume only canonical facts, never
raw submissions.

1. A journaled submission is published to the submission plane (`epj`) as a **plain append**:
   submitters MUST NOT set `Nats-Msg-Id`, and native dedupe is **not relied upon**, the
   server does not accept a zero duplicate window (§13.12), so the reference config sets the
   server minimum and the guarantee rests on the header rule, not the window: a conformant
   submission carries no dedupe header and cannot be suppressed by one. Native broker dedupe
   keys on a caller-set header value compared
   **stream-wide**, so on a shared submissions stream any writer could pre-seed a predicted
   header value from its own allowed subject and silently suppress another caller's first
   submission for a full dedupe window, a cross-caller denial that no "advisory" framing
   makes safe; with the MUST NOT in force, a hostile header-bearing publish can suppress only
   another non-conformant header-bearing write. Transport retries therefore simply append
   again; the caller-scoped decision
   CAS below resolves every copy to one decision. Submission subjects and fact subjects are
   disjoint by construction (§13.2), so a submission credential cannot write a fact.
2. The **semantic fingerprint** covers every effect-defining dimension, the fingerprint
   object is `{endpoint, command,
   class, authz?, target?: {owner, actor, lifecycleUid, mappingRevision?}, inputDigest,
   outputDigest, args, authDigest?, caller: {id, lifecycleUid}, goalId?, id}`, and the
   fingerprint VALUE is that object's `sha256:<hex>` content digest per §13.7 (strict
   RFC 8785 over I-JSON, the SAME canonicalization every contract artifact uses; one
   canonicalizer, never a second): absent optional fields are OMITTED from the object, never
   written `null`, so two implementations digest identical bytes, which also makes the
   fingerprint **computable for EVERY parseable submission**, however incomplete: a
   parseable envelope missing `class` or digests fingerprints the subset it carries and is
   rejected with that fingerprint. **"Parseable" here means canonicalizable I-JSON**, not
   merely syntactically valid JSON: bytes that parse but cannot be canonicalized, duplicate
   object names, a lone surrogate, a non-finite or out-of-I-JSON-range number; have no
   interoperable RFC 8785 form and therefore no fingerprint, so they take the quarantine
   path exactly as unparseable bytes and an invalid `id` do (§13.4 item 3: raw-byte digest,
   no fingerprint). Every submission thus has exactly one terminal path. Same id +
   same fingerprint is the same request (idempotent, first-wins); same id + different
   fingerprint (including the same args retargeted at a different lifecycle) is a loud
   `conflict`, never accepted or effected.
3. The **canonicalizer**, the narrowly scoped mediated writer for this endpoint's facts
   (§13.9); consumes the submission plane through a **normative durable `AckExplicit`
   consumer** and acks a submission ONLY after a durable decision fact exists, and, for a
   pool-admitted acceptance, ONLY after the §13.6 EPW enqueue create has additionally
   succeeded (or lost its CAS to an already-present entry): a crash anywhere between
   acceptance and enqueue therefore redelivers the submission, and the reconciliation
   predicate resolves the redelivered copy; recovery never has to DISCOVER orphaned
   acceptances, because an acceptance without its enqueue is by construction an unacked
   submission that comes back. A crash before
   the fact redelivers the submission; a crash after it observes the CAS winner on
   redelivery. It validates each submission (schema, body/subject agreement incl. the target
   block, authorization per §13.6, and (for work-pool commands) pool admission/capacity
   BEFORE acceptance) and then decides each request exactly once by publishing a
   **decision fact** to the caller-scoped subject
   `epf.<endpoint>.dec.<cOwner>.<cActor>.<cUid>.<id>` with create-only CAS (expected last
   sequence on the subject = 0), so distinct callers can never squat each other's ids. **For
   an action command the canonicalizer additionally binds the goal before accepting**: it
   create-only-CASes a **goal-bind fact** `epf.<endpoint>.goal.<cOwner>.<cActor>.<cUid>.<goalId>.bind`
   carrying the accepted fingerprint, and rejects (`conflict`) any later submission whose
   `goalId` matches but whose fingerprint differs, so two distinct `id`s naming one `goalId`
   cannot both be accepted-and-effected (the decision CAS keys on `id`, which alone would let
   both through; the goal-bind CAS keys on `goalId`, which stops the second BEFORE acceptance
   and effect, not at the terminal-result stage where the effect has already happened).
   The decision is `accepted` or `rejected` (with the catalog error); **rejection is as
   durable, caller-readable, and idempotent as acceptance**, so a permanently invalid
   submission is distinguishable from a lost one. First decision wins atomically; a later
   attempt fails its CAS and reads the existing fact. There is no append-then-memo pair to
   crash between. The canonicalizer is a **singleton per endpoint** (one active principal,
   epoch-fenced like any serve identity, recovered through the §13.1 takeover barrier):
   admission checks (pool capacity for work-pool commands) are thereby serialized with the
   decisions they gate, so two canonicalizers cannot both admit the last slot; capacity is
   consumed by the acceptance itself, never checked apart from it. A submission that cannot
   yield a decision key; bytes that are not canonicalizable I-JSON (unparseable, duplicate
   object names, lone surrogate, out-of-range number), or no `id` within the token
   grammar; **or bytes that breach the command's declared `admissionCeiling`** (§13.7): raw
   size over `maxBytes`, nesting over `maxDepth`, or member count over `maxItems`; is
   **quarantined, never redelivered forever**: the canonicalizer publishes a
   **`QuarantineFact`** to the disjoint quarantine family
   `epf.<endpoint>.quar.<sourceSeq>` (§13.2); keyed by the source sequence, which exists
   for every stored copy by construction, in a family that shares no namespace with
   caller-chosen `dec` ids, so no legal request id can collide with a quarantine key, with
   create-only CAS, and terminally acks
   (`AckTerm`) the submission ONLY after that fact durably exists (or its CAS loss shows it
   already does), so a poison message cannot pin `MaxAckPending` and the
   fact-before-terminal-ack rule holds on the poison path exactly as on the decision path.
   `QuarantineFact` = `{ v: 1, decision: "quarantined", sourceSeq, submissionDigest (the
   `sha256:<hex>` digest of the raw stored bytes, §13.7), error: { code (catalog token),
   detail? (≤ 256 bytes) }, caller?: { id, lifecycleUid } (from the broker-authenticated
   submission subject, when it parses), ts }`, every field bounded or fixed-size, so the
   fact fits by construction; it never carries the poison bytes themselves.
4. Journal submissions set `replyExpected: false`; the caller **observes its decision** by
   watching/reading its own decision subtree (`epf.<endpoint>.dec.<its triple>.>`, a
   caller-scoped read grant minted with every journal capability). An action command's
   accept/reject is exactly its decision fact, expected within the submission deadline.
5. The **acceptance fact is self-sufficient for effect and replay** (`AcceptanceFact`, the
   `accepted` decision): `{ v: 1, id, decision: "accepted", fingerprint, request: <the
   canonical EndpointRequest, args INLINE, bounded by the broker's max_payload; a submission
   too large is refused loudly with resource-exhausted, never spilled into storage>, caller:
   {id, lifecycleUid}, target?: {owner, actor, lifecycleUid, mappingRevision},
   contractDigests: {input, output}, authzDecision: {revision, epoch},
   route: "effects" | `pool.<pool>` (the acceptance's SINGLE execution route, decided by
   the canonicalizer at admission: a pool-routed acceptance is executed by the pool's
   worker path (§13.5) and the effects consumers MUST ack it without effect; an
   effects-routed acceptance is executed by exactly one instance off the shared effects
   durable (§13.9). No acceptance is ever executed twice, because the fact names its route),
   readinessDeadlineMs?: <the acceptance-relative readiness bound, present iff the command
   declares bounded readiness, §13.6; persisted HERE because it is goal state, not the
   request's decision deadline>,
   workExpiry?: <absolute expiry of a pool-routed item, present iff `route` is a pool, §13.8;
   survives reconciliation re-enqueue unchanged>, sourceSeq, ts }`. A `target`-bearing
   acceptance (work bound to a lifecycle) publishes ONLY after its target-indexed
   obligation row exists AND only under an unexpired admission proof the mediator issued
   for that row (§13.8: proof issuance is the post-create currency recheck, so a row whose
   target or policy moved between create and recheck never admits; the fact's durable
   address is caller-scoped, so the obligation row, keyed target-first, is the ONLY
   target-enumerable record a retirement barrier can drain;
   `target.mappingRevision` is provenance, never a fence). The
   canonicalizer preflights the **serialized decision fact**, not merely the inline args,
   against `max_payload`: a submission whose acceptance fact would not fit is rejected
   `resource-exhausted`, and the rejection fact always fits by construction: every field
   is bounded or fixed-size (the operator floor assertion covers the maximum serialized
   rejection/quarantine fact, §13.12):
   `RejectionFact` = `{ v: 1, id, decision: "rejected", fingerprint, error: { code (catalog
   token), detail? (≤ 256 bytes) }, caller: {id, lifecycleUid}, authzDecision?: {revision,
   epoch}, sourceSeq,
   ts }`; the fingerprint and the catalog error, never the args (a parseable submission
   always yields the fingerprint; the unparseable/no-id case is the QuarantineFact above,
   which requires neither `id` nor `fingerprint`). Digest-pinned
   references inside a fact may name **only already-published public contract artifacts**,
   never per-request payloads: the contract store is public, immutable, and permanent,
   the opposite lifecycle of private, horizon-bounded request content (a large-payload
   facility, if ever needed, is its own future primitive with its own store, retention, and
   §13.9 rows). Effects and replay read the fact, never the raw submission (a TOCTOU re-read
   of the untrusted log is non-conformant).
6. Decision facts/tombstones are retained at least the declared **idempotency horizon**
   (default 24h, space-configurable) AND longer than the maximum submission-log retention
   plus recovery/redelivery lag; otherwise a rebuilt canonicalizer could re-accept an old
   submission still sitting in the log as new work. The horizon is **realized by decision
   retention, not by a clock**: the create-only CAS returns the recorded decision for exactly
   as long as the fact exists, and a reused id becomes new work only once retention has
   evicted the old fact and freed its subject; there is no separate time rule for the CAS
   to disagree with. The §13.12 retention floor states the horizon by OUTCOME: no removal
   cause may drop a decision fact or tombstone before it. The canonical subjects are the authority (D12) for anything
   auditable, metered, compensated, effected, or replayed. Ordering is per-subject;
   consumers never assume cross-subject order.

**Events are not facts.** Cluster events and per-goal progress (`epe`) are direct,
epoch-fenced, instance-published notifications on a durable, ordered, replayable stream;
that is the sense in which they ride the journal contract. They do NOT pass through the
canonicalizer, carry no acceptance semantics, and MUST NOT drive effects that require
canonical acceptance; anything auditable/metered/compensated goes through submissions and
facts.

### 13.5 Verbs

- **call**, bounded request/reply (`replyExpected: true`, `deadlineMs` mandatory). On the
  `one` rail it is queue-group anycast; on `inst` it addresses one stable instance. No
  responder → `unavailable`.
- **cast**, the same subjects and grants (`replyExpected: false`): fire-and-forget,
  at-most-once, the responder MUST NOT reply and the caller never reads the rail (the nonce
  is present but unused). A cast to a journaled command is `class-mismatch`; journaled work
  goes through submissions. In the NATS binding a publish violation is asynchronous — the
  publish call returns normally while the broker refuses — so a caller-side cast
  implementation MUST watch the connection status for a violation on the cast's own subject
  and raise `permission-denied` naming that subject, exactly as a call does; a cast
  implementation MUST NOT resolve as though a refused publish had been cast.
- **watch**; observe a record (KV watch; fell-behind ⇒ re-read, §13.4) or an event topic
  (live subscription within the read grant plus filtered replay from the event stream).
  Per-key and per-goal subjects carry read containment; a watch grant names the exact subtree.
- **claim**, competitive at-most-one-winner acquisition from a durable work pool (`epw`),
  **owner-mediated**: the pool's owning endpoint holds the pool's single `AckExplicit` pull
  consumer (§13.12); workers hold **no** JetStream grant on the pool and acquire, renew, and
  settle work exclusively through the owning endpoint's reserved **`lease`** and **`commit`**
  commands on the ordinary `ep` rails. This is the only shape that satisfies both claim
  invariants at once: the delivery's ack token never leaves the party allowed to use it, and
  the attempt binding is **owner-recorded at assignment** rather than asserted by the worker
  (a worker-carried "sequence + attempt" proves nothing about delivery; an owner assignment
  does). The stored pool message is **work identity and input only, never the authoritative
  lease**: broker redelivery re-delivers the same stored bytes, so a token in the payload
  cannot fence, and the consumer's `ack_wait` is the broker's redelivery-to-owner timer only,
  never the lease. `lease` (call): the owner fetches the next stored item and records the
  lease `{item, sourceSeq, attempt: the delivery count, worker: the broker-authenticated
  caller (principal + lifecycle UID, plus epoch for endpoint workers), fencingToken,
  leaseDeadline}` in its `lease` record (key grammar §13.7, writer table §13.9) by
  **first-wins idempotent CAS per (item, attempt)**, a duplicate or
  delayed `lease` call for a still-current attempt returns the SAME lease; an attempt is
  superseded once redelivery advances the delivery count; `fencingToken` is CAS-incremented
  per attempt and `leaseDeadline` comes from the owner's own clock. Expiry revokes the claim
  at that deadline even before reassignment. Every Cotal-owned commit from claimed work is
  submitted through the reserved **`commit` command** carrying the exact lease tuple; the
  handler validates token currency AND unexpired lease against its own clock AND that the
  caller is the lease's bound worker, then performs an **atomic, idempotent per-item CAS to
  a cached terminal result**, the per-item terminal fact
  `epf.<endpoint>.wrk.<pool>.<acceptance identity>` (§13.2), create-only CAS per item,
  under its mediated writer credential (§13.9): a committed item
  can never be leased again, a duplicate commit returns the cached terminal outcome, and a
  raced commit loses loudly. Only after observing the committed terminal state does the
  owner ack the WorkQueue message; it holds the delivery natively, so the deletion
  capability is never transferred, and no worker-side ack can destroy an item whose commit
  was rejected. A lost owner ack merely redelivers the item to the owner, which observes the
  committed terminal state and acks again: **settled work is never re-enqueued as new** (the
  durable bridge is the acceptance fact plus the per-item terminal CAS; an accepted item
  with no terminal result and no live pool entry is the only re-enqueueable state, §13.6). A
  stale token, expired lease, or superseded worker is `expired`/`conflict`; workers hold no
  bypass write.
- **scatter**, a request on the `all` rail. The caller freezes a **request-scoped expected
  set**, the live instances of the class from the service registry, each as
  `(instanceId, registrationRevision, epoch)`, where `registrationRevision` is the store
  revision of the instance's `svc….spec` record key (§13.7: it advances only on mediated
  registration writes, and the record read/watch grant that freezes it is a §13.9 matrix
  row), at send time. Gather accepts at most one
  terminal reply per expected `instanceId`, attributed from the reply subject **including its
  epoch** (§13.2): a second reply from the same `(instanceId, epoch)` is classified
  `duplicate` and **reported, never silently dropped** (first reply wins); a reply from a
  frozen `instanceId` at a different epoch, or an observed registration-revision advance;
  is classified `churn` (the instance restarted mid-scatter and may never have seen the
  request) and does not count toward completion; replies from outside the frozen set are
  classified `unexpected` and never count toward completion. Completion is
  all-expected-replied or deadline, in which case the result is explicitly partial with
  `missing` / `churn` / `unexpected` / `duplicate` / `late` classifications (a churned slot
  reports as `churn`, not `missing`). An empty or unreadable registry is
  `failed-precondition`, not an empty success. Deadline mandatory. A per-instance liveness
  probe that asks the broker about an instance's own rail is such a cast, and a refused
  probe publish MUST surface as that same `permission-denied` refusal naming the refused
  subject, never as a liveness verdict: only the broker's no-responders answer is a
  liveness fact, and a refusal licenses nothing (a caller that swallowed it into `unknown`
  would read a permission problem as "no verdict" and pay the full budget for it).

### 13.6 Composites

Patterns over the verbs and contracts; zero new transport.

The reference manager's spawn goal uses the submitted request's `id`. If the caller's
submission wait ends before acceptance is observed, the reference client retains that id
in its local error details and the spawn tool's unknown result. This is a readback
coordinate, not evidence of acceptance or permission to retry. Existing caller-scoped
goal reads resolve a later terminal without adding a command or transport kind.

**Action**, a long-running command. `action` is a command **marker**, never a class: an
action command's submissions are `class: journal` (§13.3).

1. The caller submits with a client-generated `goalId` and the request fingerprint (§13.4).
   Accept/reject is the durable decision fact (§13.4), expected within the submission's
   decision deadline; there is no reply-rail answer to recover.
   **Authorization linearizes at acceptance**: the acceptance fact persists the caller and
   target lifecycle tuples, command + contract digests, and the authorization decision
   revision/epoch it was made under. A scope narrowing before acceptance rejects the goal;
   after acceptance it blocks *new* goals but an accepted goal continues, unless the
   command's contract declares **continuous reauthorization**, in which case each declared
   checkpoint re-validates and deterministically transitions to `cancelling`/`failed`
   (`permission-denied`) on narrowing. Handle expiry/revocation mid-goal follows the same
   declared policy.
2. States: `accepted → running ⇄ waiting → succeeded | failed | cancelled | expired |
   uncertain`, with
   `cancelling` between a cancel and its terminal state. This is the **single status
   vocabulary** for every long-running surface. All five of `succeeded`, `failed`,
   `cancelled`, `expired`, and `uncertain` (item 6) are **terminal**, and first-terminal-fact-wins
   applies uniformly: `uncertain` is not an absence of an outcome, it is the outcome
   "this action's success signal did not arrive within its readiness deadline".
3. Progress rides per-goal events (`epe…goal.<caller triple>.<goalId>.progress`), read-scoped
   to the caller at mint time. The goal's current state is a status-only record projection;
   the journal owns the facts.
4. Cancel is the reserved `cancel` command: `graceful` (compensations, default) or
   `terminate`. Cancel of an unknown/terminal goal is `failed-precondition` with the cached
   outcome attached. Cancel races completion at the mediated commit point: first terminal
   fact wins; the loser observes it.
5. The terminal result is a journal fact and is cached. The full payload is retained at least
   the declared result retention (default 24h); a **terminal tombstone**
   `{goalId, fingerprint, state, outcomeDigest}` at least the idempotency horizon (≥ result
   retention; outcome-stated by the §13.12 retention floor). Same goalId + fingerprint returns the cached outcome (after payload eviction:
   the tombstone summary, `data.evicted: true`); same goalId + different fingerprint is
   `conflict`; beyond the horizon a reused goalId is explicitly new work.
6. **Bounded readiness (`uncertain`).** An action whose success signal may lawfully not
   arrive within its readiness bound declares a **readiness deadline**, a distinct,
   acceptance-relative bound persisted in the acceptance fact/goal state, NOT the
   submission's `deadlineMs` (which bounds only the decision, §13.3). Spawn readiness is
   the reference case: its readiness deadline is **30 s**, the migrated presence-or-exit
   backstop, D29; every legacy spawn-timeout consumer converges on this single bound. A
   spawn whose agent application waits for the operator before its session joins, as its
   connector declares for the launch's options (a session picker, for one), has its running
   process as the success signal: the owner records `succeeded` once that process has run
   briefly without exiting, and the result's `waitingFor` names the screen. A spawn
   whose connector declares that an idle seat is woken only after a confirmed startup, and
   whose startup watch ended `ready` (the input screen without the confirmation), succeeds
   with the result's `startupNotice` carrying the connector's clause; a reader that does not
   read it sees an ordinary success. When
   the deadline passes without the signal, the owner records the goal's terminal **result
   fact** (`goal….result`, §13.2) with the outcome
   `uncertain`, and the goal IS terminal: `uncertain` is a terminal outcome like
   `succeeded`/`failed`, immutable, first-terminal-fact-wins as for any goal (there is no
   call and no reply rail here: an action is a journal submission, and the result fact IS
   the caller-visible outcome, item 5). The underlying ENTITY's later convergence
   (ready/exited) is observable on that entity's own status record (`svc….status`, the
   lifecycle mapping); a caller that needs the eventual answer watches the entity, never
   the goal; the goal is not rewritten and its status does not linger non-terminal. An owner
   that observed the entity still running when the deadline passed MAY add a detail of kind
   `ai.cotal.goal.entity-running` to the `uncertain` result's `details`, and a caller then
   reports the action as still starting.
7. Goals bind the target's `(principal, lifecycleUid)` (§13.1): a goal accepted against a
   lifecycle is not redeemable, cancellable, or effectful against a same-name successor. A
   restarted instance (same `instanceId`/UID, advanced epoch) recovers its goals from journal
   + records; a superseded epoch cannot commit transitions.

**Awaitable checkpoint**; one durable pause primitive (approvals, guard holds, payment
authorization). A waiting action mints a checkpoint: a durable token persisted with the goal,
a `waiting` status carrying the checkpoint id and its **deadline generation**, and a durable
timer (§13.12). Deadlines are mandatory. Heartbeat/extension CAS-advances the generation in
status, then replaces the timer (a new `.schedule` request; the mediated timer writer's
same-subject `.armed` publish is the server rollup, §13.2/§13.12, the 2.14 atomic
stop-plus-publish is NOT assumed at the 2.12 floor). A firing timer carries
`(timerId, generation)`; the endpoint validates the generation against current status before
acting, stale fires **no-op**. Because status and timer are two resources with no atomic
bridge, a **durable reconciler** on the owning endpoint repairs the pair after crash or
leadership change WITHOUT any status↔schedule read the no-read timer plane cannot serve: the
reconciler **re-emits a `.schedule` request at the current generation for every `waiting`
status it owns**, and a same-`(timerId, generation)` arm is **idempotent at the timer writer**
(it re-derives the same `.armed` message; a duplicate is a no-op replacement), so
over-emission is harmless and a missing schedule is repaired without the reconciler ever
having to observe whether one exists. Stale-generation fires still no-op at the handler. Cancellation of a timer is cleanup, never the correctness boundary.
Timer retention MUST exceed the maximum deadline plus a recovery margin. Resume: a `resume`
command presenting the checkpoint token; resume authorization is **one-use** (journaled by
create-only CAS on the checkpoint token; duplicate resume is `conflict`) and holder-bound
(§13.10). Expiry fails the checkpoint closed.

A settlement MAY name the answer it accepted. The one-use settle fact carries an OPTIONAL
`answerId`, and the status carries the matching OPTIONAL `settledAnswerId`; both are id tokens,
both are permitted ONLY on a `resumed` settlement, and an implementation MUST reject either on an
expiry. Their key sets are closed: an endpoint that does not know these keys MUST hard-error on a
fact that carries one. The answer's payload MUST NOT ride either field.

**Guard checkpoint**, the pre-effect authorization hook. A command carrying the governed
`ai.cotal.guarded` trait MUST NOT effect until the guard endpoint named by the trait value
answered **allow** (class call). Answers: `allow | deny | hold` plus optional signed
obligations (attenuations the endpoint MUST apply; monotonic). `hold` converts the action to
`waiting` on a checkpoint owned by the guard decision. Timeout or unreachable guard is
**deny** (fail closed). Ordering is guard-then-effect. Side-effecting guards own their own
reconciliation.

**Capability handle**, the one passable reference type: a signed JSON grant, RFC 8785
canonical, Ed25519-signed by a key in the trust-anchor registry (§13.10):

`{ v: 1, id, space, issuer: { keyId }, holder: { id, lifecycleUid }, grants: [{ endpoint,
instanceId?, commands: [{ name, authz?, targetOwner?, targetActor?, targetLifecycleUid? }],
reads?: [<record-key or event-topic subtree>] }], iat, nbf?, exp, parentDigest?, sturdy,
epoch?, sig }`

A grant entry carries **every subject-level dimension** a capability has (§13.9): a targeted
command names its authorization mode and target components; read scopes name exact
record-key / event-topic subtrees. The per-command target tuple is a **closed set of three
legal shapes**, no target components; `targetOwner` alone; or the full triple
`{targetOwner, targetActor, targetLifecycleUid}`, and **every other combination is
schema-invalid** (`contract-invalid`): in particular `targetActor` without
`targetLifecycleUid` (a handle that pins a recyclable alias component MUST pin the lifecycle
it means) and `targetLifecycleUid` without `targetActor` (a lifecycle restriction with no
compile target would otherwise be silently DROPPED into an owner-wide grant, a partial
tuple never weakens into a broader one). The normative compiler maps a grant entry to
exactly the subjects the equivalent minted capability would receive (never wider) it MUST
consume every present signed component (a component the compile target cannot express is
schema-invalid, never ignored), and every legal entry HAS a compile target:

- a **no-target** entry compiles to the untargeted or `self` form per the command's
  contract; an `authz` field on it is schema-invalid.
- an **owner-domain** entry (`targetOwner` alone) compiles to the mode its `authz` field
  names, `owner` (the default), `child`, or `ledger`, and NOTHING else: each pins the
  signed `targetOwner` in that mode's own subject form (§13.2), **never collapsing `child`
  or `ledger` to `owner`** (the modes are distinct validator-primary rails and rewriting
  one into another widens authority), and **`authz: "any"` is schema-invalid in a handle
  grant entry** (`contract-invalid`): the `any` rail is operator-ceiling authority, minted
  only as a standing capability under an operator-scoped anchor (§13.10), never conferred
  or attenuated through a handle; a compiler therefore has no `any` case, and no
  implementation choice exists between rejecting, literalizing, or widening it.
- an **actor-pinned** entry (the full triple) compiles to the `handle`-mode form pinning the
  full signed triple `<targetOwner>.<targetActor>.<targetLifecycleUid>` (§13.2); an `authz`
  field on it is schema-invalid (the triple IS the mode).
- an **instance** entry compiles to
  the exact `ep.inst` rails; complete, because `(endpoint, instanceId)` is the whole instance
  address and instance ids are never reused (§13.1).

A capability that cannot be represented in this shape MUST
NOT be carried by a handle.

- **Two uses, both fail-closed.** *Attenuation:* presented in the `auth` slot, a handle only
  narrows; the handler enforces `effective = presenter-cred ∩ handle.grants ∩
  issuer-authority`, and additionally requires any signed target triple to match the
  request's target and the current mapping (`expired` on mismatch); it never confers broker
  reach. *Conferral:* a handle grants reach only by **redemption through the trusted auth
  path** (the exchange/callout of §9/§10), which verifies the signed target triple against
  the current mapping **at redemption time** (`expired` on mismatch) and mints a short-lived
  credential whose grants are the intersection of issuer authority, handle grants, and the
  redeeming holder's current lifecycle + credential; actor-pinned grants compile to
  `handle`-mode subjects carrying the verified triple (§13.2), so a target lifecycle that
  rotates after mint is caught by the endpoint's currency check; no handler-side widening
  exists. The minted credential is **ledgered before release** in the credential ledger
  (§13.1), keyed under the redeeming holder's lifecycle with the FULL presented handle
  chain as its `sourceChain` (plus the per-ancestor `bysrc.` index keys), so
  takeover/retirement barriers revoke it with the family and revoking ANY handle in its
  lineage (parent or leaf) cascades to it. Chain verification itself checks the
  revocation status of EVERY sturdy link in the chain, not only the presented leaf,
  failing closed on any revoked ancestor.
- **Holder-bound:** `holder` names the one `(principal, lifecycleUid)` that may present or
  redeem it; bearer transfer exists only as an explicit issuer-signed re-issue. `space` binds
  it to one space. A recycled alias cannot present its predecessor's handles (UID mismatch).
- **Attenuation chain:** `parentDigest` references the parent handle; a child MUST be ⊆ its
  parent under the **normative containment order**, per grant entry: endpoint within the
  parent's endpoint/domain pattern; `instanceId` equal or newly pinned (never widened to
  absent); commands a name-subset with per-command mode never higher in `self < owner < any`
  (`child`/`ledger`/`handle` are grantable only where the parent names the same mode); target
  components equal or newly pinned; read subtrees subject-prefix-contained, and per
  envelope: same `space`, validity window within the parent's, `sturdy` only if the parent is
  sturdy. The issuer of a child is the parent's holder, anchor-registered with a `handles`
  role whose scope covers the child (§13.10); the same containment order defines issuer-scope
  coverage. Presentation carries the full chain inline (`parentDigest`-linked artifacts
  presented together, no ambient fetch); verification walks every link to a registered
  anchor, failing closed on widening, unknown/revoked keys, or expiry.
- **Sturdy vs live:** live handles (`sturdy: false`) bind the current process `epoch`, are
  never persisted, `exp ≤ 24h`, and die on restart. Sturdy handles bind the lifecycle UID
  (surviving supervised restart), persist as issuer-namespaced `handle.<issuerKeyId>.<id>`
  records (spec create-only; status = revocation state, monotonic; §13.9 writer table), and
  verifiers MUST check revocation (fail closed if unreadable). Max sturdy TTL is
  space-configured (default 30d).
- Handles are reusable within TTL unless a composite declares one-use (checkpoint resume);
  the replay matrix of §13.10 governs every signed artifact.

**Session (bidirectional stream)**, the generic composite for interactive byte/frame
streams (terminal attach is its first consumer; nothing terminal-specific is normative). It
is exactly D26's cast-ingress + watch-egress composed over dedicated per-session subjects,
no new verb and no new transport: the `in` subject is a cast-only rail (caller publishes,
endpoint subscribes) and the `out` subject is a watch rail (endpoint publishes, caller
subscribes). A session is established by an ordinary command whose answer is a **session
grant**: a one-use,
holder-bound handle (live: bound to the caller's lifecycle AND current process epoch,
live authority dies on restart, §13.1, so redemption fresh-checks the holder epoch and an
unredeemed grant does not survive the caller's restart, plus the serving instance epoch) naming a fresh
unguessable `sessionId` and the epoch-pinned session subjects
`eps.<endpoint>.<sessionId>.<epoch>.in` (caller → endpoint) and `….out` (endpoint → caller).
Session subjects are **core-only**, never stream-captured; the bounded flow window lives in
memory and a dropped frame is the composite's problem, not retention's. Redemption mints
exact asymmetric per-session credentials: the caller publishes `in` and subscribes `out`;
the serving instance the reverse; no third party holds either, and no standing wildcard EPS
grant exists. Frames are opaque; flow control is bounded (window declared in the grant;
overflow is `resource-exhausted`, never unbounded buffering). Close is explicit, and
revocation has a **durable** named authority that survives the
serving endpoint: the trusted auth path (the exchange/callout of §9/§10) persists a **session
ledger row** at redemption, key `session.<sessionId>` in the auth store (§13.12), value
`{sessionId, endpoint, serving instance + epoch, holder (principal + lifecycleUid), both
minted credential ids, per-credential revocation marks, state, exp}` (the endpoint is in the
row because an `instanceId` is unique only within its endpoint, so every serving-party
operation authenticates against the full serving identity the row pins), create-only CAS per
`sessionId` (this CAS IS the one-use
redemption), state monotonic
(`active → closed | expired | superseded | retired`, all terminal), and each per-session
credential is simultaneously a credential-ledger row under its holder's lifecycle (§13.1),
which is the index the §13.1 barriers enumerate, and a barrier that revokes a
session-sourced credential MUST resolve its `session.<sessionId>` row, transition it
terminal, and revoke BOTH per-session credentials, so either side's takeover or retirement
tears down the whole pair, not its own half. Redemption's writes are ordered by a **finalize CAS**, so no half-issued session is ever
usable: the create-CAS writes the session row in state `issuing` (this create IS the
one-use), then both per-session credential rows are written gate-checked (§13.1), then the
redemption **CAS-finalizes the session row `issuing → active`**, fresh-checking BOTH the
holder and serving process epochs and both lifecycle gates at that CAS, and releases the two
credentials only on finalize success. A credential is authority ONLY once its session row is
`active`; an `issuing` row confers nothing. Close/expiry/either barrier CAS the row to a
terminal state (`closed`/`expired`/`superseded`/`retired`) and revoke both credential ids by
name (the ids are known from the row, whether or not both credentials were released) so a
crash mid-issue leaves an `issuing` row that the expiry sweep collects (revoking both ids and
tombstoning), never a live half-pair, and a redemption racing a close loses its finalize CAS
and releases nothing. A revocation mark is set only by a revoke that SUCCEEDED; a terminal
row with an unmarked credential is retried by every later sweep pass, exactly the unconfirmed
ids, until both marks confirm, so a transient revocation failure can never quietly leave half
a pair alive. The auth path revokes BOTH per-session
credentials with eviction (bounded
propagation) on any of: an **authenticated close input** on the trusted auth path itself,
a defined operation of the SAME exchange/callout surface that redemption already uses
(§9/§10, off-broker, so no broker grant row applies): the caller authenticates as one of
the session's two parties (its lifecycle or per-session credential) or as the operator and
names the `sessionId`; the auth path verifies party membership against the ledger row
before transitioning it. The in-band close frame
is an advisory peer signal, never the revocation authority, because EPS subjects are
core-only and captured by nothing; expiry per the handle rules (`exp` is enforced by the
auth path's own timer, not by the endpoint), or the serving
epoch's supersession / lifecycle retirement via the §13.1 barriers (either side's lifecycle:
holder and serving rows both index the family). Neither side can keep a
half-closed session alive, and a crashed serving endpoint cannot orphan one, the ledger, not
the endpoint, remembers what to revoke. Ledger rows are retained at least the maximum
session `exp` plus a recovery margin. The session dies with the serving instance's epoch
(the epoch is in the subject, so a restarted instance cannot resume it; a durable session is
a new establishment). Routing is authenticated broker routing end to end; there is no loopback URL
or out-of-band transport in the contract, and cross-machine reachability is exactly broker
reachability.

**Remote manager service registration.** A remote registered user becomes a manager only through
one host-operated, typed `prepare → activate → renew` exchange. It is not a generic credential
mint surface and is available only on the loopback/operator face to a signed-in human holding the
closed `manager-service` view (§13.1); the public exchange and managed-agent secret exchange
refuse every stage. All inputs and stored stage records are closed schemas. An operation is keyed
by `{ owner, managerActor, lifecycleUid, instanceId, operationId }`, where `managerActor` and the
lifecycle UID come from the server-authorized view, `instanceId` is opaque and collision-resistant,
and `operationId` is caller-generated for replay convergence. A repeated operation with the same
fingerprint returns its recorded result; a different fingerprint at the same coordinate is
`conflict`; a retired lifecycle or instance is never revived.

`prepare` fresh-checks the ledger scope and lifecycle, freezes only
`epgate.manager.<instanceId>`, and durably stages the exact service registration, contract closure,
status coordinate, requested public nkey, and credential lifetime. It releases no usable material.
`activate` re-checks that same live ledger row and gate, creates the exact `svc.manager.<instanceId>`
registration, publishes only the staged immutable contract artifacts, and asks the host to sign
NATS JWT material for that public nkey. The host writes the matching
`epcred.manager.<instanceId>.<credentialId>` row before the gate-finalize CAS and returns the JWT
only after that CAS; it never returns a signing seed. `renew` is the only way to obtain successor
public-nkey JWT material. It re-checks the live `supervise` grant, owner, actor, lifecycle,
instance, current gate, and bounded renewal window, then records and releases a replacement under
the same family. It cannot create another instance or broaden a staged contract, record, endpoint,
or credential grant. A crashed operation resumes only from its durable stage and operation id.

Renewal is bounded and denial is fail-closed. When a manager cannot renew because its login,
ledger scope, or host service is unavailable, it reports degraded state, retains already-live
agents only while their independently valid authority remains usable, and refuses new starts,
restarts, replacement credentials, and unsafe recovery. It MUST NOT turn a transient failure into
static authority or kill a live agent merely to make the state look healthy. When the current
family expires or is revoked, the host's verified revocation path closes it; a later restart still
requires a new successful prepare/activate operation. Same-owner descendant provisioning is
host-validated at every request, and loss of that validation also refuses a new start or restart.

**Platform control registration.** A platform-run control manager registers, activates, renews,
and retires through the same typed `prepare → activate → renew` protocol and operations as the
remote manager service above: `session`, `retire`, `transferReader`, `renewStandingBundle`, `renewRunDriver`, the
host-owned maintenance operations, retained-agent validation, the goal-index scan, admin
authorization, and run admission and attempt. Each request rides inside one closed
`platform-control-authority` envelope that adds only the space, the assigned account, and the
assignment revision. The envelope and every inner request are closed schemas. The envelope carries
no profile name, permission set, subject, lifetime, claim, or identity assertion. An inner request
keeps its existing operation-specific coordinates, such as the `session` operation's requested
`exp`, which the host caps at its own bound, and the run admission's served `run.subject`, which
the host re-parses and checks against the caller's issued ceiling. The host validates each one as it
does under `manager-service`. No field of either selects a signing subject, permission set,
profile, claim, or a credential lifetime past the host's bound, and an unknown kind or field MUST
be refused as `bad-request` with no effect. The managed-agent enrollment, retirement-preparation, and
runtime kinds are not carried; a host without its own storage for them MUST refuse them as
`unimplemented`. The door is a typed in-process operation of the host's authority context. It
exists only where the host supplies its assignment source, and no listener serves it. It needs no
exchange capability: the host MUST NOT hand its loopback capability or account signer to a control
manager, and a host that runs the control manager in another process carries the closed envelope
over its own caller-bound channel. A request carrying a human IdP token MUST be refused. It is not a
generic credential-mint surface and adds no daemon, listener, or protocol.

Host-owned maintenance is narrower under this view than under `manager-service`. Under
`platform-control`, a maintenance request's `targetInstanceId` MUST equal the assigned instance id
for both `evict-family-principal` and `reconcile-registration`, and the host MUST observe that the
target gate's principal is the platform owner plus that instance's fixed serve actor. Both checks
run after the assignment check and before any liveness probe, revocation, eviction, or reopen. A
failure is `permission-denied` with no effect. A foreign slot holder therefore blocks platform
registration with the existing registration refusal, and it is repaired only through its own
owner's `manager-service` maintenance or the host operator's guarded gate repair, neither of which
this view reaches. `manager-service` maintenance, including its reconciliation of a foreign slot
holder, is unchanged.

Renewal and fencing are the remote manager service's, unchanged. `renewStandingBundle` returns the
closed five-credential family for the same caller-held nkeys and the assigned account only when the
current gate is open, its principal is the platform owner plus the fixed serve actor, its process
epoch equals the request's, and the host-keyed current registration proof matches. A stale epoch
is `conflict`. A revoked or advanced assignment refuses the next request, renewal included, so
revocation never waits on a person and renewal never authenticates one. The remote manager
service's degraded-state and fail-closed renewal rules apply with the assignment in place of the
login and ledger scope. A start after revocation or expiry requires a fresh `prepare` and
`activate` under a current assignment.

**Environment manager bootstrap and registration.** A hosting authority MAY serve this
family as an ordinary registered, instance-addressed endpoint. `bootstrap` declares the
ephemeral class only. Its responder realizes §13.4's at-most-once effect through the
authenticated reservation and canonical fingerprint; this adds no idempotency-basis field
to §13.7's contract. It is not a journal action. Wrong-class invocation MUST refuse before
effect. Its bootstrap principal has the fixed `environment-bootstrap` grant of §13.9,
not manager supervisor or executor authority. The host admits first, waits for observed
provider RUNNING, then mints and delivers the file through a native post-boot secret/file
channel. It caps lifetime at the lesser of five minutes and remaining admission lifetime;
no channel or insufficient remaining lifetime refuses delivery rather than using an
expired create-time file. No provider create or guest boot acknowledgement is registration.

The bootstrap file carries version one, the environment record ID, preselected manager
instance and lifecycle, credential and private connection nkey, finite expiry, pinned WSS
broker hostname, authority endpoint and instance, and the two bootstrap commands' contract
digests. It carries no owner session, account signing seed, profile selector or grants.
The verified immutable image's system trust store anchors TLS; deployments using a private
CA MUST separately supply explicit digest-bound mesh trust. Certificate and hostname
verification MUST remain enabled. Bootstrap admits no trust-on-first-use path. The host
MUST NOT retain the private bootstrap seed after writing the private file; its durable
record holds only digest, recipient lifecycle, expiry and consumed state. The guest MUST
refuse any file version other than one before connecting.

The post-boot channel first delivers the private file, then starts or signals the stock
entry point. An entry started without the file refuses and exits; it never polls for one
or opens a plane without it. The manager takes and unlinks the manager-only file before
ordinary command handling or connecting a plane. It generates the five manager nkeys locally and atomically persists
them with the bootstrap recovery material in its private store before sending a request.
A crash after taking the file but before that first persistence is terminal: the external
host closes the environment and retires its reserved UID. It MUST NOT mint another
bootstrap for that admission.

`bootstrap` takes only `{ environmentId, identities }`, where `identities` has the five
public-nkey coordinates of the existing registration request. It returns the existing
`RemoteManagerAuthorityMaterial` for preparation. The broker-authenticated subject selects
the admitted bootstrap caller; payload fields cannot choose its owner, account, manager
instance, lifecycle, permission ceiling or private signing material. Before preparation,
a first-wins CAS MUST pin the canonical request fingerprint and the preselected manager
coordinates. A different fingerprint at that reservation refuses `conflict`. An unknown
issuance outcome stays held and MUST NOT start another preparation.

`bootstrap-status` takes only `{ environmentId }` and reads that caller's reservation.
During the bootstrap lifetime it returns the same recorded, public-nkey-bound preparation
result, or an explicit pending state. It issues nothing and selects no new keys or
lifecycle. Same-input lost-reply recovery uses this command, not a second prepare. A
completed registration or terminal admission refuses both commands; expiry does not delete
the reservation or permit reuse. Stored preparation material may contain signed JWTs under
the host-private credential-custody contract, never a guest private seed.

The subsequent `authority` command takes one closed `{ v: 1,
kind: "environment-manager-authority", environmentId, admissionRevision, request }`
envelope. `request` is one of the existing closed manager registration, standing renewal,
maintenance, managed-child enrollment, child retirement, retained-child validation,
transfer, goal-index read, serve-time admin-authorization decision, run-admission or
run-attempt requests, plus the closed `environment-manager-deregister` request below.
Per-session attach and shared MCP sessions are unsupported in this first family: their
entry points MUST refuse `unimplemented` before issuing, staging or redeeming any session
material. The authority MUST refuse `session` and `session-revoke` requests rather than
minting a JWT whose guest-side stage would need the forbidden auth-store write. The stock
manager does not construct the session plane for this family. Its attach/shared-MCP entry
refuses before `mintSessionServing`; the host's session refusal is a second fence.
There is no generic signing, mint, profile, permission, administrative control or
cross-environment operation. An unknown field or
kind refuses `bad-request` before any effect. Missing host policy refuses `unimplemented`;
an adapter MUST NOT substitute a human or platform holder.

Preparation, activation and restart use the existing registration barrier, performed by
the trusted host through this closed authority command. The guest MUST NOT execute
`registerRemoteManagerAuthority` over the shipped executor's direct store profile. This
requires an additive stock host-mediated registration path; a