85 lines
4.3 KiB
Markdown
85 lines
4.3 KiB
Markdown
|
|
---
|
||
|
|
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.
|