Compare commits

..

1 Commits

4 changed files with 47 additions and 52 deletions

View File

@@ -7,8 +7,7 @@
| Version | Highlights |
|---|---|
| **v0.8.1** *(2026-06-20, tag pending PM)* | **Org item-type parity + collection-scoped attachments + grant-scoped hook** (`4c0a289`, four parallel streams): `relicario org add`/`edit` now cover **all 7 item types** — Card/Key/Totp (Dev-B `6e73c5e`) and Document (Dev-C `4c0a289`) on the shared `item_build` foundation (Dev-A `b09e0ce`); org attachments stored collection-scoped at `attachments/<slug>/<item-id>/<att-id>.enc` with a default cap (Dev-C); `relicario-server` `classify_path` grant-scopes those attachment writes (Dev-D `db4e05a`, server `0.1.1`**requires pre-receive hook redeploy**). **Still deferred:** extension org read/write (forward plan: `docs/superpowers/specs/2026-06-20-extension-cli-parity-gap-analysis.md`); org phase 2. |
| v0.8.0 *(2026-06-20)* | **Enterprise org vault — backend complete** (`7392795`): relicario-core `org` module (ECIES X25519 key wrap/unwrap, `OrgRole`/`OrgMember`/`OrgManifest` types, `filter_for_member`, `schema_version: 1`); relicario-server org hook (`verify-org-commit`: signature verification, path-scoped authz, `enforce_owner_only_elevation` on parent role, `enforce_schema_monotonicity`, `generate-org-hook`, new `[lib]` target); relicario-cli — all 19 `relicario org` subcommands: init, add-member/remove-member/set-role, create-collection/grant/revoke, rotate-key (re-encrypts all blobs), transfer-ownership, delete-org, status, audit, and item CRUD (add/get/list/edit/rm/restore/purge). Org item-type parity for Card/Key/Document/Totp shipped subsequently in v0.8.1; extension org parity + phase 2 (SSO/LDAP, read audit, per-collection subkeys, HTTP plane) remain deferred. |
| *(untagged, 2026-06-20)* | **Enterprise org vault — backend complete** (`7392795`): relicario-core `org` module (ECIES X25519 key wrap/unwrap, `OrgRole`/`OrgMember`/`OrgManifest` types, `filter_for_member`, `schema_version: 1`); relicario-server org hook (`verify-org-commit`: signature verification, path-scoped authz, `enforce_owner_only_elevation` on parent role, `enforce_schema_monotonicity`, `generate-org-hook`, new `[lib]` target); relicario-cli — all 19 `relicario org` subcommands: init, add-member/remove-member/set-role, create-collection/grant/revoke, rotate-key (re-encrypts all blobs), transfer-ownership, delete-org, status, audit, and item CRUD (add/get/list/edit/rm/restore/purge). **Not yet shipped:** `org add`/`edit` for Card/SshKey/Document/Totp; extension org parity (Dev-D); phase 2 (SSO/LDAP, read audit, per-collection subkeys, HTTP plane). |
| v0.7.0 *(2026-06-01)* | Extension restructure (Plan C) complete — Phases 3/4/6 merged via 3 parallel worktree streams under PM coordination: setup wizard crypto migrated into the SW (`create_vault`/`attach_vault`; `setup.ts` 1230→58 LOC + step registry); `vault.ts` split 1037→194 LOC into 5 focused + 2 support modules; `vault_locked` intercept lifted into `shared/state.ts`; `get_vault_status` SW message + sidebar status indicator closing the last `relicario status` CLI/extension parity gap |
| v0.6.0 *(2026-05-30)* | Security audit fixes; device authentication; backup/restore + LastPass import; fullscreen UX Phases 1+2A+2B; v0.5.1 Streams A/B/C (3-column vault layout + bottom-sheet picker + toast system; left-nav settings; Recovery QR end-to-end + setup wizard Style C); 1C-γ (attachments + Document type + device registration + trash + field history); Plan B multi-stream refactor (commands/ split, prompt_or_flag, core/WASM seam); vault-tab management surfaces revamp (settings synced/local split, devices fingerprint, trash purge countdown, field-history polish, item-history-index, `#history/<id>` routing); doc-structure redesign (rename to DESIGN/CRYPTO/docs/FORMATS, scope headers + Next: footers); GPL-3.0-or-later license |
| v0.2.0 | Typed-item rewrite (Plans 1A/1B/1C-α/β₁/β₂) |
@@ -17,11 +16,11 @@ See `CHANGELOG.md` for tagged-release detail and `STATUS.md` for the per-train c
## Up next
All three 2026-05-04 architecture-review specs are shipped; the enterprise org vault backend (v0.8.0) and org item-type parity + collection-scoped attachments (v0.8.1) are shipped. Forward plan for extension parity: `docs/superpowers/specs/2026-06-20-extension-cli-parity-gap-analysis.md`. Pending items in rough priority order:
All three 2026-05-04 architecture-review specs are shipped; enterprise org vault backend is shipped (2026-06-20). Pending items in rough priority order:
- **Org-vault item-type parity** — `org add`/`edit` support for Card, SshKey, Document, Totp (Login/SecureNote/Identity ship today)
- **Extension org parity — read** — org switch + collection-filtered browse in the popup/vault tab (Dev-D, deferred)
- **Extension org parity — write** — `org add`/`edit`/`rm` from the extension (Plan B-2; the CLI side reached all-7-type org write in v0.8.1, so this is unblocked CLI-side)
- **Personal-side extension gaps** — favorites UI, group/tag/filter editing across all type forms, attachment-remove router wire + per-item purge UI, autofill registrable-domain matching (per the parity gap analysis)
- **Extension org parity — write** — `org add`/`edit`/`rm` from the extension
- **Phase 4: command palette** — ⌘K global search + action dispatch across the vault tab (no spec yet)
## Medium-term

