Architecture
Ports-and-adapters over a flat crates/ workspace. cuora-core is pure domain — cuora:// refs, the redacting Secret newtype, the env axis, actor attribution — with no I/O. Ports are traits (cuora-config, cuora-vault, cuora-deploy); impls live in separate crates; crates/cuora-cli (package cuora) is the composition root that wires impls to verbs.
Design rules:
- No
unsafe, no async — the binary stays dependency-light and synchronous; vault impls that speak HTTP use a blocking client (ureq), so no runtime embeds anywhere. Secretnever exposes plaintext — noDisplay/Debugof the value;.expose()is the only way out, called at edges only (child env injection,cuora envexports,get).- Stores hold ciphertext only — every vault impl encrypts client-side with age (ADR-0001); a remote vault is ciphertext over the wire and at rest, never a plaintext service.
- Ports are conformance-tested —
cuora-testkitruns the same contract suite against every vault impl (cuora-vault-age,cuora-vault-cf): upsert/rotate/list/env-isolation semantics can't drift per backend. - One ops shell —
cuora-opsowns config load, env→vault routing, the env-pin rule, and audit emission; the CLI and the TUI are both thin front-ends over it and cannot diverge.
| crate | layer | purpose |
|---|---|---|
cuora-core | domain | cuora domain core: cuora:// refs, redacting Secret, env axis, actor, config model — pure, no I/O |
cuora-config | port | Config port: where cuora.toml-shaped config comes from |
cuora-deploy | port | Deploy port: write-only push targets (sinks) for secret values — never a source |
cuora-vault | port | Vault port: readable secret stores — get/put/rm/list/rotate, per-env; every impl stores opaque ciphertext |
cuora-ops | ops | cuora ops I/O: config load, env/ref resolution, vault open, verb plumbing, audit emission |
cuora-config-toml | adapter | Config impl: cuora.toml file (explicit path, env, ./, ~/.config) |
cuora-deploy-cf | adapter | Deploy impl: push secret values to a Cloudflare account Secrets Store (write-only sink) via the CF API v4 |
cuora-driver | adapter | tamias-driver-cuora — pavo view driver over cuora vault metadata (read-only; never renders secret values) |
cuora-identity | adapter | Identity port: where the age identity (master key) comes from — file, keyring, later sealed variants |
cuora-identity-file | adapter | Identity impl: the ~/.config/cuora/identity.txt age keyfile (0600) — the headless/CI default |
cuora-identity-keyring | adapter | Identity impl: the platform session secret store (secret-service / Keychain / Credential Manager) via keyring-core |
cuora-notice | adapter | Process-wide UX notices — cuora:-prefixed stderr notes, mutable for terminal-owning surfaces |
cuora-serve | adapter | cuora serve — the ndjson request/response IPC surface orchestrators embed, with the external policy-decision consult (P-005) |
cuora-shell | adapter | Run a command in a working directory via bash -c — the generic shell-out helper |
cuora-testkit | adapter | Vault conformance suite — every cuora-vault impl passes the same contract tests (fake values only) |
cuora-vault-age | adapter | Local vault impl: one age-x25519-encrypted JSON doc — the v1 default backend |
cuora-vault-cached | adapter | Composite vault impl: read-through age-encrypted local cache in front of any backend (P-007) |
cuora-vault-cf | adapter | Remote vault impl: self-hosted Cloudflare Worker + D1 holding per-secret age ciphertext — the Worker never sees plaintext |
cuora | binary | cuora — secrets broker for agents: reference URIs, redaction, env-injection CLI |
Front-ends
cuora(CLI) — clap verbs +--json/manifest/schemafor machines; the agent interface.cuora ui(TUI) — a read-only ratatui browser over the same ops; feature-gated (--no-default-featuresfor a lean binary).cuora serve— ndjson IPC over the Vault port with an exec-driven[serve.pdp]policy-decision hook (ADR-0003); peregrin's embedding point.
Boundary — secret materialization only
cuora owns refs, vaults, env injection, redaction, and the audit wire. Neighbors own theirs — see boundaries for the full map.