# CLI authentication

Static minting requires local signing authority. Account sign-in and actor grants are separate, and diagnostic reports state which local claims they checked.


## auth mint

```bash
cotal auth mint <name> [--profile agent|observer|admin] [--out <private-file>] [--json|--plain]
cotal auth mint <name> --provision [--role <role>] [--space <space>] [--server <url>]
cotal auth mint <name> [--expires-in <seconds> | --expires-at <unix-seconds> | --no-expiry]
cotal auth mint <name> --identity <private-creds> --out <private-file>
cotal auth mint <name> --replace
cotal auth mint <name> --endpoint <class> --out <private-file> [--channels <a,b>] [--expires-in <seconds>]
```

The fixed-profile forms use the existing native static credential profiles. No generic host
profile is exposed.
A space registered as open refuses `UNSUPPORTED`, since it authenticates no one and issues no
credentials. Per-user-auth spaces refuse too; a cached login does not replace signing authority. The
resolved root supplies the persona ACL, signer and default storage together. A root that cannot be
resolved refuses with its next step, such as `cotal up`. Conflicting current-folder and
selected-root accounts refuse before issuance.

A label takes letters, digits and `_`, since `-` separates the parts of a principal's name. `--role`
takes letters, digits, `_` and `-`. A label or role with any other character, or an empty one,
refuses `USAGE_INVALID` without the command's help. The refusal names the character, the value and
the characters allowed, and its Next names the value with each such character replaced by `_`.

`agent` is the default profile. Its channel ACLs and role come from the selected root's
`<name>.md` persona file, when present. It does not fall back to `default.md` for a different name.
Explicit `--allow-subscribe`, `--allow-publish` and `--role` override the file's grants. A persona
file of that name that cannot be read as a persona refuses the mint `SCHEMA_INVALID`, exit 1, even
when the flags name every grant. The mint refusal does not print file values or parser text.
It names the file and `cotal persona show <name>` to inspect it. A role from the file that is not one word
of letters, digits, `_` and `-` refuses the same way, naming `cotal persona edit <name>`. With
`--identity` naming an agent credential this space issued, the new credential keeps that
credential's read channels, post channels and role in place of the persona's, and each flag still
replaces its own grant. The receipt lists the grants as May read, May post and Role, marking those
carried from `--identity`; JSON has them under `result.grants`, with `fromIdentity` naming the
carried ones. An empty ACL flag means no explicit entries. The native read-ACL resolver still enforces boot membership.
Without a matching persona, `--identity` or grant flags, the agent credential has no channel grants
or role. Its validity does not establish that it can perform a particular task.
Observer and admin have fixed native scope and reject agent ACL, role and provisioning flags
rather than implying those inputs narrowed anything.


### Private credential output

