Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 47 additions & 23 deletions BUILD_DEVIATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,31 +93,55 @@ Format:
`test_write_scratchpad_entry_raises_deferred_write_error` asserts
the error message + tag stay correct.

### D-kr2-st2-capability-matrix-mirror

- **Bucket**: KR-2 ST2 (IsoKron memory provider — read paths)
- **Why**: Sea MCP server does not expose a `kora__read_kora_capability_row`
tool on main (`b0804640`, 2026-05-20). The `ACTOR_CAPABILITY_MATRIX`
source of truth is a TS const at
`packages/sea-mcp-server/src/capability-matrix.ts`, not a Postgres
table — so Approach B (asyncpg SELECT) is not viable. PM-decided
STOP-gate resolution on 2026-05-20: ship C2 (Python mirror) now
rather than block CC#3 on a CC#1 dependency.
- **Closes when**: K-7 (Sea MCP capability-row tool) ships — at that
point the read path swaps to call the MCP tool, the Python mirror
becomes a test fixture only, and the parity test stays in place
as a smoke check across CI configurations that still hit the
mirror as a fallback. PM is drafting the K-7 bucket.
## Closed

### D-kr2-st2-capability-matrix-mirror — closed by KR-7b (2026-05-20)

- **Bucket**: KR-2 ST2 (capability matrix Kora row)
- **Resolved by**: KR-7b — `populate_capability_matrix_from_mcp` in
`plugins/memory/isokron/capability_matrix_mirror.py` fetches the
authoritative Kora-row matrix from K-7's `kora__read_kora_capability_row`
Sea MCP tool (substrate `ee730853`) at `IsoKronMemoryProvider.initialize()`
via the KR-7a-wired `IsoKronMCPClient`, replacing the hand-mirrored
49-entry C2 dict in place. The dict identity is preserved, so
`capability_check.actor_has_capability` keeps consuming it by
reference — no caller-side refactor needed. Forward-stable: K-13's
upcoming capability additions flow through automatically at the
next provider start.
- **Spec quote** (KR-7b § 90): *"KR-7b ships boot-time MCP fetch
replacing hand-mirrored TS-source dict. K-7 (ee730853) shipped the
substrate-side tool. Forward-stable; KR-7a transport wiring is
independent and may eventually consolidate to a unified MCP client."*
CC#3 elected Option A (use KR-7a's `IsoKronMCPClient`) over Option
B (parallel httpx fetch path) since KR-7a is shipped — one
canonical MCP-call pattern across all closure swaps.
- **Production-test posture** (same as KR-7): K-7's handler is a
`notImplementedHandler` stub on substrate main; dispatch tier
(queued substrate-team) un-stubs it. KR-7b's code shape is sound;
mock tests verify the populate machinery; production deploys wait
on dispatch tier. On fetch failure, hand-mirrored fallback stays in
place + `[kora.capability_matrix.fallback]` WARNING logged so
dev/test ergonomics survive substrate downtime.
- **Hand-mirrored fallback retained**: 49-entry C2 dict stays as the
default at module import — same content, repurposed from "C2
interim" to "dev/test fallback". The parity test at
`tests/plugins/memory/test_capability_matrix_parity.py` keeps
guarding the fallback against TS-source drift so dev parity matches
production-substrate parity (and so when K-13 ships, the parity
test catches the 2-line bump that the fallback needs even though
production picks up the new caps automatically).
- **Guarded by**:
- `plugins/memory/isokron/capability_matrix_mirror.py` — top-of-file
`[kora.isokron.todo]` tag.
- `tests/plugins/memory/test_capability_matrix_parity.py` —
parses the TS source and asserts every cap_name → kora_value
matches the Python mirror in both directions.
- `plugins/memory/isokron/README.md` § "Operator pitfalls" —
operator-facing drift notice.
- `tests/plugins/memory/test_capability_matrix_mcp_fetch.py` —
11 tests covering happy populate, in-place dict mutation, defensive
error paths (None client / missing key / non-dict / non-bool /
non-str / propagated underlying error), and provider.initialize
success-INFO + dual-fallback-WARNING paths.
- `tests/plugins/memory/test_provider_end_to_end.py` —
`_FakeMcpClient.invoke` routes by tool_name and returns a
canonical-shape matrix for `kora__read_kora_capability_row`;
E2E asserts the initialize-time fetch fired + replaced the dict.

## Closed
### D-kr2-st4-no-chain-emit-mcp-tool — closed by KR-7 (2026-05-20)

### D-kr2-st4-no-chain-emit-mcp-tool — closed by KR-7 (2026-05-20)

