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

feat(KR-P2-RUNBOOKS-PANEL): operator runbooks viewer (12th admin panel)#84

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

feat(KR-P2-RUNBOOKS-PANEL): operator runbooks viewer (12th admin panel)#84
rafe-walker merged 1 commit into
mainfrom
feat/kora-KR-P2-RUNBOOKS-PANEL

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

12th admin panel. Operationally-critical surface — when DR fires at 2am, operator opens `/runbooks` and reads the procedure inline. No tabbing to docs.

Read-only by design: runbooks may reference operational secrets in their text (e.g. "rotate the wsk_* token via doppler secrets set"); panel renders verbatim, operator executes outside. Never an "execute this command" button.

§2 verifications (run before code)

Runbook path Status
`kora_docs/15_status_and_roadmap/dr_runbook.md` ❌ not vendored (separate kora-docs repo) — renders as [runbook pending] placeholder
`kora_docs/15_status_and_roadmap/token_rotation_runbook.md` ❌ not vendored — placeholder
`kora_docs/15_status_and_roadmap/deploy_runbook.md` ❌ not vendored — placeholder
`kora_docs/00_canonical_current_state/kora_dna.md` ❌ not vendored — placeholder
`docs/deploy-fly-io.md` present (5113 bytes; shipped by KR-P2-F-pre ST3)

Existing `web/src/components/Markdown.tsx` reused for content rendering — no new markdown lib added (code blocks / headers / lists / inline code / links / hr all already supported).

Backend

`kora_cli/web_server.py`:

  • `_RUNBOOK_MANIFEST`: explicit `Dict[str, (title, repo-relative path)]`. NOT auto-discovered — ops want a stable known index.
  • `GET /api/runbooks` — manifest endpoint. One entry per pinned tuple; `available` + `size_bytes` + `last_modified` populated for files actually present, nulls for placeholders.
  • `GET /api/runbooks/{runbook_id}/content` — raw markdown with `text/markdown` content-type. Defensive on multiple axes:
    1. id regex `^[a-z][a-z0-9_]*$` rejects `../etc/passwd` etc. (WARN logged so ops spot traversal attempts)
    2. id is only ever a dict-key lookup against the manifest — user input never composed into a path
    3. Resolved path verified `is_relative_to(repo_root)` — belt+braces against future manifest typo
    4. 1 MiB size cap (returns 413 if exceeded)
    5. Manifest entry but file missing → 404 with `"not yet authored"` (distinct from "unknown id" so FE renders placeholder vs error)
  • Repo root resolved via `file` (stable across uvicorn launch dirs).

Frontend

`pages/RunbooksPage.tsx` — sidebar/content layout (280px + 1fr grid on desktop; single-column on mobile):

  • Sidebar: available/unavailable icon + title + path; click selects
  • Content pane:
    • No selection → "Select a runbook from the sidebar" empty-state
    • Selected + unavailable → [runbook pending] placeholder card with expected-path callout
    • Selected + available + loading → spinner
    • Selected + available + loaded → reused `Markdown` component renders body inside `max-h-[calc(100vh-260px)]` scroll container; header strip shows title + path + size + last-modified relative+absolute + Print button
  • Print button triggers `window.print()` for 2am paper-copy operators

`lib/api.ts` — new `fetchText()` helper (mirrors `fetchJSON` session token + non-2xx-throws semantics); `RunbookEntry` + `RunbooksManifest` interfaces; `getRunbooks` + `getRunbookContent` clients.

