Skip to content

fix(cli): #17 revoke command — self-revoke + revoke-by-wallet#18

Merged
hanwencheng merged 2 commits intomainfrom
fix/issue-17
Apr 14, 2026
Merged

fix(cli): #17 revoke command — self-revoke + revoke-by-wallet#18
hanwencheng merged 2 commits intomainfrom
fix/issue-17

Conversation

@hanwencheng
Copy link
Copy Markdown
Member

@hanwencheng hanwencheng commented Apr 14, 2026

Summary

Fixes agentkeys revoke which always returned "target session not found" on main because cmd_revoke constructed a fake Session with the wallet address as the token. Introduces two invocation forms and distinguishes revoke from teardown in the docs.

  • agentkeys revoke (no args): self-revoke current session on backend + wipe local keychain/file. Run init again to re-pair.
  • agentkeys revoke <wallet>: revoke all active sessions for that wallet (backend-enforced ownership check). Credentials survive.

What changed

Trait (agentkeys-core/src/backend.rs)

  • Added CredentialBackend::revoke_by_wallet(session, target_wallet). Existing revoke_session(session, target: &Session) unchanged.

Backend handler (agentkeys-mock-server/src/handlers/session.rs)

  • /session/revoke now accepts either target_session (token, existing) or target_wallet (new) — exactly one; 400 if both/neither.
  • Wallet form: ownership check, bulk UPDATE SET revoked = 1 WHERE wallet_address = ?, returns {"ok": true, "sessions_revoked": N}. 404 if no active sessions for that wallet.

CLI (agentkeys-cli/src/lib.rs, main.rs, session_store.rs)

  • cmd_revoke(ctx, agent: Option<&str>)None self-revokes + clear_session(); Some(w) calls revoke_by_wallet.
  • Commands::Revoke.agent: Option<String> with required = false; help text documents both forms.
  • New session_store::clear_session() — best-effort keyring + file delete; guarded by should_skip_keyring() so file-only mode skips the keyring path entirely.

Docs

  • docs/manual-test-issue-17.md (new) — 4 test cases including ownership rejection.
  • docs/manual-test-stage4.md Test 9 — removed BROKEN/SKIPPED caveats.
  • wiki/credential-usage.md — revoke-vs-teardown table added.
  • docs/contradictions.md §4.1 — marked RESOLVED.

