Identity and actors
Refusals about who is acting, the keys that prove it, and the ceremonies that mint them.
33 classes. 9 explained, 24 named only.
Decisions in this product are signed. That means most refusals in this family are about one of four things: a name that is not the enrolled name, a key that is absent or already there, a ceremony that will not run twice, or an owner act with nobody at the terminal to confirm it.
⚠ Almost every refusal here is deliberately not self-correcting. The product will not guess which identity you meant, because the guess would be right almost every time — and being right almost every time is what makes a silent correction dangerous rather than safe.
The name is one fold away from an enrolled one
Class: actor-near-miss.
What it means. The name you passed differs from an enrolled, active name only in case or whitespace. Nothing was written.
What to do. The refusal prints the canonical spelling in quotes, so a trailing space is visible, and it prints the exact --actor argument to re-run with. Use it.
⛔ Do not run the enrolment ceremony. Enrolling the misspelling would mint a second registry row for one person. This class exists as its own class precisely so that you are not sent to enrol.
Why the product will not just fix it for you. The actor name is signed content. A silent normalisation would write a name you never typed into bytes your key then attests to.
The name is unknown to the registry
Class: actor-not-enrolled.
What it means. The name matched no enrolled, active actor. Two cases produce it that are worth telling apart before you act, because in both of them the remedy the refusal suggests is the wrong one:
- A look-alike character. A name written with a Cyrillic character that is visually identical to a Latin one is not treated as a near miss — the fold is case and whitespace only, on purpose. The refusal will invite you to run the enrolment ceremony. ⛔ Do not run it. Nothing was substituted and nothing was written, so no signature is at risk; what is wrong is only the advice, and following it would mint a second registry row for one person. The refusal prints the active roster;
saphan actor listshows it too. Correct the name at the keyboard. - A retired actor. A name one fold away from a retired row is
actor-not-enrolledtoo, and the refusal will again invite you to enrol. ⛔ Do not enrol someone the workspace retired on purpose.
⚠ In both cases the refusal is correct and its advice is not. This is known.
Why the comparison is not widened to look-alike characters. A confusable fold that fires wrongly merges two identities inside signed content — silently, and irreversibly, because the distinct spellings were the only evidence the two were ever different people. The narrow fold's worst case is a misleading sentence in a refusal a human reads.
The workspace has no signing key
Class: owner-key-absent.
What it means. Owner-class decisions are signed by the workspace key. This session has none, so the decision was refused and nothing was written. A brand-new workspace has none.
What to do — and note that every step is a human act.
- Check what is there.
saphan actor list --workspace <path>reports the key-id if a key exists, or says the workspace key is absent. ⚠ This verb is a read: it never mints. A read that minted would hand every keyless session a fresh self-authorising key as the price of looking. - Mint the key.
saphan actor init --workspace <path>. It writes the secret readable only by you, writes a cover beside it so the value can never be committed, and prints the derived key-id. The value itself is never printed, logged, or returned on any surface. - Enrol the key-id. Minting is not enrolment. Until an actor row names this identity the gate still refuses, under a different class naming the roster rather than the key. Enrolment is an owner-root ceremony, run on the machine holding the root key.
- Confirm.
saphan actor listnow shows the key-id and marks the roster row that matches it.
⚠ If you are running against the wrong workspace, the fix is not to mint a second key — re-run against the workspace that owns the record.
A key is already at the secret path
Class: workspace-key-exists.
What it means. saphan actor init found a file already at the secret path and refused rather than clobber it. That is all this refusal proves. It fires on any existing file — including a dangling symlink or a directory — because it asks whether the file is there, never whether it is good. ⚠ So it does not prove the workspace has a usable key.
There is no force flag, for the same reason root creation has none: a re-mint derives a new key-id, and every decision this workspace has already signed would verify against a key that no longer exists.
What to do. Ask saphan actor list. It reports the key-id, and a refusal from it means the file is not a key. Then:
what actor list or a gate says | what to do |
|---|---|
| the workspace key is absent | Mint it with saphan actor init. |
| the workspace secret has loose permissions | Restrict the file's mode to owner-only. ⛔ Do not re-mint: the key is fine, its mode is not. |
| the workspace secret is malformed | Restore the file from wherever this workspace's secret is kept. Only if it is genuinely lost: remove it and mint a new one — accepting that every signature the old secret wrote stops verifying. |
A root already exists on this host
Class: ceremony-root-exists.
What it means. saphan ceremony root found a root already in place — a root key, or its public half, which is deliberately left on disk after a ceremony as the evidence that this machine has a root. There is no force flag. Re-minting would silently invalidate every prior signature. A restore from the paper words (saphan ceremony root --restore) refuses the same way on a machine that already holds a root.
What to do. If you meant to mint a root, you are on the wrong host or the root you are looking for is already here. Establishing a new root is re-founding the fleet's trust, not a re-run — treat it as such. If you are restoring and this machine holds the wrong root, the restore's own refusal tells you to remove that public key by hand, deliberately, and restore again.
⚠ There is no root rotation. After a successful ceremony the private root key is wiped from disk and the paper words are the only way to sign as the root; --restore re-establishes it from those words. Losing the paper means you can never sign a new delegation or admit a new machine under the existing chain. Store it as you would a vault key, and consider geographically separated copies with equal physical protection.
The read-back was pointed at the ceremony's own word list
Class: ceremony-readback-self-referential.
What it means. The root ceremony requires a witness to read the recorded words back, and the engine compares them position by position. Only on an exact match is the key material erased from disk. The read-back was supplied from a file, and that file was the ceremony's own word list — which proves nothing.
What to do. A file read-back (--readback-file) exists for scripted rehearsals and for a witness's transcription. Point it at what the witness wrote, not at what the ceremony produced. Every file the ceremony wrote is still on disk. ⚠ Accepting the air-gap banner non-interactively (--yes) is allowed and never skips proof-of-capture; this refusal is that proof working.
An owner act with nobody at the terminal
Classes: owner-presence-absent, owner-presence-unconfirmed.
What they mean. Owner-class acts — a gate decision of the owner's class, a merge, a machine admission and their siblings — ask for a one-time six-character code that the product prints on the controlling terminal and you type back there. The workspace key says which workspace signs; the code is what says a person is at the keyboard. Nothing was written in either case.
owner-presence-absent— there was no terminal to ask on: the act ran from a script, a pipe or a background process. It is also what an owner act started in the console's browser terminal gets: there, the only accepted confirmation is your passkey, through the console's own prompt.owner-presence-unconfirmed— the code was asked for and not confirmed: five wrong codes, or the read was interrupted.
What to do. Run the act yourself, from a terminal you are sitting at on that host; the refusal prints the command line to re-run. From the console, use the passkey button on the gate the act belongs to. ⛔ Do not look for a way to feed the code from a script — the code is withheld from every other output stream on purpose.
⚠ What this does not claim. It stops a cooperating agent from performing an owner act simply because nothing refused it. It does not stop a hostile process running as your own user, which can open a terminal of its own.
The product refuses to start at all
Class: custody-binary-unsafe.
What it means. The program checks the custody of its own binary before it does anything. If that check refuses, nothing ran: no command, no workspace resolution, no write.
What to do — the refusal is self-diagnosing, so read it before you type anything.
- Read the whole refusal. It lists every broken clause, not the first one, so you fix the misconfiguration in one pass rather than re-running to discover the next problem. Each clause carries a literal, runnable fix command.
- Check the two identity lines against each other — the line naming the binary's owner and the line naming who you are running as. If the owner is neither the system nor you, the binary belongs to a third account: that is exactly the misconfiguration this check exists to catch, and copying a permissions command will not fix it. ⛔ Decide which account should hold the control plane before you change anything. This is a human decision, and the product deliberately does not make it for you.
- Run the printed fix commands, in the order printed. They change ownership, group and mode, and nothing else. Whether a line needs elevation is computed for you from who owns the thing being changed and who you are — not from the kind of command.
Named, not yet explained
These classes are real, deliberate and named. This section does not yet carry their resolution. If you are holding one, the name below is what it indicates, and asking us is what will get it written down.
| class | what its name indicates |
|---|---|
actor-registry-absent | The workspace has no signed actor registry, or it holds no actor rows. ⚠ Worth knowing: an absent registry is not consent — it is not read as "everyone is allowed". |
actor-registry-unreadable | A registry exists and could not be read: the owner root pin on this machine is missing or unloadable, a row fails verification, or a sequence has a gap. ⚠ Treat as unknown, never as empty. |
actor-registry-unwired | This build connected no registry store to the check, so it cannot say who may sign. The refusal itself calls it a defect of the product rather than of your workspace. ⛔ Do not enrol anyone over it — report it. |
actor-retired | The named actor is in the registry and has been retired. Recorded decisions made under a retired name stay valid; what a retired name cannot do is write a new one. |
ceremony-capture-mismatch | The witness read-back did not match the recorded words position by position. The refusal names the position, never the word, and the key material is deliberately left on disk so the paper can be corrected and checked again. |
ceremony-intermediate-exists | saphan ceremony intermediate found an intermediate key or certificate already in place. ⚠ It has a --force; overwriting orphans every short-lived certificate already issued under the old intermediate. |
ceremony-restore-word-count | A restore received a number of words that cannot be the root's 24. |
ceremony-restore-unknown-word | A word is not in the dictionary — a transcription error, or words from another list. The refusal names the position, never the word. |
ceremony-restore-pin-mismatch | The words decoded to a different root than the one you named with --expect-pin. Nothing was written. |
ceremony-restore-pin-unknown | No --expect-pin was given, and nothing else on the machine can say which root is expected. |
ceremony-restore-record-mismatch | The words produced the root you named, and this workspace's record does not verify under it — a correct paper record aimed at the wrong workspace. ⚠ Unlike the pin mismatch, the pin had been written; it was rolled back. |
ceremony-restore-no-presence | A restore was run with standard input that is not a terminal and without --yes. |
ceremony-restore-out-dir | --out-dir was given to a restore. A restore always writes to this machine's own trust directory, and refuses to be pointed elsewhere. |
custody-loose-perms | The workspace secret is readable or writable by group or others. ⛔ Do not re-mint: the key is fine, its mode is not. |
custody-malformed-secret | The workspace secret exists and is not a valid 32-byte regular file. |
no-identity | An act resolved no actor: no --actor, no SAPHAN_ACTOR, and no identity.actor in the configuration file. |
scope-denied | A call to the MCP server asked for data its caller's scope does not grant. ⚠ It is never sent to the client, which gets one uniform denial; it is written to the server's access log and the refusal record. |
server-cert-key-exists | saphan trust server-cert found a key already at --key-out and --reuse-key was not given. |
server-cert-no-san | saphan trust server-cert was asked for a certificate with no --dns and no --ip. A certificate with only a common name is rejected by TLS clients. |
server-cert-ttl-required | saphan trust server-cert was run without --ttl. There is no default: how long the gateway's identity lives is your decision. |
session-actor-key-absent | A gate decision of the master class found no workspace key in the session, as distinct from an owner-class decision (owner-key-absent). |
token-unmapped | A token verified, and the client it names is no longer registered — for example, revoked after the token was issued. ⚠ Like scope-denied, it reaches the access log, not the client. |
trust-key-exists | saphan trust init found a root key already at its target path. Same reason as ceremony-root-exists: a re-mint would invalidate every signature the old root produced. |
trust-refused | Verifying a signed document refused its trust chain — for example saphan verify, saphan laws cat or saphan order sign. The refusal names the cause and echoes nothing past it. |