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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5JvzEse4xUxLZKhofyeCD
This commit is contained in:
107
docs/superpowers/specs/2026-06-20-relicario-v0.8.1-parity.md
Normal file
107
docs/superpowers/specs/2026-06-20-relicario-v0.8.1-parity.md
Normal file
@@ -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:<field>` 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<String>` flag args |
|
||||
|
||||
- **Org storage** is `items/<slug>/<id>.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/<slug>/<id>.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/<item-id>/…`; org: `attachments/<slug>/<item-id>/…`).
|
||||
- **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 <s> --title <t> [--holder <h>] [--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 <s> --title <t> [--label <l>] [--algorithm <a>] [--public-key <p>] [--material-stdin]`
|
||||
- `org add totp --collection <s> --title <t> [--issuer <i>] [--label <l>] [--period 30] [--digits 6] [--algorithm sha1] [--secret <b32> | --secret-stdin]`
|
||||
- `org add document --collection <s> --title <t> --file <path>` — 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/<slug>/<item-id>/<att-id>.enc` — collection-scoped, mirroring `items/<slug>/<id>.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/<slug>/<item-id>/<att-id>.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/<id>/<att>.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.
|
||||
Reference in New Issue
Block a user