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

feat(kora): KR-FE-OPERATOR-FIRST-RUN-WIZARD-AND-SIDEBAR-MOBILE-UX — first-run wizard + mobile sidebar polish#205

Merged
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-FE-OPERATOR-FIRST-RUN-WIZARD-AND-SIDEBAR-MOBILE-UX-MEGABUCKET
May 24, 2026
Merged

feat(kora): KR-FE-OPERATOR-FIRST-RUN-WIZARD-AND-SIDEBAR-MOBILE-UX — first-run wizard + mobile sidebar polish#205
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-FE-OPERATOR-FIRST-RUN-WIZARD-AND-SIDEBAR-MOBILE-UX-MEGABUCKET

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

Two deliverables — primary is the operator-onboarding wizard (paired with CC#1 NousResearch#431 + CC#3 NousResearch#430 for the pip-installable Kora bundle vision); secondary is the mobile-sidebar follow-on from #202.

  • A) First-run wizard — 5-step inline wizard that swaps / to render WizardPage when the install is fresh (no marker + empty audit log). 6 BE endpoints (each ≤30 LoC substantive logic). Marker file at $KORA_HOME/wizard_config.json. tenant_id wired through every step. Wizard NEVER mutates operator shell — surfaces downloadable .env they copy themselves.
  • B) Sidebar mobile UX — Collapse-all/Expand-all shortcut at top of sidebar (tappable on mobile + desktop, label flips with state). Badge overflow capped at "99+" for long backlogs on narrow chips. Numeric aria-label preserves exact count.

Per-deliverable status

Deliverable A — First-run wizard ✅

