From b2f3739673df957e7d85ba5c469cb87aec6eb99a Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sat, 20 Jun 2026 16:38:05 -0400 Subject: [PATCH] docs(spec): v0.8.1 org item-type parity (Card/Key/Document/Totp) design Card/Key/Totp = CLI-only parity via shared item-build module; Document adds org attachment storage + a relicario-server hook change that grant-scopes attachment paths (closing the Unrestricted gap). Secrets via interactive prompts + --*-stdin escape hatches. Four suggested dev streams (A foundation, B Card/Key/Totp, C Document+attachments, D hook). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01L5JvzEse4xUxLZKhofyeCD --- .../2026-06-20-relicario-v0.8.1-parity.md | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-20-relicario-v0.8.1-parity.md diff --git a/docs/superpowers/specs/2026-06-20-relicario-v0.8.1-parity.md b/docs/superpowers/specs/2026-06-20-relicario-v0.8.1-parity.md new file mode 100644 index 0000000..2aaa02f --- /dev/null +++ b/docs/superpowers/specs/2026-06-20-relicario-v0.8.1-parity.md @@ -0,0 +1,107 @@ +# Relicario v0.8.1 — Org Vault Item-Type Parity (Design Spec) + +**Date:** 2026-06-20 +**Status:** Approved (design) — implementation plan to follow +**Predecessor:** `docs/superpowers/specs/2026-06-06-relicario-enterprise-org-vault-design.md` (org vault shipped v0.8.0, `50b5c01`) +**Tracked-from:** the org-vault plan's deferral note — `docs/superpowers/plans/2026-06-06-enterprise-org-vault.md:3839` and the v0.8.1 follow-up list at `:6132`. + +## Goal + +Bring `relicario org add` and `relicario org edit` to **full item-type parity** with the personal vault. Today the org surface supports only **Login, SecureNote, Identity**; this milestone adds **Card, Key, Document, Totp**. + +Secrets are entered via **interactive prompts by default**, with **`--*-stdin` escape hatches** for non-interactive scripting and the acceptance tests — matching the personal vault's secret-input philosophy while staying fully testable. + +Document support additionally requires org-side **attachment storage** and a **`relicario-server` pre-receive hook change** that grant-scopes attachment write paths (closing a latent authorization gap). + +## Background — current state (verified) + +| Surface | Coverage today | Mechanism | +|---|---|---| +| Personal `add` (`commands/add.rs`) | All 7 types | per-type `build_*_item`; flags + `prompt_secret`/stdin | +| Personal `edit` (`commands/edit.rs`) | All 7 types | interactive per-type, "blank to keep", **field history** (synthetic `core:` FieldIds) | +| Org `add` (`commands/org.rs::build_org_item`) | Login / SecureNote / Identity | plain value flags only (incl. `--password`) | +| Org `edit` (`commands/org.rs::run_edit`) | Login / SecureNote / Identity | flat `Option` flag args | + +- **Org storage** is `items//.enc` only (`org_session.rs::item_path`). There is **no attachment support and no settings/caps** on the org side. +- **Hook** (`crates/relicario-server/src/lib.rs::classify_path`) classifies paths as `Protected` (members/collections/org.json), `Item { collection }` (`items//.enc` — grant-authorized), `Rejected`, or **`Unrestricted`** (everything else — gated only by the per-commit member-signature check). An `attachments/...` path therefore currently falls through to **`Unrestricted`**: any member could push attachment blobs regardless of collection grants. Document parity must close this. + +Why the four types were deferred: their personal builders read secrets interactively (`prompt_secret` / multiline stdin), so they had no non-interactive path the org acceptance tests could drive. Document additionally has no org storage target. + +## Design + +### Approach + +**Shared builder/edit module + parity on both surfaces.** Extract the per-type item construction, secret-resolution, and interactive-edit logic into one CLI module that *both* the personal commands and the org commands call. This eliminates the existing personal↔org builder duplication and prevents the two surfaces from drifting again. `--*-stdin` is added to **both** surfaces (true parity), not org-only. + +Rejected alternatives: (B) duplicate org-specific builders in `org.rs` — smaller blast radius but locks in two diverging builder sets, which is exactly the drift this milestone is paying down; (C) push builders into `relicario-core` — overkill, since prompt/stdin/storage logic does not belong in the bytes-in/bytes-out core. + +### 1. Shared item-build module — `crates/relicario-cli/src/commands/item_build.rs` + +- **`SecretSource` resolution**: a helper that resolves a secret field in priority order — explicit flag value → `--*-stdin` (read a single line, or multiline-to-EOF for key material / note body) → interactive `prompt_secret`/`prompt`. If a required secret has no flag, no stdin flag, and the process is non-interactive (no TTY), it errors clearly rather than hanging. +- **Builders** `build_login`, `build_secure_note`, `build_identity`, `build_card`, `build_key`, `build_totp` → return a fully-populated `Item` (no storage side effects). +- **`build_document`** → returns `(Item, EncryptedAttachment)` so each caller writes the encrypted blob with *its own* master key and *its own* path layout (personal: `vault.root()/attachments//…`; org: `attachments///…`). +- **Shared per-type interactive edit helpers** — mutate a `&mut ItemCore` slice in place and record field history via the existing synthetic-`FieldId` scheme (`commands/edit.rs::push_history`), reused by both personal and org edit. +- Personal `add.rs` / `edit.rs` are refactored to call these helpers with **no behavior change** (existing personal tests stay green), then gain `--*-stdin` flags. + +### 2. CLI surface — `org add` + +Extend `OrgAddKind` (in `main.rs`) with `card` / `key` / `document` / `totp` subcommands mirroring the personal `AddKind` flags, plus the org-required `--collection` and the secret-stdin flags: + +- `org add card --collection --title [--holder ] [--expiry YYYY-MM] [--kind credit|debit|gift|loyalty|other] [--number-stdin] [--cvv-stdin] [--pin-stdin]` — secrets prompted when a TTY is present and no `--*-stdin` flag is set. +- `org add key --collection --title [--label ] [--algorithm ] [--public-key

] [--material-stdin]` +- `org add totp --collection --title [--issuer ] [--label ] [--period 30] [--digits 6] [--algorithm sha1] [--secret | --secret-stdin]` +- `org add document --collection --title --file ` — no secret; file bytes encrypted with the org key and written to the collection-scoped attachment path. + +Retrofit `org add login` to accept `--password` / `--password-stdin` (+ prompt fallback) so the existing type matches the new convention. All paths flow through the shared builders and are committed via the existing signed `org_git_run` path with the same `Relicario-*` trailers as today's `run_add`. + +### 3. CLI surface — `org edit` + +Restructure `run_edit` to dispatch per item type (mirroring personal `edit`): interactive "blank to keep" by default, with flag / `--*-stdin` overrides for scripts and tests. Field history is recorded with the same synthetic-key scheme as personal edit. Document edit accepts an optional `--file` that re-encrypts and replaces the primary attachment (re-points `DocumentCore.primary_attachment` + `AttachmentRef`, stages old + new paths). Grant + collection-existence checks are unchanged. + +### 4. Org attachment storage + cap + +- **Layout:** `attachments///.enc` — collection-scoped, mirroring `items//.enc`. +- **`org_session` methods:** `attachment_path`, `save_attachment`, `load_attachment`, `remove_item_attachments` (purge removes an item's attachment directory). +- **Cap:** a **default constant** in the CLI org path (mirroring the personal-vault `attachment_caps` default; the spec/code cites the source line per the code-constant-pinning rule). Per-org configurable caps are out of scope for v0.8.1. + +### 5. Hook change — `relicario-server` + +- Extend `classify_path` (`lib.rs`) to recognize `attachments///.enc` and classify it as `PathClass::Item { collection: slug }` — reusing the existing grant + slug-existence authorization for items. Apply the same defenses as the `items/` branch: exact segment count and a `.`-free slug guard (path-traversal defense). +- This converts attachment writes from `Unrestricted` to grant-scoped, closing the gap. +- **Version bump** for `relicario-server`; the release notes must call out a **coordinated server redeploy** (the deployed pre-receive hook must be rebuilt) — Document writes to a not-yet-upgraded server still succeed but remain `Unrestricted` until the hook is updated. + +### 6. Tests (acceptance) + +- `crates/relicario-cli/tests/org_items.rs`: non-interactive add → get → edit → rm round-trips for **Card, Key, Totp, Document** driven through the `--*-stdin` flags; secret masking verified in `org get` without `--show`; a grant-denied attachment-write case. +- `crates/relicario-server` lib tests: `classify_path("attachments/eng//.enc") == Item { collection: "eng" }`; rejection cases for malformed attachment paths. +- Existing personal `add`/`edit` tests stay green after the shared-module refactor (behavior-preserving). +- Green across all crates (`cargo test`). + +### 7. Living-docs updates (per CLAUDE.md discipline) + +- `docs/FORMATS.md` — org attachment path layout + the default cap constant (cite source line). +- `crates/relicario-cli/ARCHITECTURE.md` — the shared `item_build` module + per-type org `add`/`edit`. +- `docs/SECURITY.md` — attachment writes are now grant-scoped (closing the `Unrestricted` gap). +- `STATUS.md` / `ROADMAP.md` / `CHANGELOG.md` — on release; mark org item-type parity landed, move Document/attachment + hook change to shipped. +- Extension docs untouched — extension org **writes** remain deferred (Plan B-2). + +## Out of scope (v0.8.1) + +- Extension org **writes** (`Plan B-2`). +- Per-collection subkeys, read audit, SSO/SAML/LDAP, HTTP management plane (phase 2). +- Per-org **configurable** attachment cap (a default constant ships now). + +## Suggested execution decomposition (for the plan) + +Four parallel dev streams; Dev-A is the dependency gate for B and C, Dev-D is fully independent: + +| Stream | Scope | Depends on | +|---|---|---| +| **Dev-A** | Shared `item_build` module (SecretSource, builders, shared edit helpers); refactor personal `add`/`edit`; add `--*-stdin` to personal CLI | — (foundation) | +| **Dev-B** | Org `add`/`edit` parity for **Card / Key / Totp**; secret-stdin flags; field history; `org_items` tests | Dev-A module interface | +| **Dev-C** | Org **Document** + attachment storage (`org_session` methods, default cap, doc add/edit via `--file`); Document tests | Dev-A (`build_document`) | +| **Dev-D** | `relicario-server` hook: `classify_path` attachment grant-scoping; server tests; version bump | — (independent) | + +## Open questions + +None blocking. The cap value and the exact `--*-stdin` flag spellings are finalized in the plan against the personal-vault source.