View File

@@ -5,23 +5,10 @@
## Version
**Last release tagged:** v0.6.0 — rolled up Phase 2B, v0.5.1 Streams A/B/C, 1C-γ, Plan B refactor (Cycles 1+2), management-surfaces revamp, and the doc-structure redesign into one tag.
**Active track:** **v0.8.1 — org item-type parity — COMPLETE (on `main` `4c0a289`; tag pending PM).** All four parallel streams merged: shared item-build foundation + personal add/edit refactor (Dev-A, `b09e0ce`); org add/edit parity for Card/Key/Totp (Dev-B, `6e73c5e`); org Document + collection-scoped attachment storage (Dev-C, `4c0a289`); grant-scoped attachment write-path hook (Dev-D, `db4e05a`). See the v0.8.1 landing section below.
**Active track:** **extension restructure (Plan C) — COMPLETE.** All six phases merged. Phases 1, 2, 5 merged 2026-05-30; Phases 3, 4, 6 merged 2026-05-31/06-01 via three parallel worktree streams (Dev-A/B/C under PM coordination). Versions bumped to v0.7.0; tag pending.
## What landed on main since the v0.5.0 version bump
### v0.8.1 — org item-type parity + collection-scoped attachments + grant-scoped hook (merged 2026-06-20, `4c0a289`)
Spec: `docs/superpowers/specs/2026-06-20-relicario-v0.8.1-parity.md`; plan: `docs/superpowers/plans/2026-06-20-relicario-v0.8.1-parity.md`. Four parallel streams under PM coordination (relay-bus):
- **Dev-A — shared item-build foundation** (merge `b09e0ce`): `commands/item_build.rs` (shared secret-resolution, type parsers, per-type `build_*`/`edit_*` helpers, `push_history`); personal `add`/`edit` refactored onto it; personal `--*-stdin` flags for non-interactive scripting/tests.
- **Dev-B — org Card/Key/Totp parity** (merge `6e73c5e`): `OrgAddKind` gains Card/Key/Totp; `org edit` becomes per-type interactive dispatch (the old "login/secure-note/identity only" bail is gone).
- **Dev-C — org Document + collection-scoped attachments** (merge `4c0a289`): `OrgAddKind::Document`; `org_session.rs` attachment storage (`attachment_path`/`save_attachment`/`load_attachment`/`remove_item_attachments`) writing `attachments/<slug>/<item-id>/<att-id>.enc`; default org attachment cap; `org add document --file` + `org edit --file`; purge removes the item's attachment dir.
- **Dev-D — grant-scoped attachment hook** (merge `db4e05a`): `relicario-server` `classify_path` recognizes `attachments/<slug>/<item-id>/<att-id>.enc` (3 segments, slug-only `.`-free guard) as `Item { collection }`, converting attachment writes from `Unrestricted` to grant-scoped — closing a latent authz gap. Bumped `relicario-server` to 0.1.1; `docs/SECURITY.md` documents the required pre-receive hook redeploy.
Result: `relicario org add`/`edit` now reach **all 7 item types** (Login, Secure Note, Identity, Card, Key, TOTP, Document); org attachments are collection-scoped on disk and grant-enforced at the hook. The C↔D path contract held in the merge — Dev-C's `save_attachment` emitter (`attachments/{slug}/{item}/{att}.enc`) exactly matches Dev-D's `classify_path` authorization. **Deploy note:** the pre-receive hook must be rebuilt on the server for attachment writes to be grant-scoped in production.
**Still deferred — forward plan in `docs/superpowers/specs/2026-06-20-extension-cli-parity-gap-analysis.md`:** extension org **read** (Dev-D) and **write** (Plan B-2) — the extension has no org concept yet; org phase-2 (SSO/LDAP, read audit, per-collection subkeys, HTTP plane). That parity gap analysis is the authoritative forward plan for extension↔CLI parity (org read/write plus a cluster of personal-side extension gaps: favorites UI, group/tag/filter editing, attachment-remove router wire, per-item purge).
### Phase 2B — polish foundation + form layout (merged 2026-05-02, `5da1e52`)
Spec: `docs/superpowers/specs/2026-05-02-phase-2b-form-layout-design.md`
@@ -127,10 +114,10 @@ Item CRUD commands (B9B14): `org add` (`OrgAddKind`: Login/SecureNote/Identit
**A5 doc-fix** (`enforce_owner_only_elevation` parent-role close, `519e503`) and this living-docs sweep also landed.
**Tracked follow-ups:**
- `org add` / `org edit` parity for Card, Key, Document, Totp — ✅ **SHIPPED v0.8.1** (`4c0a289`; all 7 item types now supported)
- Extension org-vault switch + read parity (Dev-D) — still deferred; forward plan in the parity gap analysis
- Extension org write operations — still deferred (Plan B-2)
**Tracked follow-ups (deferred, not shipped):**
- `org add` / `org edit` parity for Card, SshKey, Document, Totp item types (Login/SecureNote/Identity only today; `get`/`list` can display all types if present)
- Extension org-vault switch + read parity (Dev-D deferred)
- Extension org write operations
- Phase 2: SSO/LDAP federation, read audit log, per-collection subkeys (true cryptographic scope separation), HTTP management plane
**Known limitations (by design in phase 1):** shared org master key — reads are not cryptographically scoped per collection (hook scopes writes; client filters manifest); no read audit (git records writes only); `delete-org` is a local tombstone only (hook rejects protected-file deletion on push).
@@ -182,10 +169,10 @@ Per the 2026-05-30 post-v0.6.0 audit of the three 2026-05-04 architecture-review
**Enterprise org vault** — ✅ **COMPLETE (backend)** — all 19 CLI subcommands + core + server hook merged `7392795` 2026-06-20. Deferred follow-ups tracked in the landing section above.
Pending follow-ups (in rough priority order; **forward plan:** `docs/superpowers/specs/2026-06-20-extension-cli-parity-gap-analysis.md`):
- **Extension org parity — read** (Dev-D): org context switch + collection-filtered browse in the popup/vault tab
- **Extension org parity — write** (Plan B-2): `org add`/`edit`/`rm` from the extension — blocked behind extension org-read landing (and now unblocked on the CLI side, which reached all-7-type org write in v0.8.1)
- **Personal-side extension gaps** (from the parity analysis): favorites UI, group/tag editing on all type forms, popup type/tag filters, attachment-remove router wire + per-item purge UI, autofill registrable-domain matching
Pending org-vault follow-ups (in rough priority order):
- `org add`/`edit` parity for Card, SshKey, Document, Totp
- Extension org switch + read parity (Dev-D)
- Extension org write operations
- **Phase 4: command palette** — ⌘K global search + action dispatch across the vault tab (no spec yet)
Long-term: relay server, mobile. See `ROADMAP.md` for the longer arc and `CHANGELOG.md` for tagged-release history (the `v0.8.1` CHANGELOG entry + version bump are owned by the PM in this lift).
Long-term: relay server, mobile. See `ROADMAP.md` for the longer arc and `CHANGELOG.md` for tagged-release history (current head: `v0.6.0`; the `v0.7.0` entry covers extension-restructure completion).

View File

@@ -3,7 +3,7 @@
//! (`commands/org.rs`). Centralizing it keeps the two surfaces from drifting.
use std::collections::HashMap;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use zeroize::Zeroizing;
@@ -255,23 +255,39 @@ pub(crate) fn build_totp(
})))
}
/// Read a file and encrypt it as an attachment under `key`, deriving its display
/// metadata. The plaintext is held in a `Zeroizing` buffer so it is wiped after
/// encryption. Returns the encrypted blob plus (filename, mime_type, size).
pub(crate) fn encrypt_document_file(
path: &Path,
key: &Zeroizing<[u8; 32]>,
max_bytes: u64,
) -> Result<(EncryptedAttachment, String, String, u64)> {
use relicario_core::encrypt_attachment;
let bytes = Zeroizing::new(
std::fs::read(path).with_context(|| format!("failed to read {}", path.display()))?,
);
let enc = encrypt_attachment(&bytes, key, max_bytes)?;
let filename = path
.file_name()
.ok_or_else(|| anyhow::anyhow!("file path has no filename: {}", path.display()))?
.to_string_lossy()
.into_owned();
let mime_type = crate::parse::guess_mime(&filename);
Ok((enc, filename, mime_type, bytes.len() as u64))
}
pub(crate) fn build_document(
title: String, file: PathBuf, key: &Zeroizing<[u8; 32]>, max_bytes: u64,
) -> Result<(Item, EncryptedAttachment)> {
use relicario_core::item_types::DocumentCore;
use relicario_core::{encrypt_attachment, AttachmentRef};
let bytes = std::fs::read(&file).with_context(|| format!("failed to read {}", file.display()))?;
let enc = encrypt_attachment(&bytes, key, max_bytes)?;
let filename = file.file_name()
.ok_or_else(|| anyhow::anyhow!("file path has no filename: {}", file.display()))?
.to_string_lossy().into_owned();
let mime_type = crate::parse::guess_mime(&filename);
let primary_attachment = enc.id.clone();
use relicario_core::AttachmentRef;
let (enc, filename, mime_type, size) = encrypt_document_file(&file, key, max_bytes)?;
let mut item = Item::new(title, ItemCore::Document(DocumentCore {
filename: filename.clone(), mime_type: mime_type.clone(), primary_attachment: primary_attachment.clone(),
filename: filename.clone(), mime_type: mime_type.clone(), primary_attachment: enc.id.clone(),
}));
item.attachments.push(AttachmentRef {
id: primary_attachment, filename, mime_type, size: bytes.len() as u64, created: item.created,
id: enc.id.clone(), filename, mime_type, size, created: item.created,
});
Ok((item, enc))
}

