Skip to content

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.
  • Secret never exposes plaintext — no Display/Debug of the value; .expose() is the only way out, called at edges only (child env injection, cuora env exports, 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-testkit runs 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-ops owns 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.
cratelayerpurpose
cuora-coredomaincuora domain core: cuora:// refs, redacting Secret, env axis, actor, config model — pure, no I/O
cuora-configportConfig port: where cuora.toml-shaped config comes from
cuora-deployportDeploy port: write-only push targets (sinks) for secret values — never a source
cuora-vaultportVault port: readable secret stores — get/put/rm/list/rotate, per-env; every impl stores opaque ciphertext
cuora-opsopscuora ops I/O: config load, env/ref resolution, vault open, verb plumbing, audit emission
cuora-config-tomladapterConfig impl: cuora.toml file (explicit path, env, ./, ~/.config)
cuora-deploy-cfadapterDeploy impl: push secret values to a Cloudflare account Secrets Store (write-only sink) via the CF API v4
cuora-driveradaptertamias-driver-cuora — pavo view driver over cuora vault metadata (read-only; never renders secret values)
cuora-identityadapterIdentity port: where the age identity (master key) comes from — file, keyring, later sealed variants
cuora-identity-fileadapterIdentity impl: the ~/.config/cuora/identity.txt age keyfile (0600) — the headless/CI default
cuora-identity-keyringadapterIdentity impl: the platform session secret store (secret-service / Keychain / Credential Manager) via keyring-core
cuora-noticeadapterProcess-wide UX notices — cuora:-prefixed stderr notes, mutable for terminal-owning surfaces
cuora-serveadaptercuora serve — the ndjson request/response IPC surface orchestrators embed, with the external policy-decision consult (P-005)
cuora-shelladapterRun a command in a working directory via bash -c — the generic shell-out helper
cuora-testkitadapterVault conformance suite — every cuora-vault impl passes the same contract tests (fake values only)
cuora-vault-ageadapterLocal vault impl: one age-x25519-encrypted JSON doc — the v1 default backend
cuora-vault-cachedadapterComposite vault impl: read-through age-encrypted local cache in front of any backend (P-007)
cuora-vault-cfadapterRemote vault impl: self-hosted Cloudflare Worker + D1 holding per-secret age ciphertext — the Worker never sees plaintext
cuorabinarycuora — secrets broker for agents: reference URIs, redaction, env-injection CLI

Front-ends ​

  • cuora (CLI) — clap verbs + --json/manifest/schema for machines; the agent interface.
  • cuora ui (TUI) — a read-only ratatui browser over the same ops; feature-gated (--no-default-features for 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.