feat(intake): gated keyring GET /keys metadata list + same-origin proxy (Phase 2)
What + why
Phase 2 (P2.2) of epic &60 (closed). To test the signed-API onboarding (register → approve → sign), an operator needs to see which keys exist + their lifecycle status. The craig-intake-keyring sidecar holds the keys but exposes no list route. Add a gated, metadata-only GET /keys on the keyring + a same-origin intake proxy GET /signed/v1/keys (the browser never hits the keyring directly — ADR-042 §D8 internal-only).
Plan: docs/modules/ROOT/pages/plans/craig-intake-portal-phase2-integration-aids.adoc (§ P2.2).
Design (see plan for detail)
- Keyring:
KeyringState::list_all()(sorted, deterministic) + DTOSignerKeyListEntry { kid, display_name, status, created_at }(NOpublic_key_jwk, NOuser_identifier). - Gate: new
enable_key_list: boolonKeyringSettings(envCRAIG_INTAKE_KEYRING__ENABLE_KEY_LIST, default OFF); the route is conditionally mounted inbuild_router— absent (→ 404) when off. - Intake proxy
GET /signed/v1/keys(Shines signed nest), mirroringregister_signer_key(reuses theExtension<reqwest::Client>+KeyringUrl); relays JSON; redacts an outage to 500.
Acceptance criteria
- Keyring
GET /keyslists registered keys' metadata (no JWK / no user_identifier) when the gate is ON; route absent → 404 when OFF. - Deterministic ordering; unauthenticated-but-gated + internal-only (not host-published) per ADR-042 §D8.
- Intake proxy relays the list; a keyring outage is a redacted 500.
- Keyring unit + router tests; intake proxy test green.