The default credential is created through its canonical secret-store kind. An existing label is
not overwritten. Use another label or an explicit private output. `--replace` replaces the label's
standing credential when it has expired, has no expiry claim or has a key that
`auth revoke-credential` revoked. It mints under a new identity unless `--identity` names one, which
a revoked key never is, and removes a no-expiry record beside it unless `--no-expiry` writes a new
one. The receipt names what it replaced and its public key under `result.replaced`. A replacement
does not revoke the credential it replaced, so a copy of one with no expiry claim stays valid. The
receipt then warns `REPLACED_CREDENTIAL_VALID`, naming that key and the
[`auth revoke-credential`](#auth-revoke-credential) route that stops it. A standing credential that is still
valid refuses `CONFLICT`, naming its expiry, and so does one that is not a native credential.
`--profile` still defaults to `agent`, so a replacement of an observer or admin credential needs
`--profile observer` or `--profile admin`; a replacement with another profile than the one it
replaced warns `REPLACED_PROFILE_CHANGED`. A replacement holds only the grants its persona file,
`--identity` or grant flags name, not those of the agent credential it replaced. When both are
agent-profile credentials, the old one names this space's account as issuer, and their grants
differ, the receipt warns `REPLACED_GRANTS_CHANGED`, naming both and the
`--allow-subscribe`, `--allow-publish` and
`--role` flags that name the replaced ones. With no standing credential under the label, `--replace`
mints a new one and warns `NOTHING_REPLACED`. Exported files stay unchanged; the warning names
`doctor auth --space <space>` to inspect their state and the guidance for each file, and the
space's root to run it in, since `doctor auth` reads the root it runs in.
`--replace` takes no `--out`. An exported file stays outside
managed `.cotal` and workstation state. Its path, label, profile and public key, without the
credential, are recorded under `credentials/` in the Cotal home so `doctor auth` lists the file. A pre-existing explicit output must be an owned private
native credential matching the nkey deliberately supplied through `--identity`; unrelated files
and identities refuse. New outputs use create-only publication. The output's directories follow
the [`space create`](/api-reference/cli-selection/#selecting-a-space) path rule, which also accepts a root-owned sticky directory
such as `/tmp`, and the file is owner-only. Unsupported platforms refuse rather than weakening it.
A credential is never written through a symbolic link: a link among the output's folders refuses
`PERMISSION_DENIED`, naming the link, the folder it points to and the same file named without the
link. A path through a file refuses `USAGE_INVALID`, and a folder this account cannot create
refuses `PERMISSION_DENIED`, naming the folder. None of them writes anything.

`--identity` reuses the native nkey. The out-of-band agent mint reports its newly allocated
lifecycle UID, with a warning that it did not reuse the old inbox/delivery footprint. Use that
reported UID for the resulting client; do not infer it from the label or assume the old UID was
preserved. No private nkey or JWT appears in a receipt. A key revoked with `auth revoke-credential`
refuses `CONFLICT`, because a credential signed now would postdate the revocation and the broker
would accept the key again; mint a fresh key without `--identity`.


### Provisioning

Without `--provision`, fixed-profile minting writes credentials only. `--provision` uses one native provisioner
connection to pre-create that lifecycle's bind-only DM/delivery and role footprint. It performs
the authenticated preflight against the same recorded broker and TLS requirement, without
pruning a registration. An acknowledgement proves neither a joined agent nor task completion.
A partial or unknown result retains the allocated public identity and lifecycle for diagnosis
and does not retry the mint.

A fixed-profile credential expires 24 hours after it is minted unless `--expires-in` or
`--expires-at` sets another time, in native seconds. `--no-expiry` issues one with no expiry claim and
records that request beside the file, in `<file>.no-expiry.json`, so `doctor auth` reports it as a
warning rather than a finding. The receipt reports the resulting JWT expiry. The three flags are
mutually exclusive, and an `--expires-at` that is not in the future refuses before anything is written. So does an expiry
after the last date Cotal can represent, in the year 275760. JSON is
one finite document; plain output uses the same public receipt. Signing material is exported by
[auth signer](/api-reference/cli-endpoints/#account-signer-export), not a signer flag on mint. The old top-level `mint` spelling
is refused with the literal namespace replacement.

A minted credential stays valid until it expires, and one minted with `--no-expiry` until it is
revoked or the space identity rotates. [auth revoke-credential](#auth-revoke-credential) stops one
credential and keeps the store. Rotating the space identity stops every credential the space issued:
`cotal down`, then `cotal reset space <space>` (or `cotal clean all --force` from the root when
`down` left no registration), then `cotal up`. That reset also removes the space's store, with its
workflow runs and their journals, channel history and stored messages, so the space starts again
with no history. `cotal reset space <space> --dry-run` lists what the store holds first. It also stops every
earlier full backup of the space from restoring into this root: only its registry restores, and
chat and DM history start empty (see [Backups](/api-reference/cli-backups/#backups)).


## Endpoint caller credentials

`--endpoint` is a separate closed mode, not an ACL override on an observer or admin profile. It
requires a private `--out` in an existing protected physical parent outside managed state. Only
`--channels`, `--expires-in`, space/server and presentation flags combine with it. It has no default
lifetime: without `--expires-in` the caller carries no expiry claim. An empty or
omitted channel list grants no explicit read/post channels. Supplied channels deliberately enter
both ACLs, as in the native standalone Linear caller form. No persona ACL or role is inferred.

The same static-root/account checks run first. The existing native control instrument then resolves
a live endpoint and digest-verifies its visible command surface. This issuer accepts only nonempty,
bounded untargeted synchronous ephemeral surfaces. A targeted, journaled or action command refuses
the whole issuance before footprint work; unsupported rows are not silently dropped. The result
records the observed responder and contract digests, not a promise that every future incarnation
has the same implementation. The caller is scoped to the class's observed command names, not that
single answering instance.

The native agent provisioner creates the new lifecycle's bind-only footprint and additional endpoint
request rows. No spawn, run or admin capability is added. The ordinary native agent baseline remains;
this is not a new hand-written permission profile or a managed-seat launch adapter. User-auth/open
spaces still refuse. The selected broker's required TLS is retained for discovery and provisioning.


### Issuance records

An immutable owner-private `<out>.issuance.json` intent precedes the first footprint operation.
It contains public identity/lifecycle, requested scope and contract coordinates, never credentials.
Its presence is not a grant or successful submission receipt. An existing output or retained intent
blocks another issuance, even if no output was published. Partial/unknown provisioning retains that
original intent and does not replay the native call. Success reports native footprint acknowledgement
and the exact privately published file; provisioning is not presence, task completion or cleanup.
Temporary provisioner close is reported separately and can leave a partial result.

Rich/plain receipts show the actual caller/lifecycle, described endpoint owner and responder,
explicit channel scope and native expiry. Command tables show at most 40 rows with an omission
count; JSON retains every command and contract digest. The local issuance request is not a broker
request acknowledgement.

No credential bytes or environment-export recipe are printed. Endpoint describe/invoke consume
this paired file under the same registered static root:

```bash
cotal ep describe ep:<class> --space <space> --creds /absolute/private/caller.creds
cotal ep invoke ep:<class> <command> --space <space> --creds /absolute/private/caller.creds --args '<json>'
```

Keep the matching `<private-file>.issuance.json` beside the credential. The consumer checks both
protected snapshots and validates the selected root's full static trust chain. The native JWT
signature, matching user seed, current validity window, account and current delegated signing key
are checked before connecting. A retained older signing key is not guessed from a different root.
The caller coordinates and recorded commands must reproduce the signed publish/subscribe/response
permissions and principal tags through the native builder. The unsigned local intent selects that
comparison; it grants nothing and proves no submission, lifecycle currency or completion. Original
responder/contract metadata remains an issuance observation, not an instance or future-code pin.

The connection retains registered TLS and uses only that file. Its broker admission is separate
from local checks. Invocation accepts only an included untargeted synchronous ephemeral method
from the fresh digest-verified surface. A changed targeted, journaled or action shape refuses before
publication. Instance pins, scatter, admin, agent targeting and goal-follow flags are unsupported
for this caller mode. Calls do not repair a binding, switch authority or resubmit an unknown effect.
`--server`, when supplied, must repeat the registered broker. Target/reference selection is
read-only, including when a paired file is selected. Receipts retain the public caller binding;
describe shows methods outside the file scope without granting them. Issuance next steps keep
explicit file selection instead of switching back to the normal operator instrument.

Arbitrary raw files, issued-generation files and different native profiles are not interpreted as
this standalone caller. User-auth issuance and managed static launch are not supported.


## Account login

```bash
cotal login --idp <auth base URL> [--client-id <id>] [--plain]
cotal logout --idp <auth base URL> [--local]
```

Signs you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it
once per machine. `--plain` presents the approval prompt and signed-in result without color.
Device approval is still interactive and has no native noninteractive or JSON result in this
client; `--no-input` refuses before starting a provider request. The IdP URL must use `https://`.
Plain `http://` is accepted only on a loopback IP literal such as `127.0.0.1` or `::1`, for an IdP on this machine. `localhost` is refused because it
is a name: a hosts entry would choose the IdP. It prints your IdP subject, the id the operator
grants against. When the trusted
`/token` response advertises a same-origin space catalog, login validates and records that account's
spaces immediately, lists them under `Your spaces`, and names `cotal use <name>` to select one. A
space registered from elsewhere that has not granted your account refuses with those discovered
spaces as its remedy, because only that space's operator can grant you. On the root that holds the
space's grant ledger, the refusal names the operator's `cotal actor grant` command instead. After a
login, every command on that mesh works under your identity: each connect takes a fresh IdP
proof, exchanges it locally for a short-lived bearer, and is authorized against the actor
ledger at connect time. A provider that cannot be reached, or that stops answering, refuses with
`DEPENDENCY_UNAVAILABLE` (exit 2) and caches no session. `logout` revokes the IdP session, clears
its cache, and removes only that
account's discovered registry entries. See
[identity & auth](/docs/identity-and-auth/).


## actor

```bash
# an upsert of the WHOLE row: name all three ACL flags, or pass --full for the wide defaults below
cotal actor grant <actor> --sub <IdP subject> --scope a,b --allow-subscribe a,b --allow-publish a,b [--role <r>] [--label <l>] [--json|--plain]
cotal actor grant <actor> --sub <IdP subject> --full [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>] [--json|--plain]
cotal actor revoke <actor> (--sub <IdP subject> | --owner <u_…>) [--json|--plain]
cotal actor label <actor> (--sub <IdP subject> | --owner <u_…>) <label> [--json|--plain]
cotal actor list [--sub <IdP subject>] [--json|--plain]
```

| Flag | Default | Meaning |
|---|---|---|
| `--space <s>` | the resolved mesh | Space whose ledger to manage |
| `--sub <subject>` | none | The IdP subject (shown by `cotal login`) the actor belongs to. With `list`, only that subject's rows are listed |
| `--owner <u_…>` | none | The derived owner token (alternative to `--sub`) |
| `--full` | off | Fill each ACL flag left off with its wide default; without it, `grant` refuses unless all three are named |
| `--scope <a,b>` | `spawn,role:default` with `--full` | Capability scope (`''` = none; `spawn` = may run agents; `run` = may start workflow runs; `role:<r>` = may delegate role r; `admin` = cross-agent control; `supervise` = eligible for the closed remote manager-service view when the host enables it). Any other name is refused |
| `--allow-subscribe <a,b>` | `>` (all channels) with `--full` | Channel read ACL; the user's envelope, their agents can never read beyond it |
| `--allow-publish <a,b>` | `>` (all channels) with `--full` | Channel post ACL; also the envelope for their agents' posting |
| `--role <r>` | none | Role (scopes the task-queue consumer) |
| `--label <l>` | none | Display label for `actor list` (never the IdP subject) |
| `--parent <owner.actor>` | none | Audit link to the spawning principal. An operator grant is the authority, so the link does not narrow the row |
| `--json`, `-j`, `--plain` | off | Print one JSON document or line-oriented text, as `auth grants`, `auth grant` and `auth revoke` do |

The actor ledger is the single authorization source of a user-auth space: no row, no access.
The ledger stores no IdP subject. `actor list --sub <subject>` and `auth grants --sub <subject>`
derive the subject's owner as `grant --sub` does, with this root's owner secret and IdP pin, and list
that owner's rows, interactive and managed-agent alike. The report is titled
`Rows for <subject> (owner <u_…>)`, and JSON adds `subject` and `owner` to its result and names the
owner in its target. A subject with no rows lists none. An empty `--sub` refuses `USAGE_INVALID`.
`actor label <actor> <label>` sets only an interactive row's label. The label is outside the
authorization digest, so the row keeps its lifecycle, grant time, capabilities and channels, and a
bearer issued under it keeps working. The receipt names the earlier label, and JSON carries it as
`previousLabel`. An actor with no row refuses `TARGET_NOT_FOUND`, and a managed agent's row refuses
`UNSUPPORTED`. Neither writes anything.
`cotal help actor` lists `grant`, `revoke`, `label` and `list`, and shell completion offers them.
`actor list`, `grant` and `revoke` resolve their space and root as `auth grants`, `auth grant` and
`auth revoke` do, and refuse the same way. With no space to resolve they refuse with a next step,
such as `cotal up`, rather than assume one, and a root that does not hold the space's user-auth
ledger refuses `list` rather than report no rows. On a static or open space the refusal says that
actor grants apply only to a space started with `cotal up --user-auth`, and `grant` and `revoke`
refuse that way before asking for `--sub`, `--owner` or the ACL flags.
Otherwise a usage error refuses with exit 1 before any authority is read, and one refusal names every
missing input: `--sub` or `--owner`, and for `grant` the ACL flags left off without `--full`. An
input that is not valid refuses the same way, naming each field and why: the actor, `--owner`,
`--role`, `--parent`, a channel pattern, or a `--scope` name other than `admin`, `run`, `spawn`,
`supervise` and `role:<r>`. Nothing is written. A channel needs no creation, so a grant may name one
that has no message or registry entry yet.
`grant --full` is the **full** envelope (all channels; scope `spawn,role:default`, so it may spawn and may delegate the default role). A
`grant` that leaves off `--scope`, `--allow-subscribe` or `--allow-publish` without `--full` is
refused and writes nothing. A re-grant **replaces the whole row**, not the one field you name, so to add a capability spell
every field out: the new scope plus the row's current read set, post set, role and label
(`cotal actor list` and `cotal auth grants` show what a row holds: its capabilities, read and post
sets, role, label and parent). Under `--full`, a field left off does not stay as it
was: it reverts to the wide default in the table above. A re-grant retires the current interactive lifecycle through the running auth
service before it rotates the row, so copied bearers cannot cross an authorization update. The
receipt names the earlier grant as none, the same fields (titled `Actor grant stored unchanged`) or
replaced, and says when its lifecycle was retired; JSON carries `previous` and
`previousLifecycleRetired`. If that
retirement cannot be confirmed, the row is left unchanged and the command fails with the recovery
action. `revoke` uses the same retirement before deleting the row, which lets a later grant create a
real successor instead of colliding with a live predecessor. `supervise` is separate from `spawn` and `admin`: it only makes a signed-in
person eligible for the host-provided closed remote manager-service view; it does not grant
management of another owner or a general host profile. `revoke` denies the next exchange and
the next connect with no restart, and evicts the principal's live connections. It asks nothing and
takes no `--yes`, because a later grant gives the actor access again under a new lifecycle. It reports
`verified gone` only when the delivery daemon's eviction result names the principal and shows a
complete scan with no connection left; any other result prints `live-connection eviction INCOMPLETE`
with its counts, or with the reason the result was not accepted. Managed-agent rows
(written by the spawn path) live in a disjoint row space this command never touches. See
[identity & auth](/docs/identity-and-auth/).


## doctor

```bash
cotal doctor auth [--space <space>] [--fix] [--json|--plain]
```

This inspects the current root's local credential claims and recorded renewal state. It does not
prove current broker authorization, daemon adoption, connected agents or task progress. The shared
report keeps full credential paths and public issue/expiry metadata; JSON retains all diagnostic
rows without JWTs, seeds, fingerprints or raw parser/renewal error bodies. On a root that holds several
spaces, `--space` names one; it defaults to the sole space. Without `--space`, a root with several
spaces refuses with `TARGET_AMBIGUOUS` (exit 2) and names them. In a folder whose root holds no space,
the report is partial (exit 4) and its next step is to run it in a space's root, which `cotal space`
lists. Without `--space`, in the root of a space `cotal space create` prepared and no `cotal up`
started, the step is to start it with `cotal up`, then run the same command again. In the root of a
space registered as open, the report says that the space issues no
credentials, so there are none to check, and the result is a no-op (exit 0). With `--space` naming
a space whose signing trust this root does not hold, it refuses `AUTH_REQUIRED` (exit 2) and reads
no other root. Its next step names the folder that space is registered at, where the same command
runs, or else a space's root, which `cotal space` lists. For a space `cotal space create` prepared
and no `cotal up` started, the step is to start it with `cotal up` first, then run the same command
again. From another folder, that `cotal up` names the space's root with `--root`, quoted for a shell.


### Diagnosis limits

Plain diagnosis starts no repair or connection. A required legacy credential or user-auth directory
migration refuses instead of silently moving files or reading canonical absence as an empty scope.
A credential file that other accounts can open is a finding that names its mode, with `chmod 600`
on the file as its repair. That includes the space's own store credentials, and the other files are
still checked. Each file is also parsed the way `--creds` checks one, so a file the NATS
client cannot parse is a finding even when its claims are legible, such as one with Windows (CRLF)
line endings.
Once the root holds the space's trust, a missing `membership-observer` or `connection-evictor`
credential is a finding too, with `cotal down` then `cotal up --rotate-sys` as its repair, because
nothing else mints that pair again. A missing delivery or membership-rw credential is only noted,
since `cotal up` mints it.
A credential with no expiry claim is a finding whatever its kind, since a copy stays valid until it
is revoked or its signer rotates. `--fix` re-signs only the static daemon credentials, so an unbounded agent credential
stays a finding until its file is gone. The exception is one `auth mint --no-expiry` wrote, whose
record beside it still names the file's bytes: someone asked for it, so it is a
`CREDENTIAL_UNBOUNDED_BY_REQUEST` warning instead, and the run ends ok, exit 0. Both get guidance that
names the ways out. `cotal auth revoke-credential <file> --yes` between `cotal down` and `cotal up`
stops that credential and keeps the store. Replacing
a label's file with `cotal auth mint <label> --replace` once nothing uses it bounds it from then on,
and so does removing any other file and its record and minting a replacement without `--no-expiry`;
a copy made before stays valid until it is revoked. Rotating the space identity (`cotal down`,
`cotal reset space <space>`, then `cotal up`) retires every credential the space issued and removes
the file, and the reset removes the space's store with its history. An expired label's file is a finding too, and
its guidance names that same `--replace`, next to the managed renewal that re-signs an agent a
manager runs. An agent file whose key was revoked is a finding in state `revoked`, with
`result.items[].revokedAt`, however far off its expiry is. A revoked key is not signed again, so
for a label's file the guidance names `--replace`, and for any other agent file it names stopping
the agent that uses it. Each `--replace` this guidance names, for an unbounded, expired or revoked
file, carries the file's profile as `--profile observer` or `--profile admin`, and an agent file's
read and post channels and its role as `--allow-subscribe`, `--allow-publish` and `--role`, since
the replacement is a new key, which holds only the profile and grants its mint names. For an
observer or admin file it names no managed renewal, as no manager runs one.

Each file `auth mint --out` wrote for the space from this root and that is still there is listed
under Exported credential files, and in `result.exported`, with its state and expiry. These rows
are not findings. An expired one is an `EXPORTED_CREDENTIAL_EXPIRED` warning. One near expiry or
expired names `cotal auth mint <label> --identity <file> --out <file>`, which signs the same key
again with the same grants. One whose key was revoked, or that an earlier space identity issued,
names a mint with a new key instead, because the broker refuses that key and `auth mint --identity`
refuses a revoked one. For an agent file, that mint names the file's read and post channels and its
role with `--allow-subscribe`, `--allow-publish` and `--role`, since a new key holds only the grants
its mint names.
A run with no findings says so in its heading only when nothing else is printed. While an exported
file needs attention, or a credential is unbounded by request, the heading starts `No findings;` and
names each, such as `2 exported credential files need attention`. The exit code stays 0.
Missing local signing trust is not inferred to mean an open or healthy mesh. An unreadable renewal
record prevents a complete healthy verdict. Locally ordered older incarnation files are labeled as
file observations, never proof of a live successor or permission to delete or respawn an agent.


### Supported repair

`--fix` explicitly permits native local material migration and the supported static daemon re-sign
phase. Its path-move and re-sign acknowledgements are retained separately. It holds the native
renewal lease when using the broker and refuses another holder. Offline repair is labeled as
uncoordinated; an authenticated preflight failure is not called an offline target. User-auth roots
cannot receive a static-signer repair fallback. No system account is rotated and no agent is
respawned or silently given a new identity.

A delivery or membership-rw credential the NATS client cannot parse holds no key to re-sign, and
the manager cannot renew it either. When no broker answers, `--fix` mints it again under a new
identity, as `cotal up` mints a missing one, and its result row has `reminted: true`. While its
broker answers it stays a finding, and its guidance and row name `cotal down` first. A refusal
because the manager holds the renewal lease names the same step. Every result row that is not a
plain re-sign has a `reason`. When `cotal up` starts with such a membership-rw credential, the
delivery daemon runs without it, and `up` warns `DAEMON_CREDENTIAL_UNPARSABLE`, naming the same step.

A local re-sign does not erase a recorded broker refusal or claim that a daemon adopted it.
A recorded refusal names the space's manager, the renewal owner, as the next step. A pre-per-space
`.cotal/renewal.json` names no space, so it is reported as a leftover and never read as the space's
verdict.
Remaining findings, incomplete audit reads and partial/unknown repair outcomes keep classified
finite receipts. Inspect the original renewal owner and lifecycle rather than repeating a mutation
or creating replacement agents merely to obtain a different report.


## Account screens

Bare `login` chooses a known provider from cached account metadata or registered spaces. When
none is known, an interactive terminal asks for the provider auth URL. `--idp` selects it directly.
The device-approval link and code are written only to stderr. Session tokens are never printed.
The sign-in receipt separates account identity from each space's grants. This client's device
flow requires approval and refuses `--no-input` before requesting a device session.

Bare `logout` chooses among cached providers. A single cached provider is unambiguous; scripts
with multiple providers must select one with `--idp`. `logout --json` and `--plain` report the
same result. No cached session is a local no-op, not proof that all provider sessions are gone.
Revocation must return before local cleanup. If revocation is unconfirmed the login is retained
for retry, and the refusal names `--local`; a later cleanup failure is partial and keeps the
confirmed server outcome. `logout --local` clears this machine's cached login and that account's
discovered registry entries without contacting the provider, for a provider that is down or does
not confirm sign-out. The provider session stays valid until it expires or is revoked at the
provider, and the receipt says so.


`endpoints --json` and `endpoints --plain` show the authorized live presence snapshot, including
agents and infrastructure. This legacy live read is labeled as presence, not a saved-reference
inventory or proof that a service registration is active. Native contract resolution remains a
separate read. Each `--json` item carries the presence values: `reportedStatus`, `condition`,
`activity`, and the ISO 8601 times `statusSince`, `activitySince` and `activeAt`, each null when the
record has none. The report works out the ages it prints from those times.


## Authority

Bare `auth` reports only safe cached provider metadata. It does not verify a session online,
turn a sign-in into a grant, or print tokens. Its next steps name the `--space` you gave or the
space a bare command resolves; with no space they point to `cotal space`. `auth grants` reads the
selected root's authoritative user-auth actor ledger; a root without its owner secret and pinned IdP
refuses rather than reporting an empty authoritative list. A static or open space has no such ledger
on any machine, so there `auth grants`, `auth grant` and `auth revoke` refuse with `UNSUPPORTED` and
say that actor grants apply only to a space started with `cotal up --user-auth`. `auth grant` and
`auth revoke` refuse there before they ask for a subject or the ACL flags. A space that cannot be
resolved refuses with its next step, such as `cotal up` or `cotal space`.

`auth grant <actor>` and `auth revoke <actor>` use the existing local operator authority path.
Choose `--sub` or `--owner`, never both. Grant requires explicit scope/read/post lists unless
`--full` deliberately selects the native wide defaults. These usage errors refuse with exit 1 before
the space's authority is read, and one refusal names every missing input. Re-grant/revoke
retain predecessor lifecycle retirement and concurrent-row checks before replacing or deleting
authority. A retirement that did not run, because the auth service is not running, refused the
connection or answered that the retirement did not run, is refused (exit 2) with the actor row
unchanged and the recovery named, usually `cotal up`. Any other failed retirement request may have
started the retirement, so it is unknown (exit 3) and is not retried. Grant
rows in reports contain scope and ACL metadata, not token hashes or secret material.

Revocation receipts separate committed deny-new from the delivery daemon's live-eviction notice.
They do not claim all related processes stopped. An interrupted mutation is unknown and is not
retried. Neither grant management nor actor reads install connector packages. Grant management
does not issue credentials, export signers, retire lifecycles or act on capability handles.


## Internal commands

`cotal __complete <words…>` is the internal entry the shell-completion stubs call to emit candidates
for the current command line; you run it directly only to debug a completion, with
`COTAL_COMPLETE_DEBUG=1`. `cotal agent-bearer` is machine-facing
plumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their
spawn-time secret; you never run it directly either, so `cotal help` leaves it out. Its local arm uses `--dir` to discover the
capability-gated loopback service. A remotely enrolled, already-granted agent instead receives
`--exchange-url <https://base>` in its launch argv: that arm sends `{owner, actor, actorToken}` to the
pinned public exchange with no local capability, follows no redirects, and refuses every non-HTTPS
URL because the actor token is the credential in the request body. Because a seat execs it on every
bearer refresh, it skips the connector-seed boot gate entirely: it reads one 0600 token file,
exchanges it and prints the bearer without consulting or writing the operator-global seed store, so
a newer store generation cannot refuse a live seat's refresh. For the same reason the seat's command
names the CLI entry file the `cotal` bin link resolved to at spawn, not the link: an install that
later replaces the link, such as the get.cotal.ai launcher over an `npm i -g` link, leaves running
seats able to refresh. The command runs before the rest of the CLI loads and reads none of its
preferences, extensions or config folder, so a refresh does not pay the CLI's start-up time. A command line it
cannot parse goes on to the full CLI, which refuses it with the command's help. `--manager-call` asks for the
instance-bound `manager-caller` view; `--manager-instance <id>` selects an explicit live candidate,
and an owner with more than one live candidate is refused with their ids until it names one.
That mode still prints only the raw token and does not update `--health-file`. A spawn runs it once as the agent auth
preflight. When it fails there without printing a sentence of its own, the refusal names the cause: the 30 second
timeout, the signal that killed it, or its exit code. (`cotal start` is a removed tombstone: it
errors and points you to `cotal spawn --detach`.)

`cotal app-identity` is plumbing for a Claude app conversation, and you never run it directly
either. The Claude connector's local server runs `cotal app-identity provision --name <name>` when
a conversation connects. It picks the mesh as `cotal spawn` does (`--space` selects one) and prints
one JSON line. A new identity opens only after a person approves it at the mesh's authority, so
provision needs a signed-in mesh whose authority advertises connection approval v1, and the
conversation's native `--session`. It keeps the request and its secret in a private record under
`~/.cotal/app-identities`. Until the person decides, it answers
`{"ok":false,"pending":true,"requestId":"<id>","approval":{"id":"<id>","url":"<url>","expiresAt":"<time>"},"proposed":<selection>}`,
with no credential, secret or path. `proposed` is `null` for a request without a selection.
Provisioning again sends the same request: it answers the same approval while pending, and the
launch env and the reference that `retire` takes once approved. The grant is what the person
approved, which can differ from the request. A denial or an expiry drops the record, and the next
provision makes a new request. `--selection <json>` proposes channels, a role and capabilities for
that conversation. A mesh with static credentials, an open mesh, and a signed-in mesh whose
authority this machine holds have no approval page, so provision opens no identity on them.
The connector passes `--require-approval`. Every provision needs approval with or without it, and
an earlier CLI that opened identities without approval refuses the unknown flag before doing
anything. A saved request that was never issued needs the person's decision even when an earlier
version saved it.
`cotal app-identity retire --dir <dir>` still removes an identity an earlier version opened on a
static mesh: its broker footprint, then its files and directory. When the broker is not answering it
keeps them. Each provision also retires those directories whose owning process has exited. For a
signed-in identity, `retire` keeps the record until the mesh confirms retirement, and names the
mesh's phase while it is pending. A request still pending is dropped.
`cotal app-identity reap` retires the selected mesh's saved conversations whose server process no
longer exists. The connector runs it after each signed-in connection, so a connection never waits
for that cleanup. Like `agent-bearer`, it skips the connector-seed boot gate.


## auth revoke-credential

```bash
cotal auth revoke-credential <public-key | creds-file> [--space <space>] [--yes] [--json|--plain]
```

It stops one credential that `auth mint` wrote and keeps the space's store and every other
credential. Name the credential by its user public key, which the mint receipt reports as its
identity, or by its creds file, which must be private to you and issued by this space's account. A
word that is not a user public key and names no file refuses `TARGET_NOT_FOUND` and says both. A
file from another account refuses `CONFLICT`. A file the space's system account issued, such as the
stack's `connection-evictor` and `membership-observer` credentials, refuses `UNSUPPORTED`. Its Next
is `cotal down --yes`, then `cotal up --rotate-sys`, which issues a new system account and retires
the old one's credentials, and after which earlier full backups no longer restore (see [up](/api-reference/cli-startup/#up)). The command records the key in the space's account,
re-signed by the space's operator, and the broker refuses that key from its next start.

The broker loads a revocation only when it starts, so the space must be stopped: run
`cotal down --yes`, then this command, then `cotal up`. On a running space it refuses
`LIFECYCLE_BLOCKED` and names that route, and it refuses the same way while the root records a
maintenance attempt. A connection held with the credential ends at the `down` and is refused after
the `up`. It asks before it revokes, or takes `--yes`; `--json` without `--yes` refuses. A key
already revoked reports the time it was first revoked and changes nothing. `--json` reports
`publicKey`, `revokedAt` in Unix seconds, `alreadyRevoked` and `takesEffect`.

`auth mint --identity` refuses a revoked key. A credential the stack renews itself, such as its
daemons' or a managed agent's, is signed again for the same key at renewal, and the broker accepts
that newer credential, so stop or despawn that agent instead. A per-user-auth space refuses
`UNSUPPORTED` and names `auth revoke <actor>`, and an open space issues no credentials to revoke.


### Credential profiles

See the [current command contract](/api-reference/cli-auth/#auth-mint) for this behavior.

### Paired caller validation

See the [current command contract](/api-reference/cli-auth/#endpoint-caller-credentials) for this behavior.

### Whole-row grants

See the [current command contract](/api-reference/cli-auth/#actor) for this behavior.

### Lifecycle retirement

See the [current command contract](/api-reference/cli-auth/#actor) for this behavior.
