--- type: plan title: "credvault Ruby gem — implementation plan" summary: Implementation plan for the credvault Ruby gem wrapping the bw CLI with ensure/get/rotate/list/status operations, Sandi Metz ports-and-adapters design, sync-before-read, global locking, and a Minitest strategy. tags: - type/plan - project/credvault - tool/vaultwarden - domain/credential-management - domain/ruby scope: project date: 2026-07-09 last_updated: 2026-07-09 related: - credvault-integration-master-plan - credvault-vaultwarden-setup-plan source: ruby-gems --- # credvault Gem — Implementation Plan Home: `~/dev/ruby-gems/credvault/` (private GitHub repo, use git-context:repo-init). Follows the planka-api sibling pattern: client layer + CLI, Minitest, small objects. ## Surface (frozen) Commands: `ensure`, `get`, `rotate`, `list`, `status`. **No delete, no export, no raw bw passthrough.** JSON output, stable error codes (`authentication_failed`, `vault_locked`, `collection_unavailable`, `credential_not_found`, `duplicate_managed_key`, `invalid_key`, `invalid_password_policy`, `bitwarden_command_failed`, `configuration_error`). Org/collection IDs come from `~/.config/credvault/config.yml` only — never CLI args. ## Object model (ports and adapters) ``` CredentialKey, Credential, PasswordPolicy # domain values Operations::{Ensure,Get,Rotate,List}Credential(s) # use cases, depend on Vault::Repository Vault::Repository (interface) ← Vault::BitwardenRepository Bitwarden::{Client, Session, ProcessRunner} # adapter; Open3.capture3 argv arrays only Presenters::JsonPresenter exe/credvault ``` ## Non-negotiable behaviors (from critique) 1. **`bw sync` before every read path** (ensure/get/rotate/list) — stale local cache otherwise breaks idempotency and creates duplicates. 2. **One global file lock serializes ALL bw invocations** (shared appdata dir is unsafe for concurrent bw processes), not just find-then-create. 3. **Custom-field lookup is client-side**: list collection items, parse JSON, filter on `managed_key` custom field (`bw list --search` doesn't cover custom fields). 4. **Duplicate `managed_key` → explicit error**, never arbitrary pick. Cross-host races are detected-and-reported, not prevented. 5. **Auth lifecycle fully owned**: login-state persisted in `BITWARDENCLI_APPDATA_DIR`; `unlock --passwordfile` per invocation; `lock` on exit; BW_SESSION only in process memory, never in logs/errors/output. Master-password file is mode-600 (owned by the invoking user; OS isolation is a future hardening option, not v1). 6. **Rotation is vault-only and says so**: output must remind the caller that the live app still holds the old secret (two-phase workflow is the caller's job). v1 may refuse rotation without `--acknowledge-live-impact` style flag. 7. **Audit log** (JSONL, `~/.local/state/credvault/audit.jsonl`): timestamp, operation, key, result, item_id. Every `get` is logged. Never any secret material. 8. Password generation via `bw generate` or Ruby SecureRandom under named policies (`strong` 32 full-charset default, `compatibility` no-special, `passphrase` 5 words). ## Testing (Minitest) - **Unit**: fake Vault::Repository — ensure returns existing / creates missing / rejects duplicates; rotate fails on missing key and preserves metadata; list excludes secrets; no auth material in any output/error. - **Adapter**: fake ProcessRunner with canned bw JSON (login, unlock, sync, list, create, edit, lock) — primary seam, matches planka-api pattern. - **High-fidelity**: a fake `bw` shell script on PATH to exercise real Open3/argv/exit codes. - **Integration** (optional, tagged): disposable Vaultwarden container. Never production. ## Build order & delegation 1. Skeleton + config + error model + ProcessRunner/Client/Session (`status` end-to-end) — one sonnet agent. Blocker for the rest: defines the interfaces. 2. **In parallel** (disjoint operations against slice-1 interfaces, merged before review): - `get` + `list` (read paths, sync + lock + client-side filter) — sonnet. - `ensure` + `rotate` + audit log + policies — sonnet. 3. Security/quality pass — opus review + /code-review before first production use. Each slice ships with its tests green; design decisions are all above, so agents don't improvise.