docs: refresh per-crate ARCHITECTURE — missing core modules + CLI commands
Punch items from doc audit: - relicario-core: module map missing 5 public modules (backup, device, import_lastpass, recovery_qr, tar_safe); added with 1-2 sentence descriptions in the existing voice. - relicario-core: "ed25519-dalek is a dependency placeholder" was stale — device.rs now consumes it for signing/verify/keypair. - relicario-cli: Rate (zxcvbn scoring) and RecoveryQr (generate/unwrap) commands were absent from Key flows; added. - relicario-cli: "Backup-passphrase-style commands (none yet)" rewritten — Backup (export/restore .relbak) and Import (lastpass) both shipped. - relicario-cli: module map refreshed — handlers moved out of main.rs into commands/, plus prompt.rs/parse.rs/device.rs/gitea.rs surfaced. Stale main.rs:NNNN line citations on individual flows are not fixed here — those handlers now live in commands/*.rs and warrant a deeper pass later. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -16,22 +16,46 @@ locally, and lets recovery debugging happen with familiar tooling.
|
||||
|
||||
## Module map
|
||||
|
||||
The crate is three files of source and a `tests/` directory. Each source file
|
||||
has one job.
|
||||
`src/main.rs` is now a thin clap-surface + dispatcher; per-command logic lives
|
||||
under `src/commands/`. Each source file has one job.
|
||||
|
||||
- **`src/main.rs`** (`main.rs:1-1719`) — clap surface plus every command
|
||||
handler. Internal structure: a top-level `Cli` / `Commands` enum
|
||||
(`main.rs:13-275`), a flat dispatcher `match` in `main()`
|
||||
(`main.rs:277-303`), per-command handlers named `cmd_<verb>`, and a layer of
|
||||
per-type item helpers (`build_<type>_item` for `cmd_add`, `edit_<type>` for
|
||||
`cmd_edit`). The per-type split is recent: commit `3f0f5b1` extracted
|
||||
~217-line `match` arms in `cmd_add` and `cmd_edit` into focused functions,
|
||||
one per `ItemCore` variant, so each builder/editor reads top-to-bottom and
|
||||
can be tested through the same integration paths. Owns all clap argument
|
||||
parsing, all interactive prompts (`prompt`, `prompt_optional`, `prompt_keep`,
|
||||
`prompt_keep_opt`, `prompt_yesno`, `prompt_secret`), and the shared
|
||||
`commit_paths` helper that is the single chokepoint for git commits during
|
||||
vault mutations.
|
||||
- **`src/main.rs`** (`main.rs:1-492`) — clap surface and the flat dispatcher.
|
||||
Owns the top-level `Cli` / `Commands` enum and every subcommand enum
|
||||
(`AddKind`, `TrashAction`, `SettingsAction`, `BackupAction`, `ImportAction`,
|
||||
`DeviceAction`, `RecoveryQrCmd`). `main()` is a single `match` that
|
||||
delegates each variant to `commands::<verb>::cmd_<verb>(...)`. Also owns the
|
||||
three test-only env-var hooks (`test_passphrase_override`,
|
||||
`test_item_secret_override`, `test_backup_passphrase_override`) — each is
|
||||
stripped from release builds via `#[cfg(debug_assertions)]`.
|
||||
|
||||
- **`src/commands/`** — one module per top-level command. `mod.rs` re-exports
|
||||
the public surface and hosts the shared `commit_paths` helper (the single
|
||||
chokepoint for git commits during vault mutations) plus other cross-command
|
||||
glue. Per-command modules: `init`, `add`, `get`, `list` (also hosts
|
||||
`cmd_history`), `edit`, `trash` (rm / restore / purge / trash empty),
|
||||
`backup` (export / restore), `import` (lastpass), `attach` (attach /
|
||||
attachments / extract / detach), `generate`, `settings`, `sync`, `status`,
|
||||
`rate`, `device`, `recovery_qr`. `add` and `edit` each fan out internally to
|
||||
per-`ItemCore` helpers (`build_<type>_item`, `edit_<type>`) so each
|
||||
builder/editor reads top-to-bottom and can be tested through the same
|
||||
integration paths.
|
||||
|
||||
- **`src/prompt.rs`** — interactive prompt primitives shared across commands:
|
||||
`prompt`, `prompt_optional`, `prompt_keep`, `prompt_keep_opt`,
|
||||
`prompt_yesno`, `prompt_secret`. `prompt_secret` honours
|
||||
`RELICARIO_TEST_ITEM_SECRET` before falling back to `rpassword`.
|
||||
|
||||
- **`src/parse.rs`** — pure parsers for CLI-typed inputs (e.g. MonthYear
|
||||
expiries, TOTP `otpauth://` URIs, comma-separated tag lists). No I/O.
|
||||
|
||||
- **`src/device.rs`** — device-management plumbing called by
|
||||
`commands::device`: ed25519 keypair generation via `relicario-core::device`,
|
||||
on-disk layout under `<config_dir>/relicario/devices/<name>/`, and the
|
||||
read/write of `.relicario/devices.json` / `revoked.json`.
|
||||
|
||||
- **`src/gitea.rs`** — minimal Gitea REST client used by `commands::device add`
|
||||
/ `revoke` to register and remove deploy keys. Reads
|
||||
`RELICARIO_GITEA_{URL,TOKEN,OWNER,REPO}` env vars (overridable via CLI flags).
|
||||
|
||||
- **`src/session.rs`** (`session.rs:1-152`) — `UnlockedVault` lifecycle. Holds
|
||||
the derived master key in `Zeroizing<[u8; 32]>` for one CLI invocation; the
|
||||
@@ -306,13 +330,65 @@ rewrite `devices.json`, commit `device: revoke <name>`. Note that device
|
||||
keys are kept entirely separate from the KDF (passphrase × image stays
|
||||
unchanged across device add/revoke), as per the design spec.
|
||||
|
||||
### Backup-passphrase-style commands (none yet)
|
||||
### Backup (`commands::backup`, `commands/backup.rs`)
|
||||
|
||||
The import / export / `import-lastpass` commands described in
|
||||
`docs/superpowers/specs/2026-04-27-relicario-import-export-design.md` are
|
||||
not yet implemented. When they land they'll fit in the dispatcher
|
||||
(`main.rs:279-302`) alongside `Sync` and `Status`. Don't add stubs here
|
||||
until that work begins.
|
||||
Two subcommands, both keyed by a *backup* passphrase that is independent of
|
||||
the vault master passphrase.
|
||||
|
||||
- **`backup export <out> [--include-image] [--image PATH] [--no-history]`** —
|
||||
reads the entire on-disk vault layout (`.relicario/{salt,params.json,
|
||||
devices.json}`, `manifest.enc`, `settings.enc`, every `items/*.enc`, every
|
||||
`attachments/<iid>/<aid>.enc`), optionally bundles the reference JPEG and
|
||||
the `.git/` directory (as an in-memory tar), and hands the lot to
|
||||
`relicario_core::backup::pack_backup` with a zxcvbn-gated backup
|
||||
passphrase prompted twice. The resulting `.relbak` is written via
|
||||
`tmp` + rename. A `.relicario/last_backup` marker file (ISO-8601 line) is
|
||||
also written so `cmd_status` can show "last backup at …".
|
||||
- **`backup restore <input> [<target>]`** — refuses to overwrite an existing
|
||||
vault (`target/.relicario/` must not exist). Unpacks the `.relbak` via
|
||||
`unpack_backup`, then materialises every byte into the target layout. The
|
||||
bundled `.git/` tar is extracted via the hardened
|
||||
`relicario_core::safe_unpack_git_archive` (path-traversal / symlink /
|
||||
size-cap guards) with a cap of `min(100 × tar_size, 1 GiB)`; if no
|
||||
history was bundled, the target gets a fresh `git init` + initial commit.
|
||||
|
||||
### Import (`commands::import`, `commands/import.rs`)
|
||||
|
||||
- **`import lastpass <csv>`** — reads the CSV, calls
|
||||
`relicario_core::import_lastpass::parse_lastpass_csv`, then unlocks the
|
||||
vault and writes every produced `Item` through `vault.save_item` + manifest
|
||||
upsert. Failed rows surface as `ImportWarning`s on stderr and never abort
|
||||
the import; only a missing or malformed header is fatal. Commit message:
|
||||
`import: <N> items from LastPass (<csv-filename>)`. The dispatch shape
|
||||
(`ImportAction` subcommand enum) is in place for future importers
|
||||
(Bitwarden, 1Password, etc.) — each would add one `ImportAction` variant
|
||||
and one helper.
|
||||
|
||||
### Rate (`commands::rate`, `commands/rate.rs`)
|
||||
|
||||
`rate <passphrase|->` runs `relicario_core::generators::rate_passphrase`
|
||||
(zxcvbn-backed) and prints the 0–4 score, a human-readable label, and the
|
||||
estimated guess count as `~10^N`. Reads one line from stdin when the
|
||||
argument is `-`, which keeps the passphrase out of shell history. Purely
|
||||
informational — does not unlock or mutate anything; the `init` command
|
||||
calls `validate_passphrase_strength` directly and does not consult `rate`.
|
||||
|
||||
### RecoveryQr (`commands::recovery_qr`, `commands/recovery_qr.rs`)
|
||||
|
||||
Two subcommands wrapping `relicario_core::recovery_qr::{generate_recovery_qr,
|
||||
unwrap_recovery_qr}`.
|
||||
|
||||
- **`recovery-qr generate`** — re-extracts the 32-byte image_secret from the
|
||||
reference JPEG (via `get_image_path` + `imgsecret::extract`), prompts for
|
||||
the recovery passphrase (which may be the same as the vault passphrase or
|
||||
different — domain-separated by core), produces the 109-byte sealed
|
||||
payload, and renders it as a Unicode-block QR (EcLevel::M) directly to
|
||||
stdout. The payload is **never written to disk** — the user is expected to
|
||||
print or photograph it.
|
||||
- **`recovery-qr unwrap`** — reads a base64-encoded payload from stdin,
|
||||
prompts for the recovery passphrase, runs `unwrap_recovery_qr`, and prints
|
||||
the recovered `image_secret` as hex. Useful for recovery dry-runs and for
|
||||
reconstructing a lost reference image.
|
||||
|
||||
## Cross-cutting concerns
|
||||
|
||||
|
||||
Reference in New Issue
Block a user