fix(cli): #16 wallet-optional + identity aliases#20
Conversation
05a7073 to
513d836
Compare
|
Codex review (via gstack Verdict — BLOCKED (1 × P1, 1 × P2)The patch introduces a user-visible CLI regression by removing the previously supported positional Full review comments:
— codex |
|
Codex re-review v3 — design-tradeoff reply (not a code change) Codex has now flagged the same issue three times from opposite angles:
The design space:
Chosen tradeoff (accept If a reviewer disagrees and wants real coexistence, the path is a manual-parser opt-in for |
… wallet Codex P2 finding on PR #18: `agentkeys revoke <my-own-wallet>` revoked the session on the backend but left the dead token cached locally; every subsequent command loaded the stale revoked token and failed. cmd_revoke's `Some(target_wallet_str)` branch now case-insensitively compares target_wallet against session.wallet. On match: call session_store::clear_session() and return a 'was your own session — re-pair' message (matching the no-arg self-revoke form's UX). Tests added (19 total CLI tests, +2 from previous): - cmd_revoke_with_own_wallet_clears_local_session — verifies the new path wipes the local file and emits the 'was your own session' acknowledgement. - cmd_revoke_with_other_wallet_keeps_local_session — counterpart: revoking someone else's wallet must NOT touch the caller's session. P2-2 (parallel-test HOME race) is tracked separately as issue #34 — needs serial_test crate added cross-PR (#18, #19, #20, #27 all affected). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per human design call on PR #20: ship the --agent flag form (option 1) and migrate every breaking script reference in our own docs. Files updated: - wiki/credential-usage.md — synopsis, examples, lifecycle, env-var mapping section all show the new form. Added a "breaking change" callout at the top with the sed migration recipe. Removed the stale "issue #15 limitation" blockquote (issue #15 is now fixed in PR #19). - wiki/key-security.md — the read escape-hatch + run preferred-usage examples updated. - wiki/data-classification.md — credential store flow diagram updated. - crates/agentkeys-cli/src/main.rs — top-level `long_about` (shown by `agentkeys --help`) now uses the --agent flag form across all examples and explicitly documents the omit-to-use-session-wallet behavior. - docs/contradictions.md §4.3 — corrected the resolution narrative (positional Option panics clap; --agent flag is the chosen disambiguation), documented the breaking change + migration sed. No code change in this commit; the actual --agent flag landed in efa1707 already. Test: cargo test -p agentkeys-cli -- --test-threads=1 → 19 passed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Human design call: option 1 (--agent flag) — script migration appliedDecision recorded: ship the Migration applied to our own docs in commit
Tests still green: The PR is unblocked from the codex stalemate. Closing the human design call. |
Combines the full rebase of fix/issue-16 onto main (post-#18 + #19 merges), with all conflicts resolved in a single clean commit. - `CredentialBackend::list_credentials` + `resolve_identity` trait methods live side-by-side; both `MockHttpClient` and `InProcessBackend` implement both. - `cmd_run` signature is `(ctx, agent: Option<&str>, env_overrides: &[String], cmd: &[String])`: - `agent` from #16 (defaults to session wallet when None) - `env_overrides` from #19 (HashMap cache + pre-flight validation) - `cmd_store` / `cmd_read` take `agent: Option<&str>`. - `cmd_revoke` takes `agent: Option<&str>` (signature kept from #18). - `main.rs` dispatches `Commands::{Store,Read,Run,Revoke} { agent, ... }` via `.as_deref()` since each carries `agent: Option<String>`. - clap derive `Run` has both `--agent` flag AND `--env KEY=SERVICE` flag. - Top-level `long_about` and all sub-command `long_about` strings show the new `--agent` flag form. - `wiki/credential-usage.md` / `wiki/key-security.md` / `wiki/data-classification.md` migrated to the new form with a "breaking change" callout + sed migration recipe. - `docs/contradictions.md` §4.3 resolution narrative corrected. - Integration + CLI tests: 30 cli + 6 core + 46 mock-server passing. Resolves issue #16 (wallet-optional + identity aliases) after the 3-pass codex stalemate landed on the `--agent` flag design (human call). Test: cargo test -p agentkeys-cli -p agentkeys-core -p agentkeys-mock-server -- --test-threads=1
776b405 to
a021453
Compare
… resolution All 3 codex findings on PR #22 addressed in-branch: **P2 — URL-encode `--parent` aliases** The old code built the query URL via `format!("{}/identity/resolve?identity_type=alias&identity_value={}", ...)` with no encoding. Aliases containing reserved characters (`+`, `&`, `%`, spaces) sent malformed requests or failed to parse. Switched to reqwest's `.query(&[...])` builder, which percent-encodes per RFC 3986. Same pattern as the fixes on PR #20 (aa0cc95) and PR #29 (7aa9e70). **P2 — `0x-`prefixed aliases misclassified as wallets** The old code treated any `--parent` value starting with `0x` as a literal wallet, but `cmd_link` allows aliases like `0x-office` / `0x+bar`. Added `looks_like_raw_wallet(s)` that demands strict `0x + 40 hex digits` before the literal fast-path triggers; everything else goes through `/identity/resolve`. 3 unit tests (accepts canonical hex, rejects 0x-hyphen aliases, rejects short/non-hex). **P3 — Eager `--parent` resolution** Resolution previously ran unconditionally at startup, so a transient backend-down would crash `agentkeys-daemon --session <token>` or `--recover --method passkey` even though those paths never use `parent_wallet`. Moved the call into `resolve_parent_if_set` and invoked only from the pair-flow and master-approval recover branches. The `--session` seam and the `--recover --method …` 2FA path now skip parent resolution entirely. Test: cargo test -p agentkeys-daemon -- --test-threads=1 unit: 3 passed; daemon_tests: 15 passed; pair_tests: 15 passed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… resolution All 3 codex findings on PR #22 addressed in-branch: **P2 — URL-encode `--parent` aliases** The old code built the query URL via `format!("{}/identity/resolve?identity_type=alias&identity_value={}", ...)` with no encoding. Aliases containing reserved characters (`+`, `&`, `%`, spaces) sent malformed requests or failed to parse. Switched to reqwest's `.query(&[...])` builder, which percent-encodes per RFC 3986. Same pattern as the fixes on PR #20 (aa0cc95) and PR #29 (7aa9e70). **P2 — `0x-`prefixed aliases misclassified as wallets** The old code treated any `--parent` value starting with `0x` as a literal wallet, but `cmd_link` allows aliases like `0x-office` / `0x+bar`. Added `looks_like_raw_wallet(s)` that demands strict `0x + 40 hex digits` before the literal fast-path triggers; everything else goes through `/identity/resolve`. 3 unit tests (accepts canonical hex, rejects 0x-hyphen aliases, rejects short/non-hex). **P3 — Eager `--parent` resolution** Resolution previously ran unconditionally at startup, so a transient backend-down would crash `agentkeys-daemon --session <token>` or `--recover --method passkey` even though those paths never use `parent_wallet`. Moved the call into `resolve_parent_if_set` and invoked only from the pair-flow and master-approval recover branches. The `--session` seam and the `--recover --method …` 2FA path now skip parent resolution entirely. Test: cargo test -p agentkeys-daemon -- --test-threads=1 unit: 3 passed; daemon_tests: 15 passed; pair_tests: 15 passed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Post-rebase review (claude code-reviewer) flagged 3 P1 issues that map
directly to patterns in `.github/REVIEW_GUIDELINES.md`. All fixed in
this commit plus one P2 nit that was cheap.
**P1 — URL encoding via reqwest `.query()` on scope + list_credentials**
`get_scope` and `list_credentials` in `mock_client.rs` built queries with
raw `format!()` interpolation. Wallet strings with reserved characters
(`&`, `#`, `%`, `+`, spaces) would break the request or smuggle extra
params server-side. Switched both to `.get(self.url("/path")).query(&[...])`
so reqwest percent-encodes per RFC 3986. Same pattern as the fix on PR
#20 `aa0cc95` / PR #22 `ffdc908` / PR #29 `7aa9e70`.
**P1 — Case-insensitive self-target check in `update_scope`**
The self-target reject `session.wallet_address == target_wallet` was
string-equal, but the backend stores wallets in lowercase while EIP-55
checksummed input preserves case. A master passing its own wallet in
mixed-case form would bypass the self-target guard and silently
restrict its own scope (the exact failure the guard was designed to
prevent). Switched to `eq_ignore_ascii_case` matching the pattern
established in PR #18's self-revoke detection.
**P1 — Missing DENIED audit rows on scope endpoints**
`update_scope` and `get_session_scope` returned 403 on ownership
failure without inserting an audit row, breaking the
cross-agent-probing-visibility invariant established for
`list_credentials` / `read_credential` in PR #19. Added `'scope_update'
'DENIED'` and `'scope_read' 'DENIED'` inserts before the 403 return
in both handlers.
**P2 — `--add foo --remove foo` would no-op silently**
`cmd_scope` accepted combinations like `--add X --remove X`. The add
loop would push, then `retain(!remove.contains)` would cancel, and the
backend PUT would still fire with the original scope + misleading
"Scope updated" output. Added an up-front `add ∩ remove` check that
lists overlapping services and rejects.
**P2 — Narrow UPDATE to most-recent active session**
Write-side `update_scope` UPDATEd ALL active sessions for the target
wallet; read-side `get_session_scope` always returned the scope from
the most-recent session via `ORDER BY created_at DESC LIMIT 1`.
Read/write contract mismatched on wallets with multiple active
sessions (paired + recovered). Narrowed the UPDATE to use the same
ORDER/LIMIT subquery.
Test: cargo test -p agentkeys-core -p agentkeys-cli -p agentkeys-mock-server -- --test-threads=1
core: 22 passed; cli: 35 passed; mock-server: 56 passed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): #15 part 3 (fix-15b) — agentkeys scope command Closes part 3 of #15. Adds `agentkeys scope <AGENT>` subcommand with `--add`/`--remove`/`--set`/`--list` for editing a child agent's scope. Under the hood: new trait methods `CredentialBackend::get_scope` + `update_scope` backed by a new mock-server `PUT /session/scope` endpoint (owner-checked, updates all active sessions for the target wallet). Tests: 5 new CLI integration tests (list/add/remove/set/conflict). 62 total tests across cli/core/mock-server, all green under --test-threads=1. Minor: CommandContext::load_session made pub so integration tests can access the current session token. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli/mock-server): #15b v2 — address codex P1+P2 Codex P1: update_scope handler allowed a master session to target its own wallet, silently writing a restrictive scope_json onto the master's session row. Subsequent credential/read calls would start failing for services outside the accidental scope. Now rejects self-targeting with a clear bad_request. Codex P2 (URL encoding): cmd_scope's identity resolution built the /identity/resolve query string via raw interpolation, breaking aliases/emails with reserved characters ('+', '&', '=', '%', spaces). Switched to reqwest's .query() builder which percent-encodes per RFC. Codex P3 (--list exclusivity): --list now rejects combination with --add/--remove/--set up front instead of silently dropping the mutation. Deferred (tracked as follow-up): CBOR vs JSON parsing of ScopeChange request_details in mint_scope_change_session — the scope CLI uses the direct /session/scope endpoint, not the auth-request flow, so this finding doesn't affect the CLI path. Will be addressed when AuthRequest::ScopeChange approval is wired end-to-end. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli/mock-server): #29 v3 — claude P1+P2 review fixes after rebase Post-rebase review (claude code-reviewer) flagged 3 P1 issues that map directly to patterns in `.github/REVIEW_GUIDELINES.md`. All fixed in this commit plus one P2 nit that was cheap. **P1 — URL encoding via reqwest `.query()` on scope + list_credentials** `get_scope` and `list_credentials` in `mock_client.rs` built queries with raw `format!()` interpolation. Wallet strings with reserved characters (`&`, `#`, `%`, `+`, spaces) would break the request or smuggle extra params server-side. Switched both to `.get(self.url("/path")).query(&[...])` so reqwest percent-encodes per RFC 3986. Same pattern as the fix on PR #20 `aa0cc95` / PR #22 `ffdc908` / PR #29 `7aa9e70`. **P1 — Case-insensitive self-target check in `update_scope`** The self-target reject `session.wallet_address == target_wallet` was string-equal, but the backend stores wallets in lowercase while EIP-55 checksummed input preserves case. A master passing its own wallet in mixed-case form would bypass the self-target guard and silently restrict its own scope (the exact failure the guard was designed to prevent). Switched to `eq_ignore_ascii_case` matching the pattern established in PR #18's self-revoke detection. **P1 — Missing DENIED audit rows on scope endpoints** `update_scope` and `get_session_scope` returned 403 on ownership failure without inserting an audit row, breaking the cross-agent-probing-visibility invariant established for `list_credentials` / `read_credential` in PR #19. Added `'scope_update' 'DENIED'` and `'scope_read' 'DENIED'` inserts before the 403 return in both handlers. **P2 — `--add foo --remove foo` would no-op silently** `cmd_scope` accepted combinations like `--add X --remove X`. The add loop would push, then `retain(!remove.contains)` would cancel, and the backend PUT would still fire with the original scope + misleading "Scope updated" output. Added an up-front `add ∩ remove` check that lists overlapping services and rejects. **P2 — Narrow UPDATE to most-recent active session** Write-side `update_scope` UPDATEd ALL active sessions for the target wallet; read-side `get_session_scope` always returned the scope from the most-recent session via `ORDER BY created_at DESC LIMIT 1`. Read/write contract mismatched on wallets with multiple active sessions (paired + recovered). Narrowed the UPDATE to use the same ORDER/LIMIT subquery. Test: cargo test -p agentkeys-core -p agentkeys-cli -p agentkeys-mock-server -- --test-threads=1 core: 22 passed; cli: 35 passed; mock-server: 56 passed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli/mock-server): #29 v4 — claude re-review nits (dead sort, pct_encode consistency, test coverage) Follow-up on the claude-code-action re-review posted against commit 57dc9fb. All 3 substantive findings addressed; the 4th (malformed `/` doc-comments) was a false positive from pre-rebase state — grep for `^\s*/ ` returns nothing in the current tree. **Low — Redundant `sort_by` after `update_scope`** `lib.rs:769` was re-sorting `new_scope.services` after the backend call returned, but both the `--set` branch (line 749) and the `--add`/`--remove` branch (line 760) sort before the `update_scope` call. Dead op; removed. Added a comment so a future reader doesn't re-add it. **Low — `InProcessBackend::get_scope` raw URL concat** `test_client.rs:725` built the query string via `format!()` while the `resolve_identity` method right above uses `pct_encode`. Wallet strings are hex so this was safe in practice, but the inconsistency violates the URL-encoding invariant documented in `.github/REVIEW_GUIDELINES.md` pattern #3. Swapped to `pct_encode`. **Low — Missing test for `--list + --add` conflict** Added `cmd_scope_list_and_add_conflict_errors`. Also added `cmd_scope_add_remove_overlap_errors` (covering the P2 overlap guard from v3) that wasn't yet under test. Test: cargo test -p agentkeys-core -p agentkeys-cli -p agentkeys-mock-server -- --test-threads=1 core: 22 passed; cli: 37 passed (+2 new); mock-server: 56 passed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Fixes #16 — makes the
agentargument optional onstore/read/run(defaulting to the current session wallet) and accepts identity aliases (non-0xstrings) resolved via a new/identity/resolvebackend path.What changed
Trait (
agentkeys-core/src/backend.rs)CredentialBackend::resolve_identity(session, identifier) -> WalletAddressmethod.DummyBackend.Implementations
mock_client.rs— HTTP client callsGET /identity/resolve?identifier=<arg>.test_client.rs— in-process implementation.CLI
agentkeys-cli/src/lib.rs— newresolve_agent(ctx, session, agent: Option<&str>)helper:None→ session wallet.Some(s)wheres.starts_with("0x")→ passed through as wallet address.resolve_identity; unknown identities return"unknown identity '<arg>'. Use \agentkeys link` to create an alias or pass the 0x... wallet directly."`.cmd_store,cmd_read,cmd_run,cmd_revoketakeagent: Option<&str>and useresolve_agent.main.rs—Run,Store,Readhaveagent: Option<String>.Docs
docs/manual-test-issue-16.md(new) — 3 cases: default wallet, alias resolution, unknown identity error.docs/contradictions.md§4.3 — marked RESOLVED.Test plan
cargo test -p agentkeys-cli→ 19 passed (5 new tests for defaults + alias + unknown identity)cargo test -p agentkeys-core→ 6 passedcargo test -p agentkeys-mock-server→ 37 passed (no new mock-server tests — the CLI integration tests exercise/identity/resolveend-to-end throughInProcessBackend, which is sufficient; a pure-endpoint test can be added if desired)Issue
Closes #16.
🤖 Generated with Claude Code
Codex review (2026-04-14): ✅ Resolved by human design call (option 1: --agent flag). 3 codex passes flipped between mutually exclusive recommendations (positional → optional positional panics clap → --agent flag breaks scripts). Owner accepted the breaking-script tradeoff over dropping the feature or hand-rolling a manual parser. Doc migration applied in commit 776b405 (wiki + main.rs long_about + contradictions.md). See latest reply for full rationale.