Fourth Phase 2 implementation D-day. Closes ADR 0007 § 10 acceptance
criterion #9 (bootstrap workflow must be reproducible without manual
file editing) by shipping a minimal keygen CLI per § 9.1. Phase 2
functional scope is complete with this D-day — remaining work is
Phase 2 close → v0.2.0 (maintainer-triggered, explicit per
CLAUDE.md release_kit.phase_close_trigger).
NEW bin/olp-keys.mjs (~250 lines): subcommand CLI
keygen [--owner|--name=X|--tier=guest|owner|--providers=csv|--force]
Creates a key + prints plaintext token to stdout ONCE; manifest
stores only SHA-256 hash. --force revokes existing owner keys
before creating the new owner (ADR § 9.3 recovery flow).
list [--owner-only|--include-revoked]
Lists keys with token_hash redacted.
revoke --id=<key-id>
Marks the key's revoked_at; idempotent (already-revoked → no-op
+ status message); missing id → exit 2.
Common flag: --olp-home=<path> overrides ~/.olp/ (defaults to
OLP_HOME env then ~/.olp/).
package.json bin field
"bin": { "olp-keys": "./bin/olp-keys.mjs" } so npx olp-keys ...
resolves. Also "scripts": { "olp-keys": "node bin/olp-keys.mjs" }
for npm run.
Module shape (testability)
Exports runCli(argv, { out, err }) so tests invoke with synthetic
argv + IO writers (no process spawn). Main guard auto-runs when
invoked as entrypoint.
Plaintext token discipline (ADR § 5 + § 9.1)
Plaintext printed exactly once on stdout. Never logged, never
written to manifest, never written to audit. Operators capture
immediately; lost → --force revoke + regenerate.
--force async correctness
cmdKeygen is async and awaits each revokeKey (which is async —
acquires per-key write lock per § 6.4). Sequence: revoke each
existing owner manifest atomically → then createKey for new
owner. Avoids race where create-new runs before revoke-old
completes.
TESTS — Suite 22, +20 (524 → 544):
22a-1..5: parseArgv unit (--flag=value, --flag value, boolean,
mixed positional)
22b-1..5: keygen (owner default, name+providers, missing-name
error, invalid-tier error, --force revoke-then-create with
isolation tmpdir)
22c-1..3: list (empty, populated with token_hash-redaction check,
--owner-only filter)
22d-1..4: revoke (valid id, idempotent re-revoke, missing-id
error, nonexistent-id error)
22e-1..3: top-level CLI (--help / no args / unknown subcommand
exit codes)
DOCUMENTATION:
- AGENTS.md: lib/keys.mjs marker promoted to ✅; new bin/olp-keys.mjs
entry. Implementation-status-note + shipped-set updated.
- README.md: Implementation Status row added for bin/olp-keys.mjs;
Known limitations note rewritten to "Phase 2 functional scope
complete; close pending"; new Bootstrap workflow section with
copy-pasteable npx commands + recovery flow.
- CHANGELOG.md: D47 entry under Unreleased per release_kit overlay.
AUTHORITY:
- ADR 0007 — § 5 token format, § 9.1 minimal keygen command
surface, § 9.3 recovery, § 10 acceptance criterion #9 covered.
- CLAUDE.md release_kit overlay phase_rolling_mode — under
Unreleased.
- Standing autopilot grant.
Verified: 544/544 pass via npm test (no regression in 524 pre-D47
tests; 20 new Suite 22 tests all green).
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>