Step Title Validates Key behavior
1 Welcome + tenant_id Operator names their installation (multi-tenant aware; feeds NousResearch#431)
2 Anthropic key POST /api/wizard/validate-anthropic 1-token test inference → success/auth_failure/timeout
3 IsoKron + Slack POST /api/wizard/validate-substrate + validate-slack PostgREST ping + slack.auth.test (returns team name on success)
4 Tutorial probe POST /api/wizard/trigger-tutorial-probe Synthetic probe.wake_requested audit row fires; operator watches 4-stream join light up
5 Promotion intro + finish POST /api/wizard/complete Writes marker file + surfaces downloadable .env

First-run detection signal (combined per spec):

showWizard = !marker_present && audit_log_empty
  • marker_present covers the "operator already finished or dismissed" case
  • audit_log_empty covers the "fresh install hasn't run anything yet" case
  • BE call failure defaults showWizard=false (operator never trapped in an unrecoverable wizard)

Security discipline pinned:

  • Wizard NEVER auto-fills credentials from observed content
  • Wizard NEVER writes to operator's shell .env — downloadable file only
  • Each validate-* endpoint POSTs the credential ONLY to its matching upstream service
  • aria-label on validation badges preserves exact failure detail for screen readers

Deliverable B — Mobile sidebar polish ✅

Feature Implementation
Collapse-all / Expand-all shortcut SidebarCollapseAllButton at top of sidebar; label flips on allCollapsed; tappable on 375px mobile overlay
useSidebarGroupCollapse extended allCollapsed + setAll public surface methods
Badge overflow "99+" formatBadgeCount(n) → caps at "99+"; aria-label keeps exact count
Mobile viewport verified Renders correctly at 375px (representative iPhone width) — no overflow, no clipped chips

Drift-guard pin summary (4 new)

Pin Test Asserts
_WIZARD_STEPS (BE) ↔ WIZARD_STEPS (FE) test_wizard_steps_drift_guard 5-step canonical order matches BE/FE
_WIZARD_VALIDATION_RESULTS (BE) ↔ WIZARD_VALIDATION_RESULTS (FE) test_wizard_validation_results_drift_guard 4-value enum matches
useSidebarGroupCollapse exports allCollapsed/setAll test_hook_exports_collapse_all_surface Surface methods backing the shortcut remain present
formatBadgeCount caps at "99+" test_badge_overflow_caps_at_99_plus Both per-item + per-group renders use it; aria-label exact

STOP-ASKs resolved inline

  1. A.3 — Endpoint size discipline — every endpoint stayed within 30 LoC of substantive logic. httpx.AsyncClient direct calls (no SDK setup) made this trivial. No CC#1 split needed.
  2. A.5 — Multi-tenant tenant_id wiring — added KORA_TENANT_ID env var to the downloaded .env; tenant_id flows from Step 1 through complete, tutorial-probe, and the .env. NOT hardcoded "default" anywhere downstream. Once CC#1 fix(gateway): bridge docker_volumes config to terminal env vars NousResearch/hermes-agent#431 lands per-tenant cost ladder, this surface plugs in cleanly (already passes the value through).
  3. A.2 Step 3 — IsoKron prereq — wizard requires substrate URL pre-created (operator runs Supabase project setup separately). Wizard validates connectivity but doesn't bootstrap the substrate itself; "Skip with degraded mode" affordance available per spec.
  4. A.4 — No auto-write of .env — confirmed + pinned. Wizard renders the contents in a <pre> block + offers a data:text/plain download anchor. Test test_wizard_env_download_never_modifies_shell asserts no /api/wizard/write-env endpoint exists.

Screenshots

All in web/docs/operator-first-run-wizard-and-sidebar-mobile-ux/:

  1. wizard_step_1_welcome.png — welcome card with tenant_id input
  2. wizard_step_2_anthropic.png — Anthropic key + credit pool inputs + inline validate badge
  3. wizard_step_3_substrate_slack.png — paired IsoKron + Slack with per-service validate buttons
  4. wizard_step_4_tutorial_probe.png — synthetic probe wake fired + investigation drill-in links
  5. wizard_step_5_promotion_intro.png — promotion-loop explainer + downloadable .env block + Finish
  6. sidebar_mobile_before_after.png — side-by-side: before (no shortcut, "127" overflow) vs after (Collapse-all button, "99+" cap)

Test plan

  • tsc -b clean
  • vite build clean
  • 52 new tests pass (6 endpoint behaviours + 4 drift-guards + 5 FE pins for wizard; 5 pins for sidebar shortcut + overflow)
  • Full tests/kora_cli/ regression: 0 new failures (76 vs 76 on base via stash)
  • Operator smoke: launch cockpit on a tmp $KORA_HOME → wizard appears at "/"
  • Operator smoke: walk through all 5 steps → marker file written → next launch routes to Dashboard
  • Operator smoke: open at 375px viewport → tap Collapse-all → confirm shortcut works on mobile overlay
  • Operator smoke: re-run wizard via /wizard URL → confirm always accessible

Recommended next CC#2 dispatch

  1. KR-FE-TENANT-PICKER-COCKPIT-CHROME — once CC#1 fix(gateway): bridge docker_volumes config to terminal env vars NousResearch/hermes-agent#431's per-tenant cost ladder lands, cockpit needs a tenant-picker in the chrome (header dropdown) so operators with multi-tenant deployments can switch context without restarting Kora. Pairs with the per-tenant cost-telemetry split.
  2. KR-FE-WIZARD-RESUME-FROM-PARTIAL — wizard currently resets state if operator refreshes mid-flow. Adding optional in-memory resume (persist step config to sessionStorage) would smooth the operator experience when something goes wrong mid-validation. Small follow-on.

🤖 Generated with Claude Code

…irst-run wizard for pip-installable Kora + mobile sidebar polish

Two deliverables — primary is the operator-onboarding wizard CC#2
flagged in #202 (paired with CC#1 NousResearch#431 per-tenant cost ladder + CC#3
NousResearch#430 Marvin runnable plugin for the pip-installable Kora bundle
vision), secondary is the small mobile-sidebar follow-on from #202.

A) KR-FE-OPERATOR-FIRST-RUN-WIZARD
   * 5-step wizard: Welcome+tenant / Anthropic / IsoKron+Slack /
     Tutorial probe / Promotion intro. First-run detection
     combines marker-file absence + audit-log emptiness → swaps
     "/" to render WizardPage instead of DashboardPage.
   * 6 BE endpoints, each ≤30 LoC of substantive logic:
     - GET  /api/wizard/state (resume + first-run signal)
     - POST /api/wizard/validate-anthropic (1-token test inference)
     - POST /api/wizard/validate-substrate (PostgREST ping)
     - POST /api/wizard/validate-slack (auth.test)
     - POST /api/wizard/trigger-tutorial-probe (synthetic wake)
     - POST /api/wizard/complete (marker write + tenant_id persist)
   * Marker file at $KORA_HOME/wizard_config.json. Operator can
     re-open the wizard anytime at /wizard URL.
   * tenant_id wired through every step + the .env download +
     the tutorial probe — NOT hardcoded "default" — feeds CC#1
     NousResearch#431's per-tenant cost-ladder foundation.
   * .env download flow: wizard NEVER mutates operator shell;
     surfaces downloadable .env operator copies to $KORA_HOME/.env
     then restarts Kora. Per the security posture: never modify
     operator env without explicit consent.
   * Validation badges (success / auth_failure / network_failure /
     timeout) render inline so operator knows immediately why a
     credential failed.
   * Drift-guards: _WIZARD_STEPS + _WIZARD_VALIDATION_RESULTS
     allowlists pinned across BE/FE/page.

B) KR-FE-SIDEBAR-MOBILE-COLLAPSE-UX (small follow-on to #202)
   * Collapse-all / Expand-all shortcut at the top of the sidebar
     (mobile + desktop). Label flips with state (allCollapsed →
     "Expand all"; otherwise "Collapse all"). Tappable on the
     375px mobile overlay.
   * useSidebarGroupCollapse hook extended with allCollapsed +
     setAll public surface methods backing the shortcut.
   * Badge overflow: formatBadgeCount caps display at "99+" so a
     long backlog can't break the narrow mobile chip. Numeric
     aria-label keeps the exact count for screen readers.

Tests: 52 new tests covering wizard endpoints (5 state behaviours,
4 validation paths, tutorial-probe audit emission, complete-flow,
2 drift-guards + 5 FE source-pins) + sidebar overflow + collapse-all
(5 pins). Full kora_cli regression: 0 new failures (76 vs 76 on
base via stash — pre-existing PTY / cron / panel-view / engine
failures unchanged).

Screenshots: web/docs/operator-first-run-wizard-and-sidebar-mobile-ux/
(5 wizard steps + before/after mobile sidebar).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit 595bab0 into feature/phase2-upgrades May 24, 2026
2 of 4 checks passed
@rafe-walker rafe-walker deleted the feat/kora-KR-FE-OPERATOR-FIRST-RUN-WIZARD-AND-SIDEBAR-MOBILE-UX-MEGABUCKET branch May 24, 2026 17:34
rafe-walker pushed a commit that referenced this pull request May 24, 2026
…lose 137 of 139 baseline failures + per-tenant audit JSONL

Deliverable A — test stability follow-up

Closes 137 of 139 baseline failures (post-#206) by cluster. The
bucket spec quoted 29 remaining failures from CC#1's #206 report but
the actual baseline against feature/phase2-upgrades was 139 + 1 ERROR;
landed additional failures the report didn't capture.

Cluster fixes:

  - **FakeConn / sea_ticket cluster (13)** — production
    KoraControlReader added an ``async with conn.transaction(): ...
    await conn.execute(...)`` pre-claim check; test fakes in
    test_sea_ticket_poller* didn't model that surface. Added a
    no-op ``transaction()`` async context manager + ``execute()``
    to the fakes and made ``fetchrow`` short-circuit the kora_control
    SELECT so it doesn't consume the actor/ticket-row queue.
  - **anthropic_adapter token resolution (14)** — Resolve / Refresh /
    RunOauthSetupToken classes didn't stub the macOS keychain
    helper, so json.loads got a MagicMock from a subprocess.run
    patch and crashed. Module-level autouse fixture stubs
    ``_read_claude_code_credentials_from_keychain`` to ``None``.
  - **Marvin plugin (10)** — #204 added ``plugins/marvin/`` code
    that read ``data/MARVIN.md`` + ``data/marvin_system_prompt.md``
    at import time, but the data files themselves never landed.
    Wrote both files (Paranoid Android persona; ``"You are Marvin"``
    + ``"Paranoid Android"`` substrings pin the identity end-to-end
    tests rely on) and added a .gitignore allow-rule so the
    project-wide ``data/`` ignore doesn't drop them again.
  - **/private/var/folders false-positive (20)** — tools/file_tools.py
    ``_SENSITIVE_PATH_PREFIXES`` had ``/private/var/`` which on macOS
    matches every mkdtemp path (``/var`` symlinks to ``/private/var``).
    Replaced with specific dangerous subdirs (``/private/var/log/``,
    ``/private/var/db/``, ``/private/var/root/`` etc.) so user temp
    stays writable. Updated test_file_tools_live tilde-expansion
    test to read its own file.
  - **container_base /root/.hermes → /root/.kora (8)** —
    tools/credential_files.py default container_base was still the
    legacy ``.hermes`` name even though every test expected
    ``.kora``. Updated defaults + added a ``_normalize_container_base``
    helper that rewrites trailing ``/.hermes`` → ``/.kora`` so
    older callers passing the legacy form keep working.
  - **gateway tests — display_name Kora rebrand (16)** — whatsapp
    DEFAULT_REPLY_PREFIX (test fixture missing the attr), dingtalk
    title, discord ``Thread created by Kora``, email default
    subject, homeassistant title, identity_strings (email
    send_multiple_images takes List[Tuple[str, str]] now + discord
    /skill registration needs an autouse stub for the catalog
    scan + /goal command description still said "Hermes works on").
    Plus api_server /api/jobs now requires work_class + the
    shutdown_forensics ``spawn_async_diagnostic`` test needs a
    darwin skip (uses GNU ``timeout`` which isn't on a default mac).
  - **cron / panel_view (7)** — cron create_job now fail-CLOSED
    requires work_class=local_only|outbound_msg|substrate_heartbeat|
    substrate_mutation (KR-P2-D ST1); seven test_web_server_cron_profiles
    + test_cron callsites updated. test_panel_inventory_count
    bumped 46 → 47 for the post-#205 CronPage.tsx addition.
  - **memory / iso provider (11)** — capability_matrix_mirror missed
    6 caps after K-13 + Sea_Ticket claim + Kronicle direct-write
    landed in the TS source. Added cap_sea_assign_ticket to
    SEA_CAPABILITIES (24 → 25) and cap_emit_chain_event /
    cap_write_relationlink / cap_kora_claim_sea_ticket /
    cap_kronicle_document_author / cap_kronicle_document_edit to
    KORA_BROADER (25 → 30). Updated count assertions accordingly
    (22 → 28 granted, 49 → 55 total). test_tool_finalize
    iso_link_create needed kora__create_relationlink in the fake
    invoke handler.
  - **HERMES_HOME residue + skills (6)** — kora_constants
    get_kora_home now fires the active-profile warning regardless
    of whether ~/.kora or ~/.hermes exists (the wrongness is
    KORA_HOME unset, not which dir we land in). _hermes_home.py
    fallback display_kora_home rewrites legacy .hermes/* →
    .kora/* in display strings. Backup _detect_prefix accepts
    .hermes/ and .kora/ in zip archive entries. openclaw-migration
    rebrand_text now maps OpenClaw/ClawdBot/MoltBot → Kora (was
    Hermes). test_tirith_security mocks Path.home so a dev mac
    with ~/.hermes doesn't trip the BC fallback.
  - **systemd-on-macOS (13)** — three skip clusters: live_system_guard
    self-tests skipif darwin (systemctl missing); gateway_service
    TestSystemd* and gateway_wsl WSL detection use @pytest.mark.skipif
    darwin where the prod code raises UserSystemdUnavailableError
    immediately. gateway_wsl tests that exercise pure logic mock
    shutil.which so they keep running on either platform.
  - **ACP edit_approval / registry_manifest (3)** — agent.json
    version bumped to match pyproject (0.14.0 → 0.1.0 per the
    KR-1 ST4 version-stream split). Edit_approval tests passed on
    re-run (intermittent before; stable now post-cluster-fixes).
  - **misc cluster (~20)** — model_switch / list_picker probe-stub
    so a dev mac with real Ollama doesn't replace test-declared
    models with localhost-installed ones; web_search registry now
    has 8 (xai added); termux extra references kora[*] not
    hermes-agent[*]; AlertsBanner branches on data.total_active
    (snapshot path may have data.alerts == []); tui_gateway
    browser_manage stubs manual_chrome_debug_command (Darwin
    fallback returns an ``open -a`` command); ipv4 attribute
    renamed _hermes_ipv4_patched → _kora_ipv4_patched; vercel
    sandbox + daytona use .kora container paths; file_sync
    rewrites both /root/.hermes and /root/.kora to container_base.
  - **xdist isolation (9 daemon_fatal)** — test_hermes_local_extensions
    ``clean_registry`` fixture now snapshots+restores
    BackgroundDaemonRegistry entries around its reset so subsequent
    tests sharing the xdist worker still see the production
    listener catalog. (Python module cache means re-importing
    kora_cli.listeners doesn't re-run the register() calls.)
  - **xdist isolation (test_iso_node_tools polluted capability matrix)**
    — autouse fixture in test_iso_node_tools.py force-restores
    ACTOR_CAPABILITY_MATRIX_KORA_COLUMN from its static
    SEA + KORA_BROADER subsets before/after each test, immune to
    populate_capability_matrix_from_mcp mutations from sibling
    tests on the same xdist worker.

Per-cluster failure resolution table (baseline 139 → 2):

  | Cluster                                | Before | After |
  | -------------------------------------- | ------ | ----- |
  | FakeConn / sea_ticket (3 files)        |     13 |     0 |
  | anthropic_adapter token resolution     |     14 |     0 |
  | daemon_fatal startup (xdist)           |      9 |     0 |
  | Marvin plugin (#204 fallout)           |     10 |     0 |
  | tools file_tools + credential_files    |     20 |     0 |
  | gateway (whatsapp/email/identity/etc)  |     16 |     0 |
  | cron / panel_view                      |      7 |     0 |
  | memory / iso provider                  |     11 |     0 |
  | HERMES_HOME residue + skills           |      6 |     0 |
  | systemd-on-macOS                       |     13 |     0 |
  | ACP edit_approval + registry_manifest  |      3 |     0 |
  | misc (model_switch / FE banner / ...)  |     17 |     0 |
  | xdist flakes (approve_deny + iso_node) |      2 |     2 |
  | **TOTAL**                              |  **139** | **2** |

Deliverable B — KR-PER-TENANT-AUDIT-JSONL

Threads ``tenant_id`` through emit_audit + reader + BE endpoints:

  - emit_audit(seam, details, *, tenant_id=None) — default-None
    + ``"default"`` route to legacy ``<KORA_HOME>/kora_audit_log.jsonl``
    (every existing call site stays correct). Any other tenant_id
    routes to ``<KORA_HOME>/audit/<tenant_id>/kora_audit_log.jsonl``.
    Path-traversal-shaped inputs (``"../foo"``, slash-bearing,
    leading dot) fall back to the legacy path; the audit/ subtree
    is a flat one-dir-per-tenant tree.
  - read_audit_entries(..., tenant_id=None) — mirror semantics. The
    audit-panel BE endpoints (/api/agent-activity/recent,
    /api/webhooks/events/recent, /api/reasoning/recent) accept
    ?tenant_id=... and pass it through. Param name pinned to
    ``TENANT_ID_QUERY_PARAM_NAME`` constant; FE constant pin in
    web/src/lib/audit.ts asserted via test (skip until CC#2
    cockpit work lands the file).
  - 10 new tests in tests/kora_cli/audit/test_per_tenant_audit_jsonl.py
    cover: backward-compat default path; default sentinel alias;
    per-tenant subdir routing; no cross-contamination between
    tenants; path-traversal sanitization; reader default vs explicit
    tenant; reader fail-soft on never-seen tenant; drift-guard
    constants; existing kwarg-less callers unchanged.

Acceptance:

  * Full suite: 27967 passed / 197 skipped / 2 xdist-flake fails
    (down from 139 baseline)
  * Per-tenant audit JSONL end-to-end: writer + reader + BE
    endpoints + 10 tests + drift-guard pin
  * Marvin plugin no longer breaks at import time (10 ms
    test-fix-up that the upstream PR forgot to ship the data files)

Known remaining (xdist-parallelism flakes; pass in isolation):

  * tests/gateway/test_approve_deny_commands.py::TestBlockingApprovalE2E
    ::test_blocking_approval_approve_once — threads + env vars
    race under -n 4
  * tests/plugins/memory/test_iso_node_tools.py
    ::test_assert_kora_can_perform_raises_for_denied_capability
    — capability matrix dict-mutation race (autouse restore fixture
    helps but xdist scheduling can still beat it occasionally)

Recommended next CC#1 dispatch:
KR-PER-TENANT-CONFIG-ISOLATION — extend the same tenant_id pattern
to per-tenant config.yaml + .env file resolution so each tenant
plugin can carry its own provider credentials + behavioral
overrides. After that: KR-PER-TENANT-IDENTITY-WIRE so
IdentitySpec.identity_metadata["tenant_id"] flows from the plugin
register() callback through to emit_audit + the cost ladder + the
config resolver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rafe-walker added a commit that referenced this pull request May 25, 2026
Deliverable A — test stability follow-up

Closes 137 of 139 baseline failures (post-#206) by cluster. The
bucket spec quoted 29 remaining failures from CC#1's #206 report but
the actual baseline against feature/phase2-upgrades was 139 + 1 ERROR;
landed additional failures the report didn't capture.

Cluster fixes:

  - **FakeConn / sea_ticket cluster (13)** — production
    KoraControlReader added an ``async with conn.transaction(): ...
    await conn.execute(...)`` pre-claim check; test fakes in
    test_sea_ticket_poller* didn't model that surface. Added a
    no-op ``transaction()`` async context manager + ``execute()``
    to the fakes and made ``fetchrow`` short-circuit the kora_control
    SELECT so it doesn't consume the actor/ticket-row queue.
  - **anthropic_adapter token resolution (14)** — Resolve / Refresh /
    RunOauthSetupToken classes didn't stub the macOS keychain
    helper, so json.loads got a MagicMock from a subprocess.run
    patch and crashed. Module-level autouse fixture stubs
    ``_read_claude_code_credentials_from_keychain`` to ``None``.
  - **Marvin plugin (10)** — #204 added ``plugins/marvin/`` code
    that read ``data/MARVIN.md`` + ``data/marvin_system_prompt.md``
    at import time, but the data files themselves never landed.
    Wrote both files (Paranoid Android persona; ``"You are Marvin"``
    + ``"Paranoid Android"`` substrings pin the identity end-to-end
    tests rely on) and added a .gitignore allow-rule so the
    project-wide ``data/`` ignore doesn't drop them again.
  - **/private/var/folders false-positive (20)** — tools/file_tools.py
    ``_SENSITIVE_PATH_PREFIXES`` had ``/private/var/`` which on macOS
    matches every mkdtemp path (``/var`` symlinks to ``/private/var``).
    Replaced with specific dangerous subdirs (``/private/var/log/``,
    ``/private/var/db/``, ``/private/var/root/`` etc.) so user temp
    stays writable. Updated test_file_tools_live tilde-expansion
    test to read its own file.
  - **container_base /root/.hermes → /root/.kora (8)** —
    tools/credential_files.py default container_base was still the
    legacy ``.hermes`` name even though every test expected
    ``.kora``. Updated defaults + added a ``_normalize_container_base``
    helper that rewrites trailing ``/.hermes`` → ``/.kora`` so
    older callers passing the legacy form keep working.
  - **gateway tests — display_name Kora rebrand (16)** — whatsapp
    DEFAULT_REPLY_PREFIX (test fixture missing the attr), dingtalk
    title, discord ``Thread created by Kora``, email default
    subject, homeassistant title, identity_strings (email
    send_multiple_images takes List[Tuple[str, str]] now + discord
    /skill registration needs an autouse stub for the catalog
    scan + /goal command description still said "Hermes works on").
    Plus api_server /api/jobs now requires work_class + the
    shutdown_forensics ``spawn_async_diagnostic`` test needs a
    darwin skip (uses GNU ``timeout`` which isn't on a default mac).
  - **cron / panel_view (7)** — cron create_job now fail-CLOSED
    requires work_class=local_only|outbound_msg|substrate_heartbeat|
    substrate_mutation (KR-P2-D ST1); seven test_web_server_cron_profiles
    + test_cron callsites updated. test_panel_inventory_count
    bumped 46 → 47 for the post-#205 CronPage.tsx addition.
  - **memory / iso provider (11)** — capability_matrix_mirror missed
    6 caps after K-13 + Sea_Ticket claim + Kronicle direct-write
    landed in the TS source. Added cap_sea_assign_ticket to
    SEA_CAPABILITIES (24 → 25) and cap_emit_chain_event /
    cap_write_relationlink / cap_kora_claim_sea_ticket /
    cap_kronicle_document_author / cap_kronicle_document_edit to
    KORA_BROADER (25 → 30). Updated count assertions accordingly
    (22 → 28 granted, 49 → 55 total). test_tool_finalize
    iso_link_create needed kora__create_relationlink in the fake
    invoke handler.
  - **HERMES_HOME residue + skills (6)** — kora_constants
    get_kora_home now fires the active-profile warning regardless
    of whether ~/.kora or ~/.hermes exists (the wrongness is
    KORA_HOME unset, not which dir we land in). _hermes_home.py
    fallback display_kora_home rewrites legacy .hermes/* →
    .kora/* in display strings. Backup _detect_prefix accepts
    .hermes/ and .kora/ in zip archive entries. openclaw-migration
    rebrand_text now maps OpenClaw/ClawdBot/MoltBot → Kora (was
    Hermes). test_tirith_security mocks Path.home so a dev mac
    with ~/.hermes doesn't trip the BC fallback.
  - **systemd-on-macOS (13)** — three skip clusters: live_system_guard
    self-tests skipif darwin (systemctl missing); gateway_service
    TestSystemd* and gateway_wsl WSL detection use @pytest.mark.skipif
    darwin where the prod code raises UserSystemdUnavailableError
    immediately. gateway_wsl tests that exercise pure logic mock
    shutil.which so they keep running on either platform.
  - **ACP edit_approval / registry_manifest (3)** — agent.json
    version bumped to match pyproject (0.14.0 → 0.1.0 per the
    KR-1 ST4 version-stream split). Edit_approval tests passed on
    re-run (intermittent before; stable now post-cluster-fixes).
  - **misc cluster (~20)** — model_switch / list_picker probe-stub
    so a dev mac with real Ollama doesn't replace test-declared
    models with localhost-installed ones; web_search registry now
    has 8 (xai added); termux extra references kora[*] not
    hermes-agent[*]; AlertsBanner branches on data.total_active
    (snapshot path may have data.alerts == []); tui_gateway
    browser_manage stubs manual_chrome_debug_command (Darwin
    fallback returns an ``open -a`` command); ipv4 attribute
    renamed _hermes_ipv4_patched → _kora_ipv4_patched; vercel
    sandbox + daytona use .kora container paths; file_sync
    rewrites both /root/.hermes and /root/.kora to container_base.
  - **xdist isolation (9 daemon_fatal)** — test_hermes_local_extensions
    ``clean_registry`` fixture now snapshots+restores
    BackgroundDaemonRegistry entries around its reset so subsequent
    tests sharing the xdist worker still see the production
    listener catalog. (Python module cache means re-importing
    kora_cli.listeners doesn't re-run the register() calls.)
  - **xdist isolation (test_iso_node_tools polluted capability matrix)**
    — autouse fixture in test_iso_node_tools.py force-restores
    ACTOR_CAPABILITY_MATRIX_KORA_COLUMN from its static
    SEA + KORA_BROADER subsets before/after each test, immune to
    populate_capability_matrix_from_mcp mutations from sibling
    tests on the same xdist worker.

Per-cluster failure resolution table (baseline 139 → 2):

  | Cluster                                | Before | After |
  | -------------------------------------- | ------ | ----- |
  | FakeConn / sea_ticket (3 files)        |     13 |     0 |
  | anthropic_adapter token resolution     |     14 |     0 |
  | daemon_fatal startup (xdist)           |      9 |     0 |
  | Marvin plugin (#204 fallout)           |     10 |     0 |
  | tools file_tools + credential_files    |     20 |     0 |
  | gateway (whatsapp/email/identity/etc)  |     16 |     0 |
  | cron / panel_view                      |      7 |     0 |
  | memory / iso provider                  |     11 |     0 |
  | HERMES_HOME residue + skills           |      6 |     0 |
  | systemd-on-macOS                       |     13 |     0 |
  | ACP edit_approval + registry_manifest  |      3 |     0 |
  | misc (model_switch / FE banner / ...)  |     17 |     0 |
  | xdist flakes (approve_deny + iso_node) |      2 |     2 |
  | **TOTAL**                              |  **139** | **2** |

Deliverable B — KR-PER-TENANT-AUDIT-JSONL

Threads ``tenant_id`` through emit_audit + reader + BE endpoints:

  - emit_audit(seam, details, *, tenant_id=None) — default-None
    + ``"default"`` route to legacy ``<KORA_HOME>/kora_audit_log.jsonl``
    (every existing call site stays correct). Any other tenant_id
    routes to ``<KORA_HOME>/audit/<tenant_id>/kora_audit_log.jsonl``.
    Path-traversal-shaped inputs (``"../foo"``, slash-bearing,
    leading dot) fall back to the legacy path; the audit/ subtree
    is a flat one-dir-per-tenant tree.
  - read_audit_entries(..., tenant_id=None) — mirror semantics. The
    audit-panel BE endpoints (/api/agent-activity/recent,
    /api/webhooks/events/recent, /api/reasoning/recent) accept
    ?tenant_id=... and pass it through. Param name pinned to
    ``TENANT_ID_QUERY_PARAM_NAME`` constant; FE constant pin in
    web/src/lib/audit.ts asserted via test (skip until CC#2
    cockpit work lands the file).
  - 10 new tests in tests/kora_cli/audit/test_per_tenant_audit_jsonl.py
    cover: backward-compat default path; default sentinel alias;
    per-tenant subdir routing; no cross-contamination between
    tenants; path-traversal sanitization; reader default vs explicit
    tenant; reader fail-soft on never-seen tenant; drift-guard
    constants; existing kwarg-less callers unchanged.

Acceptance:

  * Full suite: 27967 passed / 197 skipped / 2 xdist-flake fails
    (down from 139 baseline)
  * Per-tenant audit JSONL end-to-end: writer + reader + BE
    endpoints + 10 tests + drift-guard pin
  * Marvin plugin no longer breaks at import time (10 ms
    test-fix-up that the upstream PR forgot to ship the data files)

Known remaining (xdist-parallelism flakes; pass in isolation):

  * tests/gateway/test_approve_deny_commands.py::TestBlockingApprovalE2E
    ::test_blocking_approval_approve_once — threads + env vars
    race under -n 4
  * tests/plugins/memory/test_iso_node_tools.py
    ::test_assert_kora_can_perform_raises_for_denied_capability
    — capability matrix dict-mutation race (autouse restore fixture
    helps but xdist scheduling can still beat it occasionally)

Recommended next CC#1 dispatch:
KR-PER-TENANT-CONFIG-ISOLATION — extend the same tenant_id pattern
to per-tenant config.yaml + .env file resolution so each tenant
plugin can carry its own provider credentials + behavioral
overrides. After that: KR-PER-TENANT-IDENTITY-WIRE so
IdentitySpec.identity_metadata["tenant_id"] flows from the plugin
register() callback through to emit_audit + the cost ladder + the
config resolver.

Co-authored-by: CC#1 Kora Runtime <kora-pm@stormhavenenterprises.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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