4.3 KiB
4.3 KiB
| type | title | summary | tags | scope | date | last_updated | related | source | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| plan | credvault Ruby gem — implementation plan | 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. |
|
project | 2026-07-09 | 2026-07-09 |
|
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)
bw syncbefore every read path (ensure/get/rotate/list) — stale local cache otherwise breaks idempotency and creates duplicates.- One global file lock serializes ALL bw invocations (shared appdata dir is unsafe for concurrent bw processes), not just find-then-create.
- Custom-field lookup is client-side: list collection items, parse JSON, filter on
managed_keycustom field (bw list --searchdoesn't cover custom fields). - Duplicate
managed_key→ explicit error, never arbitrary pick. Cross-host races are detected-and-reported, not prevented. - Auth lifecycle fully owned: login-state persisted in
BITWARDENCLI_APPDATA_DIR;unlock --passwordfileper invocation;lockon 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). - 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-impactstyle flag. - Audit log (JSONL,
~/.local/state/credvault/audit.jsonl): timestamp, operation, key, result, item_id. Everygetis logged. Never any secret material. - Password generation via
bw generateor Ruby SecureRandom under named policies (strong32 full-charset default,compatibilityno-special,passphrase5 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
bwshell script on PATH to exercise real Open3/argv/exit codes. - Integration (optional, tagged): disposable Vaultwarden container. Never production.
Build order & delegation
- Skeleton + config + error model + ProcessRunner/Client/Session (
statusend-to-end) — one sonnet agent. Blocker for the rest: defines the interfaces. - 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.
- 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.