`App.tsx` — `/runbooks` route + nav entry (BookOpenCheck icon) at end of nav per spec (operator looks for it specifically; doesn't need top placement).

Canonical runbook IDs (per manifest)

id title placeholder vs available
`dr_runbook` Disaster Recovery — post-PITR substrate_epoch bump placeholder
`token_rotation_runbook` Token rotation — wsk_* + CLAUDE_CODE_OAUTH_TOKEN unified placeholder
`deploy_runbook_canonical` Deploy — canonical (post-KR-P2-F-pre) placeholder
`kora_dna` Kora DNA reference placeholder
`deploy_fly_io` Fly deploy + Doppler secret refresh available

When kora-docs runbooks land in this repo (or get vendored at deploy time), each placeholder auto-flips to available without any code change — the manifest entry already points at the expected path.

Test plan

  • `tests/kora_cli/test_web_server_runbooks.py` — 11/11 green, covers all 7 §5 scenarios plus extras:
    • `deploy_fly_io` always-available contract guard
    • Multiple traversal pattern guards (`..`, `../etc`, `foo/bar`, `foo\bar`, `UPPERCASE`, leading digit, internal whitespace — all 404)
    • Manifest-path-outside-root refused (belt+braces against typo)
    • Defensive size cap with mocked 1.1 MiB file → 413
    • Unavailable-vs-unknown distinction (`dr_runbook` manifest entry, file missing → 404 `"not yet authored"`)
  • Full admin-panel suite: 140/140 green across 13 test files
  • `npx tsc -b` on `web/` — clean
  • `npx vite build` on `web/` — clean
  • Manual smoke on a configured dev box: navigate to `/runbooks`; sidebar shows 5 entries (4 placeholders + deploy_fly_io with green check); click deploy_fly_io and verify the Fly + Doppler markdown renders correctly; click a placeholder and verify the amber "[runbook pending]" card shows; hit Print button and verify browser print dialog opens

Pre-existing test failures (NOT this PR)

5 `test_web_server_cron_profiles.py` failures pre-exist on main HEAD (KR-P2-D ST2 added a `work_class` requirement that broke the older cron tests). Confirmed in prior PRs; flagged for separate cleanup.

🤖 Generated with Claude Code

When DR fires at 2am, operator opens /runbooks and reads the
procedure inline. No tabbing to docs. Read-only — operators read
here, execute outside (runbooks may reference operational secrets
in text; never embed "execute this command" buttons).

§2 verification: of the 5 PM-pre-listed paths, only docs/deploy-fly-io.md
is currently vendored into this repo (5113 bytes; shipped by
KR-P2-F-pre ST3). The 4 kora_docs/* paths reference a separate repo
(rafe-walker/kora-docs); they surface as "[runbook pending]"
placeholders so operators see the manifest acknowledges them but
they're not authored yet.

Reused web/src/components/Markdown.tsx (already in the project —
code blocks / headers / lists / inline code / links / horizontal
rules). No new markdown lib added.

Backend (kora_cli/web_server.py):
  * Explicit _RUNBOOK_MANIFEST: Dict[str, (title, repo-relative path)].
    NOT auto-discovered — ops want a stable known index, not a
    wandering scan of every .md.
  * GET /api/runbooks — manifest endpoint. One entry per pinned tuple
    with available + size_bytes + last_modified populated for files
    actually present; nulls for placeholders.
  * GET /api/runbooks/{runbook_id}/content — raw markdown response
    (text/markdown content-type). Defensive on multiple axes:
      1. id regex ^[a-z][a-z0-9_]*$ rejects ../etc/passwd, etc.
         (WARN logged so ops can spot traversal attempts).
      2. id is only ever a dict-key lookup against the manifest —
         user input is never concatenated into a path.
      3. Resolved path verified is_relative_to(repo_root) — belt+braces
         against a future manifest typo using ".." absolute paths.
      4. 1 MiB size cap (returns 413 if exceeded).
      5. Manifest entry exists but file missing → 404 with
         "not yet authored" message (FE renders placeholder card —
         distinct from "unknown id").
  * Repo root resolved via __file__ (stable across uvicorn launch dirs).

Frontend (web/):
  * pages/RunbooksPage.tsx — sidebar/content 280px+1fr grid (single-
    column on mobile). Sidebar lists runbooks with available/
    unavailable icon + title + path; click selects. Content pane:
      - No selection → empty-state "Select a runbook from the sidebar"
      - Selected + unavailable → "[runbook pending]" placeholder card
        with expected-path callout
      - Selected + available + loading → spinner
      - Selected + available + loaded → reused Markdown component
        renders the body inside a max-h-[calc(100vh-260px)] scroll
        container; header strip shows title + path + size + last-
        modified relative+absolute + Print button
  * Print button triggers window.print() — operators get a paper
    copy at 2am if they want one (markdown renders fine through
    default print styles).
  * lib/api.ts — new fetchText() helper (mirrors fetchJSON's session
    token injection + non-2xx-throws but returns res.text() instead
    of res.json()). RunbookEntry + RunbooksManifest interfaces;
    getRunbooks + getRunbookContent client methods.
  * App.tsx — /runbooks route + nav entry (BookOpenCheck icon) at
    END of nav per spec §3 (operator looks for it specifically;
    doesn't need top placement).

Tests: tests/kora_cli/test_web_server_runbooks.py — 11 tests
covering all 7 §5 scenarios plus extras:
  * deploy_fly_io always-available contract guard (pins the in-repo
    runbook against future repo reorganization)
  * Multiple traversal pattern guards (.., ../etc, foo/bar, foo\\bar,
    UPPERCASE, leading digit, internal whitespace all 404)
  * Manifest-path-outside-root refused (belt+braces against typo)
  * Defensive size cap with mocked oversized file
  * Unavailable-vs-unknown distinction: dr_runbook (manifest entry,
    file missing) returns 404 with "not yet authored" — distinguishable
    from "unknown id" so FE renders placeholder rather than error
140/140 admin-panel suite green (test_web_server_cron_profiles
omitted — 5 pre-existing failures from KR-P2-D ST2 work_class
requirement; not mine).
tsc -b + vite build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit 6723239 into main May 22, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-P2-RUNBOOKS-PANEL branch May 22, 2026 02:24
rafe-walker added a commit that referenced this pull request May 22, 2026
… + truthful 11-source footer (#95)

v1 row 1 (Health hero + 6 cards) untouched. Row 2 adds 4 cards in 4-col lg grid: Capabilities (total_tools + total_caps + escalating-badge for 17 unmapped C2 mirror groups) / Charter (mid-truncated revision_id + rules_hash + loaded_at + 'rules pending' fallback badge) / Recent events (last 5 chain events, stripped kora. prefix for density) / Runbooks (available vs pending counts + most-recently-modified).

isStubbed loosened to LoadStatus<unknown> with property-check (caps + runbooks responses don't carry stub field). ALL_SOURCES array consolidates 11 sources for anyStubbed banner and footer aggregate. Footer reports truthful 11-source count (PM bucket said 13; corrected — dashboard fetches 11 endpoint-backed sources; matches DIAG-BUNDLE's allowlist).

150/151 admin-panel tests pass; 1 stale assertion from PR #86 vendoring dr_runbook.md auto-improving PR #84's 'unauthored' assertion — flagged for trivial follow-up.
rafe-walker added a commit that referenced this pull request May 22, 2026
…on (#97)

PR #86 vendored dr_runbook.md + token_rotation_runbook.md; PR #84's test_get_runbook_content_404_for_unauthored_manifest_entry asserted dr_runbook as unauthored — stale. Swapped to kora_dna (still placeholder) + updated docstring explaining auto-improvement history. 151/151 admin-panel suite back to green.
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