View File

@@ -1038,25 +1038,18 @@ pub fn run_edit(dir: &Path, query: &str, totp_qr: Option<std::path::PathBuf>, fi
ItemCore::Key(k) => ib::edit_key(k, history)?,
ItemCore::Document(d) => {
if let Some(path) = &file {
let bytes = std::fs::read(path)
.with_context(|| format!("read {}", path.display()))?;
let enc = relicario_core::encrypt_attachment(
&bytes, vault.key(), crate::org_session::DEFAULT_ORG_ATTACHMENT_MAX_BYTES)?;
let (enc, filename, mime_type, size) = ib::encrypt_document_file(
path, vault.key(), crate::org_session::DEFAULT_ORG_ATTACHMENT_MAX_BYTES)?;
vault.remove_item_attachments(&collection, &id)?;
let rel = vault.save_attachment(&collection, &id, &enc)?;
let filename = path
.file_name()
.ok_or_else(|| anyhow::anyhow!("file path has no filename: {}", path.display()))?
.to_string_lossy()
.into_owned();
d.mime_type = crate::parse::guess_mime(&filename);
d.primary_attachment = enc.id.clone();
d.filename = filename.clone();
d.mime_type = mime_type.clone();
d.primary_attachment = enc.id.clone();
new_doc_attachments = Some(vec![relicario_core::AttachmentRef {
id: enc.id,
filename,
mime_type: d.mime_type.clone(),
size: bytes.len() as u64,
mime_type,
size,
created: now_unix(),
}]);
doc_attachment_rel = Some(rel);