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)
|
||||
```
|
||||
|
||||
@@ -48,7 +48,7 @@ resolved (§ Surface 1, scope-resolution table).
|
||||
|
||||
| Surface | Points | ✅ covered-live | ⬜ gap (in-scope) | 🚫 excluded-by-design |
|
||||
|---|---|---|---|---|
|
||||
| REST (OpenAPI 2.2.0, path groups) | 40 | 7 | 11 | 22 |
|
||||
| REST (OpenAPI 2.2.0, path groups) | 40 | 9 | 9 | 22 |
|
||||
| SSE events | 11 | 11 | 0 | 0 |
|
||||
| Bifrost provider planes | 8 verbs | 8 | 0 | (10 gated verbs deferred) |
|
||||
|
||||
@@ -75,6 +75,8 @@ sub-gap).
|
||||
| `POST /agents/define` | ✅ | `tier3.py:175` → `_run_define` | Tier-3 create |
|
||||
| `PATCH /agents/{id}` | ✅ | `tier3.py:219` → `_run_patch` | Tier-3 mutate (system_prompt/model) |
|
||||
| `DELETE /agents/{id}` | ✅ | `tier3.py:242` → `_run_delete` | Tier-3 hard-delete |
|
||||
| `GET /me` | ✅ | `sessions.py:411` `get_me` → `cli.py` `--whoami` | identity/whoami probe; 401→SessionApiFailed |
|
||||
| `GET /capabilities` | ✅ | `sessions.py:428` `get_capabilities` → `cli.py` `--whoami` | Echo ephemeral-template discovery |
|
||||
|
||||
**Sub-gaps inside ✅ path groups** (the method we use is live; a sibling method
|
||||
on the same path is an unwired frontier item — see frontier Tier 1):
|
||||
@@ -95,8 +97,6 @@ on the same path is an unwired frontier item — see frontier Tier 1):
|
||||
| `GET /admin/events` | ⬜ | design-brief §5 v1 **AdminEvents pane**; = issue **#11**, **blocked** on `admin.events.read` scope (infra-ops) |
|
||||
| `GET /admin/sessions/{id}/bifrost` | ⬜ | design-brief §5 v1 **BifrostState widget** — never built; admin-key-gated |
|
||||
| `GET /admin/sessions/{id}/tools` | ⬜ | design-brief §5 v1 **Tools widget** — never built; admin-key-gated |
|
||||
| `GET /capabilities` | ⬜ | server capability discovery — a turn flows through what's advertised |
|
||||
| `GET /me` | ⬜ | whoami / key-identity — "which key am I against" is a debug primitive |
|
||||
| (`GET /sessions` picker · resume) | ⬜ | sub-gaps above — presenter-wiring only, wrappers exist |
|
||||
|
||||
**Tier 2 — rounds out I/O coverage under A (postdates the design-brief):**
|
||||
|
||||
Reference in New Issue
Block a user