Skip to content

Cleans up operator key management - #59

Open
therevoltingx wants to merge 2 commits into
mainfrom
fix/improves-operator-key-management
Open

Cleans up operator key management#59
therevoltingx wants to merge 2 commits into
mainfrom
fix/improves-operator-key-management

Conversation

@therevoltingx

@therevoltingx therevoltingx commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Improve operator key CLI management

Problem

ad-seller create-operator-key had two rough edges after the operator-auth work landed:

  1. Duplicate labels — creating another operator key with an existing active label succeeded silently, making it hard to tell which key was which.
  2. Process hang — the command printed the key but never exited. Storage was never closed after asyncio.run(...), and the console-script entrypoint (ad-seller = ...:app) skipped the hard os._exit that __main__ used to bypass telemetry atexit hangs.

Operators also had no out-of-band way to list or revoke operator keys without going through the HTTP API (which itself requires an operator credential).

What this PR does

Duplicate active labels

  • ApiKeyService.create_operator_key rejects an active (non-revoked, non-expired) operator key with the same label (ValueError, includes existing key_id).
  • POST /auth/api-keys/operator maps that to 409 Conflict.
  • Revoked/expired labels may be reused. Buyer keys are unaffected.

CLI exits cleanly

  • create-operator-key (and the new commands) always close_storage() in a finally.
  • Console script entrypoint is now ad_seller.interfaces.cli.main:main, wrapped with force_exit_after so ad-seller hard-exits like __main__.

Bootstrap CLI surface

Command Purpose
ad-seller create-operator-key Mint (unchanged, plus label uniqueness)
ad-seller list-operator-keys [--include-inactive] List operator keys (metadata only)
ad-seller delete-operator-key --label … / --key-id … Soft-revoke an operator key; frees the label

delete-operator-key refuses buyer keys (use DELETE /auth/api-keys/{key_id} for those).

Docs

Updated bootstrap sections in docs/api/authentication.md, docs/guides/agent-management.md, and docs/guides/developer-setup.md.

Test plan

  • ad-seller create-operator-key --label "Ops" twice → second fails with clear error; process exits
  • ad-seller list-operator-keys shows the active key; --include-inactive shows revoked too
  • ad-seller delete-operator-key --label "Ops" then recreate with the same label succeeds
  • POST /auth/api-keys/operator with a duplicate active label returns 409
  • Reinstall entrypoint if needed: pip install -e .
  • pytest tests/unit/test_operator_auth.py

@therevoltingx
therevoltingx marked this pull request as draft August 12, 2026 05:33
@therevoltingx
therevoltingx marked this pull request as ready for review August 12, 2026 21:33

@atc964 atc964 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified all three fixes, including reproducing the hang mechanism empirically: aiosqlite's connection is a non-daemon thread, so an unclosed storage after asyncio.run blocks interpreter shutdown — the close_storage-in-finally is the root-cause fix and routing the console script through force_exit_after is sound insurance (rich flushes per print, so the one-time key survives the hard exit even piped). Duplicate-label 409 confirmed at service, HTTP, and CLI layers with an end-to-end smoke on the installed entrypoint; no weakening of the operator-auth surface (deps untouched, no new routes; the 409's key_id disclosure is operator-visible anyway). Suite 1421/0 here.

Three take-or-leave observations: (1) label uniqueness is check-then-mint, so two concurrent operator POSTs with the same label can both land — low stakes here, maybe worth a docstring line; (2) the blanket ValueError->409 in the router will silently 409 any future ValueError from that path — a dedicated exception would be tighter; (3) behavior note: a second unlabeled operator-key request now 409s on the empty/default label where it used to succeed — reads as intended, but flagging since it changes bootstrap scripts that mint twice.

Editable installs need pip install -e . re-run for the entrypoint change, as your body notes. Good cleanup.

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.

2 participants