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
Conversation
…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>
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Backend (`kora_cli/web_server.py`)
`GET /api/diag-bundle` — `StreamingResponse` with:
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:
Frontend
Test plan
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