Expand Down
29 changes: 17 additions & 12 deletions plugins/memory/isokron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,28 +93,33 @@ This `README.md` ships with **KR-2 ST1**, which delivers the structural skeleton

## Operator pitfalls

### Deferred-surface summary (3 open BUILD_DEVIATIONS as of KR-7)
### Deferred-surface summary (2 open BUILD_DEVIATIONS as of KR-7b)

All three follow the same shape: signature is forward-stable, body
swaps from `raise <DeferredError>` to `mcp_client.invoke(...)` when
the substrate-side dependency lands. **No caller refactor needed**.
Both follow the same shape: signature is forward-stable, body swaps
from `raise <DeferredError>` to `mcp_client.invoke(...)` when the
substrate-side dependency lands. **No caller refactor needed**.
Operators grep the deviation_id in logs to track defer rates.

| Deviation | What's deferred | Closes when |
|---|---|---|
| `D-kr2-st2-capability-matrix-mirror` | C2 Python mirror of `ACTOR_CAPABILITY_MATRIX` Kora column (parity test guards drift) | K-7→KR-N swap (Sea MCP `kora__read_kora_capability_row` shipped 2026-05-20; PM drafts swap bucket) |
| `D-kr2-st3-no-scratchpad-write-mcp-tool` | Scratchpad writes from `sync_turn` / `on_memory_write` / `iso_node_create` / `iso_node_supersede` | K-8 ships Sea MCP `kora__write_agent_scratchpad` |
| `D-kr3-st2-no-relationlink-write-mcp-tool` | `iso_link_create` writes — 3 substrate blockers in one (actor_kind CHECK + missing MCP tool + chain_event_id SECDEF) | K-10 ships the bundled substrate bucket |
| `D-kr2-st3-no-scratchpad-write-mcp-tool` | Scratchpad writes from `sync_turn` / `on_memory_write` / `iso_node_create` / `iso_node_supersede` | K-8 ships Sea MCP `kora__write_agent_scratchpad` (now merged → KR-8 dispatchable) |
| `D-kr3-st2-no-relationlink-write-mcp-tool` | `iso_link_create` writes — 3 substrate blockers in one (actor_kind CHECK + missing MCP tool + chain_event_id SECDEF) | K-10 ships the bundled substrate bucket (now merged → KR-9 dispatchable) |

**Recently closed**:
- `D-kr2-st2-capability-matrix-mirror` — KR-7b replaced the hand-
mirrored C2 dict with an authoritative MCP fetch via
`kora__read_kora_capability_row` at provider initialize. The
hand-mirrored 49 entries stay as a dev/test fallback (parity test
still guards drift) so substrate downtime falls back to the same
data dev sees. Production posture per IsoKron PM #27: same as
KR-7 — substrate dispatch tier un-stubs the K-7 handler;
operators grep `[kora.capability_matrix.fallback]` to confirm
fetch health.
- `D-kr2-st4-no-chain-emit-mcp-tool` — KR-7 swapped `emit_kora_event`
to route through `kora__append_event` via the KR-7a-wired
`IsoKronMCPClient`. Substrate-side failures surface as
`IsoKronMCPInvocationError` logged at ERROR; lifecycle hooks catch
+ log so the session stays alive. Production-test posture per
IsoKron PM #27: substrate-team dispatch tier (queued) un-stubs the
K-9 handler; live emits will fail until then but the code shape is
correct.
+ log so the session stays alive.
- `D-kr3-st1-capability-check-deferred` — KR-6 shipped the Python
`actor_has_capability` helper at
`plugins/memory/isokron/capability_check.py`. Every `iso_*` tool
Expand Down Expand Up @@ -171,7 +176,7 @@ idempotency) is fully tested with mocked transports.

* **Scratchpad BLAKE3 integrity is warn-only, NOT fail-closed.** Unlike the Role Charter (which raises on hash mismatch), `read_own_scratchpad` and `read_cross_agent_scratchpad` log a WARNING and return the entry on mismatch. Spec § ST3: scratchpad is mutable working memory; refusing to surface a drifted entry would block sessions on transient state. Operators monitoring chain-of-custody should grep `content_hash drift` in logs.