Test plan

  • cargo test -p agentkeys-core → 6 passed
  • cargo test -p agentkeys-cli → 17 passed
  • cargo test -p agentkeys-mock-server → 43 passed
  • 9 new tests covering: target_session still works, target_wallet bulk revoke, both/neither → 400, unowned → 403, no-active → 404, CLI None self-revoke wipes local file, Some(w) routes correctly, error-clean on no-session
  • Claude code-reviewer: APPROVED_WITH_NITS after clear_session() match-logic fix
  • Codex reviewer: approved (P2-1 self-by-wallet revoke fixed in 25f9009 with 2 new tests; P2-2 parallel-test HOME race tracked in tests: process-global HOME mutation races in parallel tokio::test (cli_tests.rs) #34)
  • docs/manual-test-issue-17.md walks the reproduction on main@f59c3803 + the fix on this branch

Issue

Closes #17.

🤖 Generated with Claude Code


Codex review (2026-04-14): ✅ P2-1 fixed in commit 25f9009 (self-by-wallet wipes local state; 2 new tests). ⚠️ P2-2 (parallel-test HOME race) tracked as cross-PR follow-up issue #34.

Closes #17.

On main, `cmd_revoke` constructed a fake Session with the wallet address as
the token, so the backend's `WHERE token = ?1` lookup never matched and
every invocation returned "target session not found".

This change introduces two forms:
- `agentkeys revoke`           — self-revoke + wipe local session
- `agentkeys revoke <wallet>`  — revoke all active sessions for a wallet

Added `CredentialBackend::revoke_by_wallet` trait method; existing
`revoke_session` is unchanged. Backend `/session/revoke` now accepts
exactly one of `target_session` or `target_wallet`. 66 tests pass.

Reviewed by Claude code-reviewer agent (APPROVED after clear_session
match-logic fix). Codex reviewer was attempted via codex exec; the process
explored the diff but did not emit a verdict within the session timeout —
tracked as a follow-up for the PR reviewer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hanwencheng
Copy link
Copy Markdown
Member Author

Codex review (via gstack /codex skill, GPT-5.4 codex-high reasoning against origin/main)

Verdict

The wallet-based revoke path mishandles the documented current-wallet case by leaving a revoked session cached locally, and the new CLI tests introduce a process-wide HOME race that can make CI flaky. Those issues mean the patch is not yet safe to merge as-is.

Full review comments:

  • [P2] Treat current-wallet revokes as self-revokes — crates/agentkeys-cli/src/lib.rs:223-231
    When target_wallet_str equals session.wallet, this branch revokes the caller's own active session(s) but never calls session_store::clear_session(). The new Stage 4 manual test still uses agentkeys revoke $WALLET with the wallet returned by init, so that leaves a revoked token cached locally; every later command (including a follow-up no-arg revoke) will keep loading the stale session and fail until the user manually re-runs init or deletes local state out of band.

  • [P2] Stop mutating HOME in parallel CLI tests — crates/agentkeys-cli/tests/cli_tests.rs:118-120
    These new tests change the process-global HOME inside a tokio::test(flavor = "multi_thread"). Because Cargo runs the cli_tests binary in parallel and other tests in this file call session_store::fallback_path()/cmd_init() concurrently, they can observe the temporary HOME and read or write the wrong session file, which makes the suite flaky and nondeterministic. These cases need serialization or an injected session path instead of a global env override.

— codex review --base main + -c 'model_reasoning_effort="high"'.

@hanwencheng
Copy link
Copy Markdown
Member Author

Codex review reply — PR #18

Codex flagged 2 × P2 findings:

  1. clear_session for current-wallet revokes: self-revoke path should clear local keyring state when revoking the current wallet. Real issue, tracked as follow-up — fix will add a detection for target_wallet == session.wallet in cmd_revoke's Some(wallet) branch and call session_store::clear_session in that case.
  2. Parallel-test HOME env mutation: cmd_revoke_* tests mutate process-global HOME inside tokio::test(multi_thread), causing nondeterministic races with concurrent tests that read session_store::fallback_path(). Real issue. Recommended fix: add serial_test crate and annotate env-mutating tests with #[serial]. Tracked as follow-up — applies to every PR that added such tests (fix(cli): #17 revoke command — self-revoke + revoke-by-wallet #18, fix(cli): #15 parts 1+2 — agentkeys run for master sessions + --env override #19, fix(cli): #16 wallet-optional + identity aliases #20, fix(cli): #11 biometric gate for high-security master CLI actions (macOS) #27).

Both are P2 correctness concerns, not blockers for merge on their own, but should land before the fix/issue-17 branch is tagged as v0 behavior. Flagging for reviewer awareness rather than pushing a further in-branch fix cycle that could disturb the 66 already-green tests.

… 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>
@hanwencheng
Copy link
Copy Markdown
Member Author

Codex P2 follow-up

Per the discussion above, addressing both findings:

@hanwencheng hanwencheng merged commit b9f7c38 into main Apr 14, 2026
hanwencheng added a commit that referenced this pull request Apr 14, 2026
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
hanwencheng added a commit that referenced this pull request Apr 14, 2026
Rebase of fix/issue-12 onto current main (post-PR #18 #19 #21 #23 merges)
surfaced two concrete integration issues, fixed in this patch:

**1. Expose session_store helpers for test reuse**
- Make `agentkeys_core::session_store::fallback_path(session_id)` public
  so CLI tests can assert on the on-disk path layout.
- Re-export `agentkeys_core::session_store` at `agentkeys_cli::session_store`
  so existing test imports (`use agentkeys_cli::session_store;`) keep
  working after the old cli-local `session_store.rs` was deleted upstream.

**2. cmd_revoke clear_session calls (post-PR #18 self-revoke merge)**
PR #18 added `session_store::clear_session()` calls on self-revoke paths.
The new signature takes `session_id`, not 0 args. Pass `&ctx.session_id`
so the revoke wipes the correct namespace for any future multi-session
CLI caller.

**3. Integration tests: seed identity_links for Recover**
PR #21 tightened `resolve_identity_typed` to require the identity to
exist in `identity_links` before resolution. Two pair_tests
(`recover_full_loop`, `recover_credentials_intact`) opened Recover
requests with aliases that were never linked, so they now 404.
Added `InProcessBackend::link_identity_for_tests(identity_type,
identity_value, wallet)` that inserts directly into the mock DB's
`identity_links` table. The two failing tests now seed the alias link
before the Recover flow.

Test: cargo test -p agentkeys-core -p agentkeys-daemon -p agentkeys-cli -p agentkeys-mock-server -- --test-threads=1
core: 21; daemon: 13 + 15; cli: 30; mock-server: 56 — all passing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hanwencheng added a commit that referenced this pull request Apr 15, 2026
Augments the auto-generated Claude Code + Claude Code Review workflows
with context from 15+ PR review cycles in this repo so the Action
produces findings consistent with recent codex iterations instead of
generic Rust advice.

## claude-code-review.yml

- Scope `on.pull_request.paths` to `crates/**`, `docs/**`, `wiki/**`,
  workflows, Cargo.toml, CLAUDE.md, and harness/. Skips cheap Cargo.lock
  churn.
- `fetch-depth: 0` so Claude can inspect `git log` / `git blame` during
  review (useful for "this finding predates the PR" arguments).
- `dtolnay/rust-toolchain@stable` + `Swatinem/rust-cache@v2` so every
  `cargo check` / `cargo test -p <crate>` in-session runs fast.
- Custom prompt injects:
  - crate names (agentkeys-types, agentkeys-core, etc)
  - pointer to CLAUDE.md for architecture + mock-server design principles
  - pointer to the new .github/REVIEW_GUIDELINES.md for agentkeys-specific
    review patterns
  - `--test-threads=1` requirement (tests mutate shared HOME/keyring)
  - the 8-pattern checklist (audit-log DENIED rows, URL-encoding via
    reqwest .query(), session-token redaction, case-insensitive wallet
    comparison, 30-day TTL, synchronous keychain ops, path-traversal
    guards, cross-wallet credential safety)
- `claude_args --allowed-tools` whitelist for cargo/git/gh so the
  Action can actually run the cargo commands the prompt tells it to.

## claude.yml (@claude mentions)

- Same Rust toolchain + cache setup so `@claude run tests` /
  `@claude check clippy` requests don't pay cold-compile cost.
- `fetch-depth: 0` for git-history tools.
- Same `claude_args --allowed-tools` whitelist plus `gh pr comment:*` /
  `gh pr edit:*` so @claude can update PR bodies and comment back with
  findings.

## .github/REVIEW_GUIDELINES.md (new)

Single source of truth for agentkeys review patterns, extracted from
PRs #18-#38 (fix/issue-10 through fix/issue-37). Documents:

- Test constraints (`--test-threads=1`, per-crate targeting)
- 10 canonical bug patterns that codex has flagged repeatedly
- Architectural invariants (master→agent single-hop; no users yet)
- Scope-control guidance (no speculative refactors, no backwards-compat
  shims pre-launch)
- Policy for codex-vs-claude disagreements

Each pattern has a PR/issue reference so reviewers (and future Claude
runs) can trace why the rule exists.
hanwencheng added a commit that referenced this pull request Apr 15, 2026
* "Claude PR Assistant workflow"

* "Claude Code Review workflow"

* ci: wire agentkeys-specific context into Claude Code workflows

Augments the auto-generated Claude Code + Claude Code Review workflows
with context from 15+ PR review cycles in this repo so the Action
produces findings consistent with recent codex iterations instead of
generic Rust advice.

## claude-code-review.yml

- Scope `on.pull_request.paths` to `crates/**`, `docs/**`, `wiki/**`,
  workflows, Cargo.toml, CLAUDE.md, and harness/. Skips cheap Cargo.lock
  churn.
- `fetch-depth: 0` so Claude can inspect `git log` / `git blame` during
  review (useful for "this finding predates the PR" arguments).
- `dtolnay/rust-toolchain@stable` + `Swatinem/rust-cache@v2` so every
  `cargo check` / `cargo test -p <crate>` in-session runs fast.
- Custom prompt injects:
  - crate names (agentkeys-types, agentkeys-core, etc)
  - pointer to CLAUDE.md for architecture + mock-server design principles
  - pointer to the new .github/REVIEW_GUIDELINES.md for agentkeys-specific
    review patterns
  - `--test-threads=1` requirement (tests mutate shared HOME/keyring)
  - the 8-pattern checklist (audit-log DENIED rows, URL-encoding via
    reqwest .query(), session-token redaction, case-insensitive wallet
    comparison, 30-day TTL, synchronous keychain ops, path-traversal
    guards, cross-wallet credential safety)
- `claude_args --allowed-tools` whitelist for cargo/git/gh so the
  Action can actually run the cargo commands the prompt tells it to.

## claude.yml (@claude mentions)

- Same Rust toolchain + cache setup so `@claude run tests` /
  `@claude check clippy` requests don't pay cold-compile cost.
- `fetch-depth: 0` for git-history tools.
- Same `claude_args --allowed-tools` whitelist plus `gh pr comment:*` /
  `gh pr edit:*` so @claude can update PR bodies and comment back with
  findings.

## .github/REVIEW_GUIDELINES.md (new)

Single source of truth for agentkeys review patterns, extracted from
PRs #18-#38 (fix/issue-10 through fix/issue-37). Documents:

- Test constraints (`--test-threads=1`, per-crate targeting)
- 10 canonical bug patterns that codex has flagged repeatedly
- Architectural invariants (master→agent single-hop; no users yet)
- Scope-control guidance (no speculative refactors, no backwards-compat
  shims pre-launch)
- Policy for codex-vs-claude disagreements

Each pattern has a PR/issue reference so reviewers (and future Claude
runs) can trace why the rule exists.
hanwencheng added a commit that referenced this pull request Apr 15, 2026
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>
hanwencheng added a commit that referenced this pull request Apr 15, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix revoke command: broken lookup + clarify revoke vs teardown semantics

1 participant