Compare commits
10 Commits
feature/v0
...
v0.8.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fa4d6824c | ||
|
|
783e3493f0 | ||
|
|
4cca9b465c | ||
|
|
5be3043ab5 | ||
|
|
cf89bf8ca4 | ||
|
|
a91ceea0ed | ||
|
|
415d8ed9ef | ||
|
|
b54aaea239 | ||
|
|
4c0a289acb | ||
|
|
c5b1917eb0 |
51
CHANGELOG.md
51
CHANGELOG.md
@@ -1,5 +1,56 @@
|
||||
# Changelog
|
||||
|
||||
## v0.8.1 — 2026-06-20 — org item-type parity + collection-scoped attachments
|
||||
|
||||
Brings `relicario org add` / `relicario org edit` to **full item-type parity** with the
|
||||
personal vault: the org surface now supports **all 7 item types** (previously Login /
|
||||
SecureNote / Identity only), adds collection-scoped attachment storage for Document
|
||||
items, and grant-scopes attachment write paths in the pre-receive hook — closing a latent
|
||||
authorization gap. Secrets are entered via interactive prompts by default, with `--*-stdin`
|
||||
escape hatches for non-interactive scripting. Tracked under
|
||||
`docs/superpowers/plans/2026-06-20-relicario-v0.8.1-parity.md`.
|
||||
|
||||
> **⚠️ Coordinated server redeploy required.** The `relicario-server` pre-receive hook
|
||||
> (now `0.1.1`) must be rebuilt and redeployed for attachment writes to be grant-scoped in
|
||||
> production. Until the updated hook is installed, `attachments/…` pushes remain
|
||||
> `Unrestricted` (gated only by the per-commit member-signature check).
|
||||
|
||||
### Added
|
||||
- **Shared `item_build` CLI module** (`crates/relicario-cli/src/commands/item_build.rs`):
|
||||
centralizes per-type secret resolution, item builders (`build_*`), and interactive edit
|
||||
helpers (`edit_*`) consumed by **both** the personal and org command surfaces, eliminating
|
||||
the prior personal↔org builder duplication.
|
||||
- **Org `add` / `edit` parity for Card, Key, TOTP, and Document** — `relicario org add` now
|
||||
creates all 7 item types; `relicario org edit` is interactive per-type ("blank to keep",
|
||||
field-history capture) instead of flat flags.
|
||||
- **`--*-stdin` secret flags** on personal and org `add` for non-interactive entry of
|
||||
passwords, card number/CVV/PIN, key material, TOTP secrets, and note bodies.
|
||||
- **Collection-scoped org attachment storage** (`crates/relicario-cli/src/org_session.rs`):
|
||||
attachments stored at `attachments/<slug>/<item-id>/<att-id>.enc` with a default
|
||||
per-attachment cap (10 MiB, mirroring the personal default at
|
||||
`crates/relicario-core/src/settings.rs`). `org add document --file`, `org edit --file`
|
||||
(replace), and `org purge` (removes the item's attachment directory) round-trip with
|
||||
git-status-clean staging.
|
||||
|
||||
### Security
|
||||
- **Grant-scoped attachment writes** (`relicario-server` `0.1.1`): `classify_path` now
|
||||
recognizes `attachments/<slug>/<item-id>/<att-id>.enc` (exactly 3 path segments, `.`-free
|
||||
slug guard) as `Item { collection }`, bringing attachment writes under the same grant +
|
||||
slug-existence check as `items/` blobs. Previously such paths fell through to
|
||||
`Unrestricted`. The Document source plaintext is read into a `Zeroizing` buffer and wiped
|
||||
after encryption. See `docs/SECURITY.md`.
|
||||
|
||||
### Changed
|
||||
- Personal `add secure-note` `--body-prompt` flag renamed to `--body-stdin` (unified
|
||||
multiline-secret model).
|
||||
|
||||
### Docs
|
||||
- Updated cli `ARCHITECTURE.md`, `docs/FORMATS.md` (org attachment layout + cap citation),
|
||||
`docs/SECURITY.md`, `STATUS.md`, and `ROADMAP.md`. New
|
||||
`docs/superpowers/specs/2026-06-20-extension-cli-parity-gap-analysis.md` is the forward
|
||||
plan for extension↔CLI parity (org read/write plus a cluster of personal-side extension
|
||||
gaps). End-user `user_docs/` guide lands as a fast-follow.
|
||||
|
||||
## v0.8.0 — 2026-06-20 — enterprise org vault
|
||||
|
||||
Git-native multi-user **org vaults**: a separate org git repository alongside each
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2156,7 +2156,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "relicario-cli"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arboard",
|
||||
@@ -2188,7 +2188,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "relicario-core"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"base64",
|
||||
@@ -2235,7 +2235,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "relicario-wasm"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"ed25519-dalek",
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
| Version | Highlights |
|
||||
|---|---|
|
||||
| *(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.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. |
|
||||
| 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-α/β₁/β₂) |
|
||||
@@ -16,11 +17,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; enterprise org vault backend is shipped (2026-06-20). Pending items in rough priority order:
|
||||
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:
|
||||
|
||||
- **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
|
||||
- **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)
|
||||
- **Phase 4: command palette** — ⌘K global search + action dispatch across the vault tab (no spec yet)
|
||||
|
||||
## Medium-term
|
||||
|
||||
33
STATUS.md
33
STATUS.md
@@ -5,10 +5,23 @@
|
||||
## 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:** **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.
|
||||
**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.
|
||||
|
||||
## 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`
|
||||
@@ -114,10 +127,10 @@ Item CRUD commands (B9–B14): `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 (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
|
||||
**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)
|
||||
- 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).
|
||||
@@ -169,10 +182,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 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
|
||||
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
|
||||
- **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 (current head: `v0.6.0`; the `v0.7.0` entry covers extension-restructure completion).
|
||||
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).
|
||||
|
||||
@@ -24,7 +24,10 @@ under `src/commands/`. Each source file has one job.
|
||||
- **`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
|
||||
`DeviceAction`, `RecoveryQrCmd`), plus the org clap surface `OrgCommands`
|
||||
(`main.rs:448`) and `OrgAddKind` (`main.rs:556`) — the latter's Card / Key /
|
||||
Document / Totp variants carry `--collection` and the `--*-stdin` secret flags.
|
||||
`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
|
||||
@@ -94,7 +97,14 @@ under `src/commands/`. Each source file has one job.
|
||||
(`items/<collection-slug>/<id>.enc` — the leading slug is what the pre-receive
|
||||
hook authorizes against, never decrypting), fingerprint-based member matching
|
||||
(`relicario_core::fingerprint`, tolerant of OpenSSH whitespace/comment
|
||||
differences), `atomic_write`, and `org_git_run`. Note `org_git_run` runs
|
||||
differences), `atomic_write`, and `org_git_run`. As of v0.8.1 it also owns
|
||||
**collection-scoped attachment storage** — `attachment_path` /
|
||||
`save_attachment` / `load_attachment` / `remove_item_attachments`
|
||||
(`org_session.rs:125-157`) at layout
|
||||
`attachments/<collection-slug>/<item-id>/<att-id>.enc` (the same leading slug
|
||||
the pre-receive hook authorizes against as for `item_path`), capped
|
||||
per-attachment by `DEFAULT_ORG_ATTACHMENT_MAX_BYTES` (10 MiB,
|
||||
`org_session.rs:20`). Note `org_git_run` runs
|
||||
**bare git** — unlike `helpers::git_run` it does NOT inject
|
||||
`commit.gpgsign=false`, because org commits MUST be signed (the hook verifies
|
||||
every commit's signature); signing config is established by
|
||||
@@ -111,19 +121,38 @@ under `src/commands/`. Each source file has one job.
|
||||
concurrent-rotation abort), `transfer-ownership`, `delete-org`, `status` /
|
||||
`audit` (verified-signer attribution + `TAMPERED` flag).
|
||||
|
||||
*Item CRUD (7):* `org add` creates typed items via `OrgAddKind`
|
||||
(`commands/org.rs:749`) — **Login / SecureNote / Identity only**; Card /
|
||||
SshKey / Document / Totp creation is a deferred follow-up. `get` / `list` can
|
||||
display any item type if present. `org get <query> [--show]` masks secrets
|
||||
unless `--show`; `org list [--trashed]` filters by the caller's collection
|
||||
grants; `org edit <query>` is flag-driven (blank flags keep current values);
|
||||
`org rm` soft-deletes, `org restore` undoes, `org purge` permanently removes
|
||||
the encrypted blob. All item ops are collection-scoped and grant-enforced. The
|
||||
audit trail emits `item-create` / `item-update` / `item-delete` /
|
||||
`item-restore` / `item-purge`.
|
||||
*Item CRUD (7):* full item-type parity with the personal vault (v0.8.1).
|
||||
`org add` creates **all seven types** (Login / SecureNote / Identity / Card /
|
||||
Key / Document / Totp) via `OrgAddKind` (`commands/org.rs:751`); each arm
|
||||
delegates to the shared `item_build::build_*` builders through `build_org_item`
|
||||
(`commands/org.rs:799`), and `run_add` (`commands/org.rs:823`) sets tags
|
||||
post-build. Document is special-cased in `run_add` (`commands/org.rs:839`): its
|
||||
builder also yields an `EncryptedAttachment` that is written via
|
||||
`save_attachment` and git-staged before the signed commit. Single-line secrets
|
||||
(card number/CVV/PIN, TOTP secret, login password) accept a `--*-stdin` flag;
|
||||
multiline secrets (Key material, SecureNote body) read stdin to EOF — the same
|
||||
`resolve_secret_line` / `resolve_secret_multiline` convention as personal `add`
|
||||
(`commands/item_build.rs`).
|
||||
|
||||
Deferred: Card / SshKey / Document / Totp `org add` / `edit` parity;
|
||||
extension org reads and writes (Dev-D).
|
||||
`org edit <query>` (`run_edit`, `commands/org.rs:1004`) is **interactive
|
||||
per-type** as of v0.8.1 (it was flag-driven before): it prompts Title, then
|
||||
dispatches on `&mut item.core` to the shared `item_build::edit_*` helpers
|
||||
("blank keeps current", field-history capture via `push_history`), mirroring
|
||||
personal `cmd_edit`. `--totp-qr` sets a Login TOTP from a QR image; `--file`
|
||||
replaces a Document's primary attachment (`commands/org.rs:1039`, rejected for
|
||||
non-Document items at `commands/org.rs:1018`). The edit commit carries
|
||||
`Relicario-Action: item-update`.
|
||||
|
||||
`org get <query> [--show]` masks every secret unless `--show`; `org list
|
||||
[--trashed]` filters by the caller's collection grants; `org rm` soft-deletes,
|
||||
`org restore` undoes, `org purge` (`run_purge`, `commands/org.rs:1164`)
|
||||
permanently removes the encrypted blob **and** the item's attachment directory
|
||||
(`remove_item_attachments`, `commands/org.rs:1173`). All item ops are
|
||||
collection-scoped and grant-enforced (`filter_for_member` over the manifest +
|
||||
`ensure_grant` before any load/mutate). The audit trail emits `item-create` /
|
||||
`item-update` / `item-delete` / `item-restore` / `item-purge`.
|
||||
|
||||
Deferred: extension org reads and writes (Plan B-2 / phase 2).
|
||||
|
||||
- **`src/helpers.rs`** (`helpers.rs:1-101`) — pure, no-state plumbing:
|
||||
`find_vault_dir_from` (`helpers.rs:14-28`) walks up parent directories
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "relicario-cli"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
edition = "2021"
|
||||
description = "CLI for relicario password manager"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "relicario-core"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
edition = "2021"
|
||||
description = "Core library for relicario password manager"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "relicario-wasm"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
edition = "2021"
|
||||
description = "WASM bindings for relicario password manager"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
# Extension ↔ CLI Parity Gap Analysis
|
||||
|
||||
- **Date:** 2026-06-20
|
||||
- **Author:** Dev-D, reconciled against an independent PM parity sweep
|
||||
- **Status:** Draft for review — **forward-planning**, NOT v0.8.1 scope
|
||||
- **Anchor commit:** `origin/main` `b09e0ce` (v0.8.0 org vault + v0.8.1 Dev-A foundation merged; Dev-B/C/D in flight)
|
||||
- **Scope note:** This plans a *future* milestone. Extension org **writes** remain explicitly out of scope for v0.8.1 per `docs/superpowers/specs/2026-06-20-relicario-v0.8.1-parity.md` (Plan B-2).
|
||||
|
||||
## Purpose
|
||||
|
||||
Survey the gap between the Relicario **CLI** (`relicario`) and the **browser extension**, classify every gap as a *real parity gap*, an *intended CLI-only* capability, or *already-planned-in-a-spec*, and produce a prioritized work list (with rough sizing) to bring the extension up to CLI parity. The driver is the project's **CLI/extension parity philosophy**: features should not ship "CLI-first, extension-later" without an explicit, recorded decision — this doc is that record for the current backlog.
|
||||
|
||||
## Method
|
||||
|
||||
Two **independent** surveys were run and then reconciled:
|
||||
|
||||
- **PM sweep** — 3 inventory agents + synthesis.
|
||||
- **Dev-D sweep** — 4 parallel readers (CLI / extension-UI / extension-SW / specs+roadmap) → synthesis → an adversarial completeness/accuracy critic, all reading from a worktree pinned at `b09e0ce`.
|
||||
|
||||
The two sweeps were deliberately blind to each other. All load-bearing claims in this document were **hand-verified against source** (greps + line reads); where the two sweeps disagree, the disagreement is flagged explicitly in §Reconciliation. Line citations are point-in-time against `b09e0ce` and may drift.
|
||||
|
||||
## Executive summary
|
||||
|
||||
Core item-CRUD parity is **excellent**. All 7 item types (Login, Secure Note, Identity, Card, Key, TOTP, Document) and the add / edit / view / list / trash / restore lifecycle are at full parity, and in several places the **extension is the richer surface** (live TOTP codes, custom fields/sections, TOTP-from-QR, password coloring, session auto-lock, autofill/capture). Where a *per-type* gap exists it is most often on the **CLI** side, not the extension's.
|
||||
|
||||
The genuine **extension-side** gaps cluster into three buckets:
|
||||
|
||||
1. **Metadata-management gaps (the headline finding):** editing **groups**, **tags**, and **filtering** is wired into only specific forms/surfaces in the extension, while the CLI offers them uniformly across all types; **favorites** has *zero* extension UI (strictly worse than the CLI). These are real, currently-shipping parity gaps on the *personal* vault.
|
||||
2. **Backend-exists-but-no-wire/UI:** attachment **removal** (`removeAttachmentsFromItem` helper exists, no `remove_attachment` router message), **per-item purge** (`purge_item` handler exists, only a bulk "empty trash" UI), and the `isInTab()` popup-mode gate that hides login/secure-note attachment editing in the popup window.
|
||||
3. **The org (enterprise) vault** — the single largest gap. The entire org feature (shipped CLI-only in v0.8.0) has **no extension presence** (no org routes, no org context). This is fully specced and explicitly deferred (Dev-D org-read / Plan B-2 org-write).
|
||||
|
||||
Plus one quality gap on the personal side surfaced by the PM sweep: **autofill hostname matching** is a naive exact-equality match.
|
||||
|
||||
Intentionally **CLI-only by design** (not gaps): real `git pull`/`push` and `.git`-history backup bundling (the extension writes straight to the host Contents API and keeps no local repo), the `imgsecret embed` recovery subcommand, recovery-QR's deliberate no-file-write contract, org **admin** (members/collections/grants/rotate/audit), and shell completions.
|
||||
|
||||
## Reconciliation with the PM sweep
|
||||
|
||||
The PM sweep concluded: extension at *near-full parity* on the personal surface, ahead in places, with the **org vault as the one material gap** and **autofill hostname matching as the only personal-side quality gap**.
|
||||
|
||||
**Agreements (both sweeps, independently):**
|
||||
- Org vault is the largest gap; it is fully specced and deferred (Dev-D read / Plan B-2 write).
|
||||
- The extension leads on live TOTP, custom fields/sections, password coloring.
|
||||
- The intended-CLI-only set: git sync/push, `.git` backup bundling, device-key deploy-key plumbing, org admin, shell completions.
|
||||
|
||||
**Dev-D refines / partially refutes the PM:** the personal surface is **not** "near-full parity with autofill as the only gap." There is a real cluster of **personal-side extension gaps** the PM sweep understated:
|
||||
- **Favorites — none in the extension** (`favorite` only round-trips through save fns; no toggle, no star in lists, no filter). The CLI is itself only add-only, so the extension is *strictly worse*. The PM hypothesis did not list this.
|
||||
- **Group editing — Login-form only** (`f-group` + `wireGroupAutocomplete` live in `login.ts` only; card/key/identity/totp/document forms pass `group` through without an input).
|
||||
- **Tag editing — Document-form only** (`f-tags` in `document.ts` only; other forms preserve-but-don't-edit).
|
||||
- **Filter — popup has no type filter** (vault-tab only) and **no tag filter** anywhere.
|
||||
- **Per-item purge** and **attachment add/remove** have working backends but no popup-reachable UI / no router wire.
|
||||
|
||||
**PM caught, Dev-D's taxonomy missed:** **autofill hostname matching.** `service-worker/vault.ts` (`findByHostname`, equality at `:344`) matches credentials by exact `icon_hint` equality (`(e.icon_hint ?? '').toLowerCase() === hostname`) — no `www.` strip, no registrable-domain (eTLD+1) match, so `www.example.com` will not match an item stored as `example.com`. Confirmed; folded in as a real LOW-MED personal-side gap. (Dev-D's capability taxonomy centered on item-CRUD/features and under-weighted the content-script autofill path — the PM sweep is the reason it appears here.)
|
||||
|
||||
**Methodology correction (a Dev-D self-sweep error, struck here):** the Dev-D extension-SW inventory referred to a `messages.ts` "that does not exist at that path." **That is false** — the file exists at `extension/src/shared/messages.ts` (227 lines): it holds the `PopupMessage` union (with `delete_item // soft-delete` at line 23), `POPUP_ONLY_TYPES` (line 168), and `CONTENT_CALLABLE_TYPES` (line 224). The inventory had merely dropped the `shared/` directory prefix. The substantive findings it supported (the unwired `searchItems`/`removeAttachmentsFromItem` helpers) are independently verified correct; the "file doesn't exist" caveat is removed from this document.
|
||||
|
||||
## Parity matrix
|
||||
|
||||
Support: **full** / **partial** / **none** / **n/a**. `gap_class`: **at-parity** · **real-gap** (extension work) · **real-gap (CLI-side)** (extension already ahead; CLI backlog) · **cli-only-by-design** · **already-planned**.
|
||||
|
||||
### Item types
|
||||
|
||||
| Capability | CLI | Ext | gap_class | Notes (evidence @ `b09e0ce`) |
|
||||
|---|---|---|---|---|
|
||||
| Login: create/view/edit | full | full | at-parity | CLI `add/get/edit login`; ext form + `add_item`/`update_item`/`get_item`. |
|
||||
| Secure Note: create/view/edit | full | full | at-parity | Both complete. |
|
||||
| Identity: create | full | full | at-parity | Both; ext also exposes `address`. |
|
||||
| Identity: view | full | full | at-parity | Both. |
|
||||
| Identity: edit | partial | full | real-gap (CLI-side) | CLI `edit_identity` omits `date_of_birth` + records no history; ext edits all. CLI backlog. |
|
||||
| Card: create/view | full | full | at-parity | Both. |
|
||||
| Card: edit | partial | full | real-gap (CLI-side) | CLI `edit_card` = holder+number only (no CVV/PIN/expiry/kind); ext edits all. CLI backlog. |
|
||||
| Key: create/view | full | full | at-parity | Both; ext takes `public_key` interactively. |
|
||||
| Key: edit | partial | full | real-gap (CLI-side) | CLI `edit_key` = key-material only (no label/algorithm/public_key); ext edits all. CLI backlog. |
|
||||
| TOTP: create | full | full | at-parity | Both; ext adds Steam Guard kind. |
|
||||
| TOTP: view | partial | full | real-gap (CLI-side) | CLI shows metadata only; ext shows live rotating code. See "TOTP live code". |
|
||||
| TOTP: edit | full | full | at-parity | Both. |
|
||||
| Document: create | full | full | at-parity | CLI encrypts file as attachment; ext `upload_attachment`. |
|
||||
| Document: view | partial | full | real-gap (CLI-side) | CLI metadata + `extract`; ext inline image preview. CLI backlog. |
|
||||
| Document: edit | none | full | real-gap (CLI-side) | CLI `edit` on Document is a no-op redirect to attach/extract; ext changes primary/supplementary files. CLI backlog. |
|
||||
|
||||
### Operations
|
||||
|
||||
| Capability | CLI | Ext | gap_class | Notes |
|
||||
|---|---|---|---|---|
|
||||
| add / edit / get / list | full | full | at-parity | All 7 types both surfaces. |
|
||||
| rm / soft-delete | full | full | at-parity | CLI `rm`; ext `delete_item` (`messages.ts:23`, handler `popup-only.ts`). |
|
||||
| trash (list) | full | full | at-parity | CLI `trash`; ext trash view. |
|
||||
| restore from trash | full | full | at-parity | CLI `restore`; ext `restore_item`. |
|
||||
| purge (permanent) | full | partial | **real-gap** | Ext UI only bulk "empty trash" (`purge_all_trash`, `popup-only.ts:420`); **no per-item purge UI**, though `purge_item` handler exists (`popup-only.ts:409`). CLI has single + bulk. |
|
||||
|
||||
### Features
|
||||
|
||||
| Capability | CLI | Ext | gap_class | Notes |
|
||||
|---|---|---|---|---|
|
||||
| Attachments: add | full | partial | **real-gap** | Login/secure-note attachment editing gated behind `isInTab()` (`login.ts:370,388`; `secure-note.ts:123,140`) — unavailable in popup window; document renders unconditionally (`document.ts`). SW `upload_attachment` is full. |
|
||||
| Attachments: view/download | full | full | at-parity | CLI `extract`; ext download + `download_attachment`. |
|
||||
| Attachments: remove | full | partial | **real-gap** | SW helper `removeAttachmentsFromItem` (`vault.ts:492`) has **no router wire** (`remove_attachment` absent — confirmed). UI removes refs at form-save only, with the same `isInTab()` caveat. CLI `detach` is full. |
|
||||
| TOTP: live code | none | full | real-gap (CLI-side) | CLI reveals raw base32 only; ext computes live codes. Extension leads. No spec mandates CLI OTP. |
|
||||
| Generator: password / passphrase | full | full | at-parity | CLI `generate`; ext generator-panel + `generate_password`. |
|
||||
| Settings: view / edit | full | full | at-parity | CLI `settings`; ext `get/set_vault_settings`. |
|
||||
| Search | partial | partial | at-parity | CLI: title-substring. Ext: client-side over title/group/tags/icon_hint; SW `searchItems` (`vault.ts:316`) exists but **unwired** (no `search_items` message). Neither does field-value full-text. |
|
||||
| Filter | full | partial | **real-gap** | CLI `list` filters type/group/tag/trashed. Ext: type filter is **vault-tab-only**; popup has none; **no tag filter anywhere**. SW `list_items` filters by `group` only. |
|
||||
| Favorites | partial | **none** | **real-gap** | CLI add-only (`--favorite` on Login add, `*` in list; no toggle/filter). Ext: **zero UI** — `favorite` only round-trips. Ext strictly worse; needs a paired CLI+ext design. |
|
||||
| Tags | full | partial | **real-gap** | CLI full create+filter all types. Ext: only Document form edits tags (`f-tags` in `document.ts`); no tag chips in lists; no tag filter. SW round-trips tags. |
|
||||
| Groups/folders | full | partial | **real-gap** | CLI all types `--group`, `list --group`. Ext: only Login form has `f-group`+autocomplete; other forms set no group; vault-tab "group" filter is actually a type filter. SW `list_groups`/group-filter full. |
|
||||
| Field history (view) | full | full | at-parity | CLI `history`; ext `get_field_history`. |
|
||||
| Custom fields / sections | none | full | real-gap (CLI-side) | CLI has no custom-field/section commands (core supports them); ext `renderSectionsEditor` covers all 7 types. CLI backlog. |
|
||||
| Autofill hostname matching | n/a | partial | **real-gap** | Ext-only feature; matcher (`vault.ts` `findByHostname`, `:344`) is exact `icon_hint` equality — no `www.` strip / eTLD+1. `www.x.com` ≠ `x.com`. (PM-surfaced.) |
|
||||
|
||||
### Org (enterprise) vault
|
||||
|
||||
| Capability | CLI | Ext | gap_class | Notes |
|
||||
|---|---|---|---|---|
|
||||
| Org: read items | full | none | already-planned | CLI `org get/list` grant-filtered, all 7 types. Ext has zero org code. Planned Dev-D. Spec: `2026-06-06-relicario-enterprise-org-vault-design.md` § Extension — Org Context; ROADMAP "Extension org parity — read". |
|
||||
| Org: write (add/edit/rm) | partial | none | already-planned | CLI write = Login/SecureNote/Identity only (`OrgAddKind`, `main.rs:560`; Card/Key/Document/Totp absent — v0.8.1 lift in flight). Ext none. Planned Plan B-2. Spec: `2026-06-20-relicario-v0.8.1-parity.md` § Out of scope. |
|
||||
| Org: member/collection mgmt | full | none | cli-only-by-design | CLI full lifecycle (~19 subcommands). Ext none — org **admin** is intended CLI-only (high-trust, low-frequency). |
|
||||
|
||||
### Vault lifecycle / infra
|
||||
|
||||
| Capability | CLI | Ext | gap_class | Notes |
|
||||
|---|---|---|---|---|
|
||||
| Vault init / setup | full | full | at-parity | CLI `init`; ext setup wizard + `create_vault`/`attach_vault`. |
|
||||
| Git sync (pull/push) | full | partial | cli-only-by-design | CLI real `git pull --rebase`/`push`. Ext writes straight to host Contents API; no local graph (`ahead`/`behind` always 0). Functionally syncs; architecturally different by design (`extension/ARCHITECTURE.md`). |
|
||||
| Device management | full | full | at-parity | CLI `device`; ext `renderDevices` + SW device CRUD. (GitHub/GitLab deploy-key API is the deferred edge.) |
|
||||
| Backup / restore | full | full | at-parity | CLI `.relbak` + git-history bundling; ext `export/restore_backup`. `.git` bundling sub-aspect is cli-only-by-design (ext has no local repo). |
|
||||
| Import (LastPass) | partial | partial | at-parity | Both LastPass-CSV only; other importers deferred both surfaces by policy. |
|
||||
| Recovery QR | full | full | at-parity | CLI generate/unwrap; ext `generate/unwrap_recovery_qr`. Webcam scan deferred both. |
|
||||
| Standalone generate (no vault) | full | none | cli-only-by-design (low-confidence) | CLI `generate`/`rate` work outside a vault; ext generator is embedded in login form + settings (needs unlocked vault). A browser extension lacks the "no vault" generator use-case a shell has. No spec; flag if user demand appears. |
|
||||
|
||||
### Intended CLI-only (no taxonomy row; recorded so they are not re-litigated as gaps)
|
||||
|
||||
| Capability | gap_class | Notes / spec |
|
||||
|---|---|---|
|
||||
| Recovery-QR file-write (`--out`) | cli-only-by-design | Negative API contract — no surface writes the payload to disk; absence *is* the security property. `2026-05-01-recovery-qr-design.md`. |
|
||||
| Org delete-org push to remote | cli-only-by-design | Phase-1 delete-org is a local tombstone; pre-receive hook rejects protected-file deletion. Pushable delete-org is phase-2. `2026-06-06-...-design.md`. |
|
||||
| `imgsecret embed` subcommand | cli-only-by-design | CLI disaster-recovery tool; the extension setup wizard's image flow covers the equivalent. |
|
||||
| Password coloring (CLI TTY) | cli-only-by-design (inverted) | Ext shipped it (v0.5.1); CLI TTY parity deferred until demand. `2026-05-01-password-coloring-design.md` § Out of scope. |
|
||||
| Shell completions | cli-only-by-design | No extension analogue. |
|
||||
|
||||
## Gap classification summary
|
||||
|
||||
- **Real extension gaps (extension work closes them):** per-item purge UI; attachment add/remove UI + `remove_attachment` wire + `isInTab()` gate; popup type filter + tag filter; tag editing on all forms; group editing on all forms; favorites UI; autofill registrable-domain matching; **org read** (specced); **org write** (specced, behind CLI type parity).
|
||||
- **CLI-side gaps (extension already ahead — separate CLI backlog, NOT extension work):** Identity/Card/Key edit field coverage; Document view/edit; live TOTP code; custom fields/sections commands.
|
||||
- **Intended CLI-only (not gaps):** git pull/push, `.git` backup bundling, org admin, `imgsecret embed`, recovery-QR file-write, shell completions, standalone generate.
|
||||
- **Already planned / deferred:** org read (Dev-D), org write + org item-type breadth (Plan B-2), org attachments/multi-vault (behind org).
|
||||
|
||||
## Prioritized forward work (extension)
|
||||
|
||||
Only items where **extension work** closes the gap. CLI-side gaps and intended-CLI-only items are excluded.
|
||||
|
||||
| Pri | Item | Size | Why | Depends on |
|
||||
|---|---|---|---|---|
|
||||
| **P0** | **Attachment remove + un-gate popup:** wire a `remove_attachment` router message to `removeAttachmentsFromItem` (`vault.ts:492`); drop the `isInTab()` gate so login/secure-note attachment **add & remove** work in the popup. Closes *both* the add half (row "Attachments: add") and remove half. | M | Backend exists and is unreachable — highest value-per-effort. The popup-mode gate is a UX cliff (can't manage login attachments without popping out). | — |
|
||||
| **P0** | **Per-item purge UI:** surface the existing `purge_item` handler (`popup-only.ts:409`) as a per-row permanent-delete in the trash view (today only bulk `purge_all_trash`). | S | Pure UI wiring over an existing handler; CLI has single-item purge. | — |
|
||||
| **P1** | **Group editing on all type forms:** add `f-group` + `wireGroupAutocomplete` to card/key/identity/totp/document (Login-only today). | M | SW (`list_groups`, group filter) already full; replicate one existing form pattern across 5 forms. | — |
|
||||
| **P1** | **Tag editing on all type forms + tag chips in lists:** promote Document's `f-tags` to a shared affordance on all 7 forms. | M | SW round-trips tags fully; only Document edits them today. | — |
|
||||
| **P1** | **Filter parity:** add a type filter to the popup (vault-tab has it) and a tag filter to popup + vault tab; optionally push type/tag params into `list_items`. | M | CLI filters type/group/tag; ext type filter is fullscreen-only, tag filter absent. | Tag editing (so tags exist to filter on) |
|
||||
| **P2** | **Favorites (paired CLI + ext):** favorite toggle in detail/edit, favorites filter, star in list rows — and extend the CLI beyond add-only, to reach *true* parity per the parity philosophy. | M | Ext strictly worse than CLI (none vs partial); both surfaces weak. Write a short spec first. | — (spec TBD) |
|
||||
| **P2** | **Autofill registrable-domain matching:** replace exact `icon_hint` equality (`vault.ts` `findByHostname:344`) with `www.`-strip + eTLD+1 matching. | S–M | `www.x.com` ≠ `x.com` today; the one personal-side quality gap. | — |
|
||||
| **P2** | **Search wire-up (hardening):** expose `searchItems` (`vault.ts:316`) via a `search_items` message, or formally adopt client-side filtering and remove the dangling helper. | S | Functionally at-parity, but an unwired helper is dead-code drift. | — |
|
||||
| **P3** | **Org read in extension (Dev-D):** org context switcher + SW org handlers (unwrap org master key into a `Zeroizing` session handle) + grant-filtered manifest browse/read in popup + vault tab. | XL | Largest single gap; entire org feature is CLI-only in the extension. Specced, deferred. | — |
|
||||
| **P3** | **Org offline read-only indicator:** "org offline — writes disabled" banner when the git remote is unreachable in org context. | S | Spec-mandated UX. | Org read |
|
||||
| **P3** | **Org SW acceptance tests:** org context replaces personal manifest cleanly; org master key never in localStorage/IndexedDB; offline mode triggers on network error. | M | Spec-mandated coverage following the feature. | Org read |
|
||||
| **P3** | **Org write in extension (Plan B-2):** org add/edit/rm including Card/Key/Document/Totp. | XL | Closes org write + item breadth. Deferred past v0.8.1. | Org read **and** CLI reaching Card/Key/Document/Totp org-write parity (v0.8.1) |
|
||||
|
||||
## Caveats
|
||||
|
||||
- Line citations are point-in-time against `b09e0ce` and drift with edits.
|
||||
- This is a planning artifact, not a commitment; sizes are rough (S ≈ hours, M ≈ a day, L ≈ days, XL ≈ a multi-stream lift).
|
||||
- Two analytical errors caught during cross-check and corrected here: (1) the struck `messages.ts`-doesn't-exist claim (file exists at `extension/src/shared/messages.ts`); (2) a few inventory line numbers were off by single digits and have been replaced with hand-verified ones.
|
||||
Reference in New Issue
Block a user