* **Capability matrix is currently a Python mirror; parity test guards drift but a Sea MCP tool is the proper substrate path.** KR-2 ST2 ships `plugins/memory/isokron/capability_matrix_mirror.py` as a hand-translated copy of `ACTOR_CAPABILITY_MATRIX`'s Kora column (the TS const at `packages/sea-mcp-server/src/capability-matrix.ts`). A parity test (`tests/plugins/memory/test_capability_matrix_parity.py`) reads the TS source at test time and asserts every `cap_name → kora_value` matches in both directions. **CI must set `KORA_ISOKRON_REPO` to point at a cloned IsoKron substrate**, otherwise the parity test skips silently and drift won't be caught. Tracked as `D-kr2-st2-capability-matrix-mirror` in `BUILD_DEVIATIONS.md`; closes when K-7 (Sea MCP `kora__read_kora_capability_row` tool) ships.
* **Capability matrix is now MCP-fetched at provider initialize (KR-7b).** `IsoKronMemoryProvider.initialize()` calls `populate_capability_matrix_from_mcp` via the KR-7a-wired `IsoKronMCPClient`, replacing the hand-mirrored dict contents in place. The hand-mirrored 49 entries remain as a dev/test fallback so substrate downtime is non-fatal; the parity test (`tests/plugins/memory/test_capability_matrix_parity.py`) still guards the fallback against TS-source drift. **CI must set `KORA_ISOKRON_REPO`** for the parity test, otherwise it skips silently. Operators grep `[kora.capability_matrix.fallback]` in production logs to confirm the substrate fetch is succeeding (per IsoKron PM #27 production-test posture, the K-7 handler is currently a `notImplementedHandler` stub awaiting dispatch tier; until then the fallback is the operating state). Closed as `D-kr2-st2-capability-matrix-mirror` in `BUILD_DEVIATIONS.md`.

* **Policy registry reads MUST set the RLS GUC inside the transaction.** `kora_policy_registry` has `ENABLE ROW LEVEL SECURITY` with a policy keyed off `current_setting('app.current_workspace_id', true)`. The reader calls `SELECT set_config('app.current_workspace_id', $1, true)` before the SELECT; without that, the query returns 0 rows silently. If you bypass `reads.read_kora_policy_registry` and write your own query, replicate the pattern or you'll get a confusing empty result.

Expand Down
138 changes: 114 additions & 24 deletions plugins/memory/isokron/capability_matrix_mirror.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,58 @@
"""Python mirror of the Kora column of ``ACTOR_CAPABILITY_MATRIX``.

[kora.isokron.todo] **C2 INTERIM** — swap to ``kora__read_kora_capability_row``
MCP tool when K-7 (Sea MCP capability-row tool) lands. PM-decided
2026-05-20 STOP-gate: blocking CC#3 on a CC#1 dependency to ship KR-2
ST2 burns days; C2 mirror is the unblock path. A parity test
(``tests/plugins/memory/test_capability_matrix_parity.py``) guards
drift against the TS source.

Source of truth:
``packages/sea-mcp-server/src/capability-matrix.ts`` in the IsoKron
substrate repo. Specifically the ``ACTOR_CAPABILITY_MATRIX`` const's
Kora column for each entry in ``SEA_CAPABILITIES`` (24) +
``KORA_BROADER_CAPABILITIES`` (24).

BUILD_DEVIATIONS entry: ``D-kr2-st2-capability-matrix-mirror`` —
closes when K-7 lands.
"""Kora-column slice of ``ACTOR_CAPABILITY_MATRIX`` — KR-7b swap.

# Two paths, one dict

* **Production (post-KR-7b)** — ``IsoKronMemoryProvider.initialize()``
calls :func:`populate_capability_matrix_from_mcp` which fetches
``kora__read_kora_capability_row`` via the KR-7a-wired
:class:`IsoKronMCPClient` and replaces this module's dict contents.
Substrate is the authoritative source; no hand-translation drift
risk. Closes BUILD_DEVIATIONS ``D-kr2-st2-capability-matrix-mirror``.

* **Dev/test fallback (legacy C2)** — the hand-mirrored 49-entry dict
below stays as the default at module import. If the MCP fetch fails
at provider initialize (transport down, substrate unreachable, etc.),
the provider logs a ``[kora.capability_matrix.fallback]`` WARNING and
the fallback stays in place — sessions still run, capability checks
still work against the (potentially stale) dev data. The parity test
at ``tests/plugins/memory/test_capability_matrix_parity.py`` guards
the fallback against TS-source drift so dev parity matches
production-substrate parity.

# Production-test posture

Same as KR-7's chain-emit closure. K-7 (`ee730853`) shipped the
substrate-side MCP tool; substrate-team's dispatch tier (queued)
un-stubs the handler. KR-7b's code is sound; mock tests verify the
populate machinery; production deploys wait on the dispatch tier
landing. Operators grep ``[kora.capability_matrix.fallback]`` in logs
to confirm the production fetch is succeeding.

# Forward stability

When K-13 (capability-matrix tightening) ships and adds new entries
on the substrate side, the populate function picks them up
automatically at next provider start — no Python-side code change.
The hand-mirrored fallback below still needs to be bumped for dev
parity (the parity test catches it at CI), but the production path
is auto-current.

Capability names are stable strings per the TS source header:
> "Capability names are stable strings — they appear in chain events,
> audit logs, error payloads, and bundle profile definitions. Renames
> are operator-direct schema changes (PolicyRegistry / chain-event-
> vocabulary tier of stability)."

So a Python mirror is safe across normal-velocity substrate evolution.
The parity test catches additions, removals, and Kora-column flips.
"""

from __future__ import annotations

import logging
from typing import Any

from .models import KoraCapabilityRow

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# SEA_CAPABILITIES — 24 entries, Kora's column from ACTOR_CAPABILITY_MATRIX.
Expand Down Expand Up @@ -140,10 +163,10 @@
def read_kora_capability_row() -> KoraCapabilityRow:
"""Return the Kora row of ``ACTOR_CAPABILITY_MATRIX`` as a typed shape.

Sync (no network IO) — this is the C2 interim path. Once K-7 lands
the Sea MCP ``kora__read_kora_capability_row`` tool, the read path
on ``provider.py`` swaps to call that tool (async), and this
function becomes a fallback / test fixture only.
Sync (no network IO). Reads the module-level dict, which is either
the hand-mirrored fallback (default at import) or the MCP-fetched
authoritative data (after ``populate_capability_matrix_from_mcp``
runs at provider initialize).

Returns:
``KoraCapabilityRow`` with ``granted`` = frozenset of cap names
Expand All @@ -156,3 +179,70 @@ def read_kora_capability_row() -> KoraCapabilityRow:
cap for cap, allowed in ACTOR_CAPABILITY_MATRIX_KORA_COLUMN.items() if not allowed
)
return KoraCapabilityRow(actor_kind="kora", granted=granted, denied=denied)


