feat(#2): consume GET /me + GET /capabilities via --whoami one-shot
v1 coverage-audit slice (capabilities+me). Both endpoints had no caller; add them as cheap boot-time debug primitives. - sessions.py: get_me (GET /me — identity/whoami) + get_capabilities (GET /capabilities — Echo ephemeral-template discovery). Mirror get_persona_state: 200 -> parsed dict verbatim, non-200 -> SessionApiFailed. Freeform dicts (frozen OpenAPI types both as objects). - cli.py: new --whoami one-shot mode (mirrors --send). Fetches both, prints an identity + capabilities report, exits. Standalone probe: mutually exclusive with --send/--session/--new/--agent; opens no session. New ParsedArgs.whoami field + main() dispatch. - Contract #2 amended (2 FNs) + validated. TDD: 5 wrapper tests + 5 cli tests (validation + mode + error). Coverage map: REST 9/40. Suite 538 green; touched code ruff-clean. Audit note: /capabilities is the Echo ephemeral-template discovery endpoint, not a generic server-caps endpoint (coverage-map framing corrected). TUI-surfacing of /me + /capabilities deferred.
This commit is contained in:
@@ -206,3 +206,48 @@ TESTS:
|
||||
limit_above_max [adversarial]: limit=300 → AssertionError; no HTTP issued
|
||||
empty_cursor [adversarial]: cursor="" → AssertionError; no HTTP issued
|
||||
```
|
||||
|
||||
## Amendment 2026-06-30 — boot-time introspection reads (v1 coverage-audit: capabilities+me)
|
||||
|
||||
The v1 coverage-audit added two read-only server-introspection endpoints as
|
||||
cheap debug primitives (surfaced via a new `ratatoskr --whoami` one-shot). Both
|
||||
mirror `get_persona_state`: GET, 200 → parsed dict verbatim, any non-200 →
|
||||
`SessionApiFailed`. The frozen OpenAPI types both responses as freeform objects,
|
||||
so the wrappers return `dict[str, Any]` (not a typed dataclass).
|
||||
|
||||
```contract
|
||||
FN get_me(client: httpx.AsyncClient) -> dict[str, Any]
|
||||
BRIEF: GET /me — the authenticated principal's identity + key metadata (spec §GET /me). Boot-time whoami: verify the key without agent-config side effects. Returns parsed JSON verbatim; spec documents {user_id, scopes, tier, display_name?, key_id?, key_label?, ...} with optional fields OMITTED (not null). Read-only, rate-exempt, no audit emission.
|
||||
PRE: [PRE-001 hard] client is not None -- assert client is not None
|
||||
POST: [POST-001 return_value] on 200 returns resp.json() unmodified -- assert result == resp.json()
|
||||
ERROR_ROUTING:
|
||||
HTTP non-200 (incl. 401 bad/absent key when auth enabled):
|
||||
local_handling: raise SessionApiFailed(status=resp.status_code, body=resp.content)
|
||||
flow_control: abort
|
||||
state_recovery: none (caller decides: bad key → re-key; degraded tier="unknown" is still a 200)
|
||||
STEPS:
|
||||
1. [setup, prescriptive] assert client is not None
|
||||
2. [sequential, prescriptive] resp = await client.get("/me")
|
||||
3. [branch, prescriptive] IF resp.status_code == 200: RETURN resp.json(); ELSE RAISE SessionApiFailed
|
||||
TESTS:
|
||||
happy_authenticated [happy,tracer]: 200 {user_id, scopes, tier, key_id} → dict returned verbatim
|
||||
anonymous_dev_mode: 200 {user_id:"anonymous", tier:"anonymous"} → dict; no key_* fields (omitted)
|
||||
401_raises [error]: 401 → SessionApiFailed(status=401)
|
||||
|
||||
FN get_capabilities(client: httpx.AsyncClient) -> dict[str, Any]
|
||||
BRIEF: GET /capabilities — server capability discovery (spec §Ephemeral Templates). Returns {ephemeral_templates: {echo: {allowed_models, default_model, system_prompt_max_bytes}}}. Any authenticated caller may read it (no instantiate scope). Parsed dict verbatim; any non-200 → SessionApiFailed.
|
||||
PRE: [PRE-001 hard] client is not None -- assert client is not None
|
||||
POST: [POST-001 return_value] on 200 returns resp.json() unmodified -- assert result == resp.json()
|
||||
ERROR_ROUTING:
|
||||
HTTP non-200:
|
||||
local_handling: raise SessionApiFailed(status=resp.status_code, body=resp.content)
|
||||
flow_control: abort
|
||||
state_recovery: none
|
||||
STEPS:
|
||||
1. [setup, prescriptive] assert client is not None
|
||||
2. [sequential, prescriptive] resp = await client.get("/capabilities")
|
||||
3. [branch, prescriptive] IF resp.status_code == 200: RETURN resp.json(); ELSE RAISE SessionApiFailed
|
||||
TESTS:
|
||||
happy [happy]: 200 {ephemeral_templates:{echo:{...}}} → dict returned verbatim
|
||||
non_200_raises [error]: 500 → SessionApiFailed(status=500)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user