8 / 8
End-to-end flows and diagnostics
Every earlier chapter looked at one part of the system on its own. This one pulls them together into a single picture and doubles as a reference to come back to when something isn't behaving.
The whole system at once
┌────────────────────────┐
│ athanor.yaml │ public key, expose flags,
│ (not secret, safe to │ access_mode, deploy.remote_path
│ commit to git) │
└───────────┬────────────┘
│ describes policy for
▼
athanor init ──► age identity ──► ~/.config/athanor/identity.txt
(shared across (private │ the only way
all projects, key) │ to decrypt
unless --own-key) ▼
┌──────────────────┐
athanor add ───────stdin─────►│ internal/store │◄──── Store.Get/Set/Run
(value never (json) │ (in the │ single entry point
an argument!) │ │ process's mem.) │ for every channel
▼ └─────────┬──────────┘
sops --encrypt │
(age recipient) │ Store.Run(): injects into
│ │ the child's exec.Cmd.Env
▼ │
secrets.enc.yaml ◄────────┘
(always encrypted)
│
┌─────────────┼──────────────────┬───────────────────┐
▼ ▼ ▼ ▼
athanor list athanor tui athanor serve-mcp athanor deploy
(names only, (human, (agent, untrusted (copies binary +
no values) trusted local context — Exposable() manifest + secrets.enc.yaml
context) checked only for + identity.txt to the VPS)
get_secret)
Access channels: who sees what
| Channel | Checks expose? |
What the consumer ultimately sees | Writes to the audit log? |
|---|---|---|---|
athanor list |
— (never returns values at all) | secret names only | no |
athanor tui (enter/y) |
no — trusted local context | the secret's value | no |
athanor run -- CMD |
no | CMD's environment; the command can print or transmit the secret |
yes |
MCP get_secret |
yes | the secret's value as text (only if Exposable(name)) |
no |
MCP run_with_secrets |
no | CMD's environment and output; the command can print or transmit the secret |
yes |
athanor deploy <host> |
— (moves the whole set at once) | the server receives both the ciphertext and the key itself | no |
Diagnostic order
When something isn't behaving as expected, check things in this order — from the foundation up:
- Nothing decrypts (
athanor list/run/tuifail with a sops error) — check that the identity file exists where it's expected (./identity.txtif the project used--own-key, otherwise~/.config/athanor/identity.txt), and that its public key matchesrecipientinathanor.yaml. A mismatched key andrecipientis the most common cause. get_secretrefuses, but you expected a value — checkexposefor that specific secret inathanor.yaml, andaccess_modeat the top of the manifest.--full-accessonserve-mcpandaccess_mode: fullin the file produce the same effect through two independent paths.run_with_secretsruns fine, but the value isn't in the output — that's not a bug: the secret lives in an environment variable inside the running process, and the tool's output is just that process's stdout/stderr. If the command doesn't print the variable, the tool has nothing to show. If the command prints a lot of data, also checkstdoutTruncated/stderrTruncated: each stream is capped at 1 MiB.- A secret shared between projects isn't staying in sync — check that
both
athanor.yamlfiles point at the exact samesecrets_file— the path resolves relative to each manifest separately, and it's easy to end up with two different files that just look similar. athanor deployfails the smoke test — tryssh <host>by hand, the same way deploy does (the alias or key from~/.ssh/config). Deploy doesn't fix SSH access problems, only reports them.
Checkpoint question. You have two projects on different VPSes. One has a
secret marked expose: true, but calling get_secret through the other
project's MCP server can't reach it. Is that a bug?
Answer: no. athanor serve-mcp, started from one project's directory,
only ever works with that project's manifest and secrets_file — even if
both projects share the same age identity, each manifest's expose flags
and its own list of secrets are independent, unless both explicitly point at
the same secrets_file.