# ---------------------------------------------------------------------------
# KR-7b — MCP-backed population at provider initialize
# ---------------------------------------------------------------------------


CAPABILITY_MATRIX_MCP_TOOL = "kora__read_kora_capability_row"
"""Name of the K-7 Sea MCP tool that returns the Kora-row matrix."""


async def populate_capability_matrix_from_mcp(mcp_client: Any) -> int:
"""Fetch ``kora__read_kora_capability_row`` + replace this module's dict.

Mutates ``ACTOR_CAPABILITY_MATRIX_KORA_COLUMN`` in place so all
callers that imported it by reference see the fresh data on next
access. ``capability_check.actor_has_capability`` consumes the
dict by name; no caller-side refactor needed.

Args:
mcp_client: a started :class:`IsoKronMCPClient` (typically from
``IsoKronConnection.get_mcp_client()``).

Returns:
Number of entries written (e.g. 49 today; K-13 will bump to 51).

Raises:
Any exception from ``mcp_client.invoke`` (e.g.
:class:`IsoKronMCPInvocationError`) — caller decides whether to
fail-closed or fall back to the hand-mirrored data.
``RuntimeError`` if the response shape doesn't match
``{"capability_matrix": {...}}``.
"""
if mcp_client is None:
raise ValueError(
"populate_capability_matrix_from_mcp: mcp_client is required "
"(resolve via IsoKronConnection.get_mcp_client() before calling)"
)
result = await mcp_client.invoke(CAPABILITY_MATRIX_MCP_TOOL, {})
fetched = (
result.get("capability_matrix") if isinstance(result, dict) else None
)
if not isinstance(fetched, dict):
raise RuntimeError(
f"{CAPABILITY_MATRIX_MCP_TOOL} returned unexpected shape: "
f"{result!r}; expected {{'capability_matrix': {{...}}}}"
)
# Defensive: ensure every value is a bool. K-7's TS-side Zod schema
# should guarantee this, but a substrate-side regression that ships
# non-bools would silently break ``actor_has_capability`` lookups.
bad = [
k for k, v in fetched.items()
if not isinstance(v, bool) or not isinstance(k, str)
]
if bad:
raise RuntimeError(
f"{CAPABILITY_MATRIX_MCP_TOOL} returned non-bool / non-str "
f"entries: {bad!r}"
)
ACTOR_CAPABILITY_MATRIX_KORA_COLUMN.clear()
ACTOR_CAPABILITY_MATRIX_KORA_COLUMN.update(fetched)
logger.info(
"[kora.capability_matrix] populated from %s — %d entries",
CAPABILITY_MATRIX_MCP_TOOL,
len(fetched),
)
return len(fetched)
Loading