Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.

feat(KR-P2-DIAG-BUNDLE): operator triage zip — 10 panel sources + manifest#89

Merged
rafe-walker merged 1 commit into
mainfrom
feat/kora-KR-P2-DIAG-BUNDLE
May 22, 2026
Merged

feat(KR-P2-DIAG-BUNDLE): operator triage zip — 10 panel sources + manifest#89
rafe-walker merged 1 commit into
mainfrom
feat/kora-KR-P2-DIAG-BUNDLE

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

Capstone-adjacent. 12 panels render Kora's state; this bucket lets the operator export ALL of it as a zip in one click for substrate-team triage or archive.

Operator UX: click "Diag bundle" on Dashboard → `kora-diag-bundle-.zip` downloads. Extract, share with substrate-team or attach to a debugging session.

Two firm contracts

  1. EXPLICIT allowlist (`_panel_sources()` function). New sources don't auto-leak into the bundle — adding one requires editing the explicit dict. Per bucket §1 fail-CLOSED.
  2. Credential-safe. Every source endpoint already excludes credentials per its own design (cost-state has a credential-leak guard from PR feat(KR-P2-COST-PANEL): cost ladder admin UI (burn + rung + deferred tickets) against stub #49; charter never returns tokens; etc.). The DIAG-BUNDLE test feat(KR-2 ST2): IsoKron read paths — Role Charter + capability matrix + policy registry #6 belt+braces grep catches aggregation accidents.

Backend (`kora_cli/web_server.py`)

`GET /api/diag-bundle` — `StreamingResponse` with:

  • `Content-Type: application/zip`
  • `Content-Disposition: attachment; filename="kora-diag-bundle-.zip"`

Per-endpoint try/except — a failing source surfaces in `manifest.errors[]` with type+message; its `.json` file is omitted; the bundle still extracts cleanly even if every endpoint fails (test #5b).

11 sources mapped:

zip file source endpoint
`operational_state.json` `/api/operational-state`
`boot_status.json` `/api/boot-status`
`cost_state.json` `/api/cost-state`
`health_rollup.json` `/api/health-rollup`
`dr_state.json` `/api/dr-state`
`sea_tickets_kora_assigned.json` `/api/sea-tickets/kora-assigned`
`kora_control_observed_state.json` `/api/kora-control/observed-state`
`capabilities.json` `/api/capabilities`
`charter.json` `/api/charter`
`chain_events.json` `/api/chain-events?prefix=kora.&limit=500`
`runbooks_manifest.json` `/api/runbooks` (manifest only; content excluded — static docs would bloat)
`manifest.json` bundle metadata: bundle_id + bundle_at + version + endpoints_included + errors

Frontend

Test plan

  • `tests/kora_cli/test_web_server_diag_bundle.py` — 11/11 green, covers all 7 §5 scenarios plus extras:
    • Always-present endpoints contract (`capabilities` + `runbooks_manifest`)
    • Manifest `endpoints_included` ↔ zip `*.json` files agreement
    • Manifest endpoint names ⊆ canonical `_EXPECTED_SOURCES` allowlist (catches auto-discovery sneaking new sources in)
    • All-endpoints-failing path still returns valid zip
    • Credential-safety contract guard via REGEX patterns (not bare substrings — the documentary literal `"wsk_*"` in a gate title is not a credential; only token-shaped strings match: `wsk_` + ≥16 alphanumeric chars, real Slack `xoxb-`/`xapp-` shapes, `sk-` + ≥20 chars, Bearer header value, JSON `{"password": ""}` field-with-value patterns)
  • Full admin-panel suite: 151/151 green across 14 test files
  • `npx tsc -b` on `web/` — clean
  • `npx vite build` on `web/` — clean
  • Manual smoke: open Dashboard, click "Diag bundle", verify zip downloads to ~/Downloads with the expected filename; `unzip kora-diag-bundle-*.zip` and verify ≥11 files inside (manifest.json + at least the always-available sources); read manifest.json to confirm endpoints_included matches reality

Notes on credential-safety guard design

The bucket §5 #6 spec originally asked for bare substring matches on `wsk_` / `xoxb-` / etc. First test run caught the documentary literal `"wsk_* token valid"` (a boot gate title from PR #45) and the runbook title `"Token rotation — wsk_* + CLAUDE_CODE_OAUTH_TOKEN unified procedure"`. Both are operator-facing descriptions of what the credential is, not credential values.

Tightened the patterns to match token-shaped strings: `wsk_` requires ≥16 alphanumeric continuation chars (real tokens, not the `*` placeholder); Slack patterns require the full `xoxb---` shape; `sk-` requires ≥20 alphanumeric chars; JSON field guard catches `{"password": ""}` but not bare mentions of "password" in copy. Spirit of the bucket preserved (catch real on-the-wire credentials) without false-positive-flagging documentary copy.

🤖 Generated with Claude Code

…ifest

One click on the Dashboard → zip download containing all 10 panel
data sources + manifest. When operator is investigating an issue
and needs to send Kora's full state to substrate-team or a
debugging session, instead of screenshotting panel-by-panel.

Two firm contracts:
  1. EXPLICIT allowlist (_panel_sources). New sources don't auto-leak
     into the bundle — adding one requires editing the explicit dict.
     Bucket §1 fail-CLOSED.
  2. Credential-safe. Every source endpoint already excludes
     credentials per its own design (cost-state has its own
     credential-leak guard from PR #49; charter never returns
     tokens; etc.). The DIAG-BUNDLE test #6 belt+braces grep
     catches aggregation accidents.

Backend (kora_cli/web_server.py):
  * GET /api/diag-bundle — StreamingResponse with application/zip +
    Content-Disposition: attachment; filename="kora-diag-bundle-
    <YYYYmmdd-HHMMSS>.zip". Per-endpoint try/except — a failing
    source surfaces in manifest.errors[] with type+message; its
    .json file is omitted; the bundle still extracts cleanly even
    if every endpoint fails.
  * _panel_sources() function (not a module dict) so fetchers
    resolve to current bound versions. 11 sources mapped:
    operational_state / boot_status / cost_state / health_rollup /
    dr_state / sea_tickets_kora_assigned / kora_control_observed_state
    / capabilities / charter / chain_events (kora.* prefix, 500
    events per bucket §2) / runbooks_manifest (manifest only;
    /content excluded — static docs would bloat bundle).
  * manifest.json carries bundle_id + bundle_at + version + the
    included endpoints array + per-endpoint errors[]. Always
    present even when every fetch fails.

Frontend:
  * api.ts — new DIAG_BUNDLE_URL constant + diagBundleHref() helper
    that applies HERMES_BASE_PATH (so the link works under the
    URL-prefix reverse-proxy setup the same way fetchJSON does for
    /api/* fetches).
  * DashboardPage.tsx — "Diag bundle" button at top-right next to
    Reload. Archive icon. Standard <a href download> pattern — no
    JS fetcher needed; browser handles the binary stream directly
    via Content-Disposition. Title attribute carries the operator-
    facing explainer.

Tests: tests/kora_cli/test_web_server_diag_bundle.py — 11 tests
covering all 7 §5 scenarios plus extras:
  * Always-present endpoints (capabilities + runbooks_manifest) —
    pin the in-process sources to detect any future regression
  * Manifest endpoints_included matches zip *.json contents
    (catches drift where manifest claims success but file missing)
  * Manifest endpoint names ⊆ canonical _EXPECTED_SOURCES allowlist
    (bucket §1 fail-CLOSED: no auto-discovery sneaking new sources
    into the bundle)
  * All-endpoints-failing path still returns valid zip with just
    manifest.json
  * Credential-safety contract guard via REGEX patterns (not bare
    substrings — the documentary literal "wsk_*" in a gate title
    is not a credential; only token-shaped strings match: wsk_
    + ≥16 alphanumeric chars / real Slack xoxb-/xapp- shapes /
    sk- + ≥20 chars / Bearer header value / JSON {"password":
    "<non-empty>"} field-with-value patterns)

151/151 admin-panel suite green across 14 test files.
tsc -b + vite build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit 174e2b9 into main May 22, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-P2-DIAG-BUNDLE branch May 22, 2026 02:43
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant