This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(KR-6): Python actor_has_capability helper — replaces assert_kora_can_perform stub, closes D-kr3-st1#12
Merged
Conversation
KR-6 ships the Python mirror of TS-side actorHasCapability
(packages/sea-mcp-server/src/capability-matrix.ts:657). Replaces the
KR-3 ST1 stub at tools/iso_node.py:assert_kora_can_perform (which
always allowed + logged [kora.isokron.todo]
D-kr3-st1-capability-check-deferred) with a real check.
Cheapest-unlock follow-on per PM's pipeline-order note: no substrate
dependency, ~120 LOC including tests, closes one of our own
BUILD_DEVIATIONS entries.
New plugins/memory/isokron/capability_check.py:
* CapabilityDeniedError — carries .capability + .reason (default
message names the capability; callers can override).
* actor_has_capability(capability) — O(1) dict lookup against the
existing C2 mirror's ACTOR_CAPABILITY_MATRIX_KORA_COLUMN. Unknown
capability raises KeyError (fail-LOUD — typo or parity-test drift;
the parity guard catches mirror divergence on both sides).
* assert_kora_can_perform(capability, *, reason=None) — raises
CapabilityDeniedError if Kora lacks the cap; KeyError propagates
for unknown caps.
Behavior change vs the TS reference: TS is fail-closed (unknown cap
returns false); Python is fail-LOUD (unknown cap raises KeyError).
Rationale documented in the module docstring: Python callers are
inside Kora's own code so unknown caps are programmer errors we
want surfaced during testing; TS callers ride through a Sea MCP JSON
boundary where unknown caps can arrive from payloads — fail-closed
there prevents privilege escalation via typo.
Stub replacement:
* tools/iso_node.py — local stub deleted; the public names re-export
from ..capability_check so existing callers (iso_link.py + tests
+ dispatcher) don't need to change their import paths.
* tools/iso_link.py — imports CapabilityDeniedError directly from
..capability_check; previously transitively via iso_node.
* tools/__init__.py — re-exports capability_check public surface.
Tool-handler integration:
* handle_iso_node_tool_call and handle_iso_link_tool_call now catch
CapabilityDeniedError and surface a structured envelope:
{"ok": false, "denied": true, "capability": ..., "reason": ...}
Mirrors the deferred-write envelope pattern from KR-2 ST3 / ST4 /
KR-3 ST2 — model gets an in-band signal rather than an uncaught
exception. (Today, all 7 tools require caps Kora has, so this path
doesn't fire in normal operation — but it's defensive against
future cap shifts.)
Tests:
* New tests/plugins/memory/test_capability_check.py (11 tests):
- actor_has_capability granted (cap_sea_create, cap_write_agent_
scratchpad)
- actor_has_capability denied (cap_override_security_or_policy_
verdict, cap_unbless_convention)
- actor_has_capability unknown → KeyError with diagnostic message
- assert_kora_can_perform granted (cap_propose_policy_change)
- assert_kora_can_perform denied → CapabilityDeniedError with
.capability + default .reason
- assert_kora_can_perform with caller-supplied .reason
- assert_kora_can_perform unknown → KeyError propagates
- Forward-stability: module has no MCP / DB / network imports at
load time (the K-7 → KR-N swap invariant — must remain a one-line
import change).
- Tightness: capability_check.py imports only the C2 mirror — no
provider / connection / reads / scratchpad / events / relationlink
pulls (keeps the swap surface tight).
* Existing KR-3 tests updated (the stub-log assertions no longer
apply):
- test_iso_node_tools.py: deleted
test_assert_kora_can_perform_stub_logs_deviation_id; added
test_assert_kora_can_perform_passes_for_granted_capability +
test_assert_kora_can_perform_raises_for_denied_capability +
test_assert_kora_can_perform_raises_keyerror_for_unknown_cap.
- test_iso_link_tools.py: renamed/flipped
test_iso_link_traverse_capability_check_logs_deviation →
test_iso_link_traverse_capability_check_passes_silently
(asserts no denial envelope + ok=True).
- test_tool_finalize.py: parametrized
test_every_iso_tool_logs_capability_stub_deviation → flipped to
test_every_iso_tool_passes_capability_check_for_granted_caps
(asserts no denial envelope across all 7 tools).
BUILD_DEVIATIONS:
* D-kr3-st1-capability-check-deferred moved from Open to a new
Closed section with the spec quote, the forward-stability
invariant explained, and the tool-handler integration noted.
README "Operator pitfalls":
* Deferred-surface table trimmed from 5 rows to 4 (D-kr3-st1
removed); a Recently-closed note replaces the entry so operators
see the resolution at a glance.
Local gates:
* ty check — 7,337 diagnostics, zero-delta vs KR-3 ST3 baseline.
* pytest tests/plugins/memory/ — 326/326 passing (11 new KR-6 + 11
new flipped + 304 pre-KR-6).
* Full suite via xdist (-n auto): 24,649 / 142 failed / 129 skipped.
Δ vs KR-3 ST3 (24,625/153/129): +24 passed, -11 failed (xdist
isolation oscillates; none touch isokron).
4 deferrals remain open pending CC#1 substrate:
* D-kr2-st2-capability-matrix-mirror ← K-7
* D-kr2-st3-no-scratchpad-write-mcp-tool ← K-8
* D-kr2-st4-no-chain-emit-mcp-tool ← K-9
* D-kr3-st2-no-relationlink-write-mcp-tool ← K-10
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
rafe-walker
added a commit
that referenced
this pull request
May 23, 2026
…132) Pairs with CC#3 KR-FEAT-AI-RESPONSE-LOOP. Operator-facing view of Kora reasoning calls — model used, tokens, duration, errors. - Backend /api/reasoning/recent stub (4 calls spanning the full cost-ladder × error-taxonomy matrix). - ReasoningPanel.tsx: 4-column stats strip + filter pills + newest-first timeline with tier-tinted model badges + color-mapped cost-rung pills + duration bars + token counts. - Dashboard card #12 (Brain icon). Row-2 now 3+3+3+3 — clean 4-row × 3-col closure. 4-layer security contract: plain-text response rendering + Anthropic key walk-payload sweep + PII walk-payload sweep (per-field and whole-blob) + TS interface enforcement. K-DG drift caught: spec example used uppercase NORMAL/WARN_75/... but agent/cost_state_holder.py:114-117 wire format is lowercase .value strings. Used lowercase in stub so real CC#3 data and the FE pill-color map agree at flip time. Backend test pins lowercase + flags intentional spec divergence. 287/287 admin-panel tests pass across 24 suites; tsc -b + vite build clean.
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
KR-6 ships the Python mirror of TS-side
actorHasCapability(packages/sea-mcp-server/src/capability-matrix.ts:657) and closesD-kr3-st1-capability-check-deferred. The KR-3 ST1 stub attools/iso_node.py:assert_kora_can_perform(which always allowed and logged the deviation ID) is replaced with a real check.Cheapest-unlock follow-on per PM's pipeline-order note: no substrate dependency, ~120 LOC including tests, closes one of our own BUILD_DEVIATIONS entries.
New module:
plugins/memory/isokron/capability_check.pyactor_has_capability— O(1) dict lookup against the existing C2 mirror'sACTOR_CAPABILITY_MATRIX_KORA_COLUMN. Returnsbool.assert_kora_can_perform— raisesCapabilityDeniedError(carrying.capability+.reason) when Kora lacks the cap;KeyErrorpropagates for unknown caps.KeyError(fail-LOUD) rather than the TS reference's fail-closedfalse. Python callers are inside Kora's own codebase, so an unknown cap is a programmer error worth surfacing during testing. TS callers ride through a Sea MCP JSON boundary where unknown caps can arrive from payloads — fail-closed there prevents privilege escalation via typo. Documented in the module docstring.Stub replacement (4 files)
plugins/memory/isokron/tools/iso_node.pyassert_kora_can_performstub deleted; the public names re-export from..capability_checkso existing callers don't have to change import paths.plugins/memory/isokron/tools/iso_link.pyCapabilityDeniedError+assert_kora_can_performdirectly from..capability_check.plugins/memory/isokron/tools/__init__.pyCapabilityDeniedError,actor_has_capability,assert_kora_can_perform).Dispatcher catch — structured denial envelope
handle_iso_node_tool_callandhandle_iso_link_tool_callnow catchCapabilityDeniedErrorand surface it as:{ "ok": false, "denied": true, "capability": "...", "reason": "..." }Mirrors the deferred-write envelope pattern from KR-2 ST3 / ST4 / KR-3 ST2 — model gets an in-band signal rather than an uncaught exception. Today all 7 tools require caps Kora holds, so this path doesn't fire in normal operation; it's defensive against future cap shifts.
Forward-stability invariant
capability_check.pyimports onlyACTOR_CAPABILITY_MATRIX_KORA_COLUMNfrom the C2 mirror — no MCP / DB / network. When K-7 shipskora__read_kora_capability_rowand a follow-on KR-N swap replaces the C2 mirror with a fresh-per-call MCP fetch, only that one import line changes — the helper's public surface stays identical.Tested explicitly:
test_module_has_no_network_or_db_imports_at_load_time— checksasyncpg,mcp,httpx,aiohttpabsent from module source.test_capability_check_only_imports_the_mirror_dict— checks no imports of provider / connection / reads / scratchpad / events / relationlink.Test plan
11 new tests in
test_capability_check.py, all passing:actor_has_capability(5):cap_sea_create→ True (Sea-tier granted)cap_write_agent_scratchpad→ True (Plan 02 primary writer)cap_override_security_or_policy_verdict→ False (operator-only Cell C)cap_unbless_convention→ False (operator-only, added 2026-05-20)KeyErrorwith diagnostic messageassert_kora_can_perform(4):CapabilityDeniedErrorwith.capability+ default.reasonreason=overrides defaultKeyErrorpropagates (NOT CapabilityDeniedError)Forward-stability (2):
Existing tests updated (3 files, parametrize flipped):
test_iso_node_tools.py— deletedtest_assert_kora_can_perform_stub_logs_deviation_id; added 3 replacement tests (granted-passes / denied-raises / unknown-raises-keyerror).test_iso_link_tools.py— flipped…_capability_check_logs_deviationto…_passes_silently(asserts no denial envelope + ok=True).test_tool_finalize.py— flipped parametrizedtest_every_iso_tool_logs_capability_stub_deviationtotest_every_iso_tool_passes_capability_check_for_granted_caps(asserts no denial envelope across all 7 tools).Gates
ty check— 7,337 diagnostics, zero-delta vs KR-3 ST3 baseline.pytest tests/plugins/memory/— 326/326 passing (11 new + 11 net from refactored deferred-log tests + 304 pre-KR-6).-n auto): 24,649 passed / 142 failed / 129 skipped. Δ vs KR-3 ST3 (24,625/153/129): +24 passed, −11 failed. Sametests/tools/*+tests/skills/*xdist isolation noise; none touchplugins/memory/isokron/.Rule-6 / BUILD_DEVIATIONS / Open asks
Closed:
D-kr3-st1-capability-check-deferredmoved from Open to a new Closed section inBUILD_DEVIATIONS.mdwith the KR-6 spec quote + the forward-stability invariant inline.4 deferrals remain open pending CC#1 substrate buckets:
D-kr2-st2-capability-matrix-mirrorD-kr2-st3-no-scratchpad-write-mcp-toolD-kr2-st4-no-chain-emit-mcp-toolD-kr3-st2-no-relationlink-write-mcp-toolAll four follow the same shape: signature is forward-stable, body swaps from
raise <DeferredError>tomcp_client.invoke(...)when the substrate dependency lands.README "Operator pitfalls" table trimmed from 5 to 4 rows; a "Recently closed" note replaces the entry so operators see the resolution at a glance.
Standing by for the K-7/K-8/K-9/K-10 substrate dispatches → 4 small mechanical follow-on KR-N swap PRs on this lane.
🤖 Generated with Claude Code