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

feat(KR-7): swap chain-event-emit defer for kora__append_event MCP call — closes D-kr2-st4#14

Merged
rafe-walker merged 1 commit into
mainfrom
feat/kora-KR7-chain-emit-mcp-swap
May 20, 2026
Merged

feat(KR-7): swap chain-event-emit defer for kora__append_event MCP call — closes D-kr2-st4#14
rafe-walker merged 1 commit into
mainfrom
feat/kora-KR7-chain-emit-mcp-swap

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

KR-7 ships the mechanical follow-on swap unlocked by K-9 (substrate f8487059) + KR-7a (b6415692). Closes D-kr2-st4-no-chain-emit-mcp-tool.

events.py:emit_kora_event body replaces raise ChainEventEmitNotAvailableError() with await mcp_client.invoke('kora__append_event', {...}), returning the substrate-assigned event_id. ~30 LOC across events.py + provider.py + tools/iso_node.py + tests + docs — the originally-spec'd ~20-40 LOC mechanical patch shape now that KR-7a wired the MCP client transport.

Production-test posture per IsoKron PM #27

  • K-9's kora__append_event handler is a notImplementedHandler stub on substrate main; substrate-team's dispatch tier (queued) un-stubs + resolves Layer-A wsk_* auth → Layer-B actor_kind='kora'.
  • KR-7's code shape is sound; mock tests verify correctness end-to-end.
  • Production deploys wait on substrate dispatch tier landing — identical posture to KR-7a (which shipped with mock token; correctness-testable independently).
  • Verify-at-first-live-emit per spec § 3: after the first real KORA_SERVICE_TOKEN mint, confirm event_log.actor_id resolves to the 0076-seeded canonical Kora actor (actor_kind='kora' AND workspace_id=<Flynn clerk_org_id>). If it resolves to a token-UUIDv5 instead (the cowork-claude-pm precedent), small substrate patch needed. Not a KR-7 blocker — operational-correctness step.

What landed

File Change
plugins/memory/isokron/events.py emit_kora_event body swap: mcp_client.invoke('kora__append_event', {workspace_id, event_type, payload}) → returns event_id (str). Defensive: mcp_client=None raises ValueError; unexpected response shape raises RuntimeError. ChainEventEmitNotAvailableError class kept exported tagged [kora.isokron.deprecated] for one-release runway.
plugins/memory/isokron/provider.py _attempt_chain_event_emit fetches IsoKronMCPClient via self._connection.get_mcp_client() (KR-7a-wired); catches IsoKronMCPInvocationError + defensive Exception at the lifecycle boundary and logs at ERROR ([kora.chain.emit.failed]) so session hooks stay alive. Successful emits log INFO [kora.chain.emit] with the event_id and invalidate the per-workspace events cache.
plugins/memory/isokron/tools/iso_node.py _handle_iso_node_supersede's kora.node.superseded emit refactored to route through provider._attempt_chain_event_emit instead of importing emit_kora_event directly — all chain emits now share one error-handling + cache-invalidation surface.
BUILD_DEVIATIONS.md D-kr2-st4 moved Open → Closed with Rule-5 spec-quote, production-test posture, deprecation-runway note. 3 deferrals remain open.
plugins/memory/isokron/README.md Deferred-surface table trimmed 4 → 3 rows; recently-closed list gains the KR-7 closure; "Chain event emission" pitfall rewritten to reflect KR-7 reality + production-test posture.
tests/plugins/memory/test_events.py Replaced the deferred-error test with 4 MCP-call-path tests + 1 deprecation-runway test (see test plan below).
tests/plugins/memory/test_provider_end_to_end.py _FakeProviderConnection now exposes get_mcp_client() returning a _FakeMcpClient that records invokes; E2E asserts both lifecycle emits (on_delegation + on_session_end) hit kora__append_event with the spec-pinned arg shape. Scratchpad-write deferrals stay at 3 (D-kr2-st3 still open).

Error handling — PM-lean propagate at function level, catch at lifecycle boundary

Per spec § 100 ("PM-lean: propagate, since chain event failure is a substrate-side issue worth surfacing"):

  • emit_kora_event propagates IsoKronMCPInvocationError directly — no catching at the function level.
  • provider._attempt_chain_event_emit catches at the lifecycle boundary (on_session_end / on_delegation / etc. can't crash on a single bad emit) and logs at ERROR rather than WARNING so the failure is operator-visible.
  • Operators grep [kora.chain.emit.failed] in logs to find dropped events; [kora.chain.emit] for successes.

Test plan

5 new tests in test_events.py (replacing 1 deferred-path test):

  • test_emit_kora_event_invokes_kora__append_event_with_expected_args — happy path: invokes the tool with {workspace_id, event_type, payload} exactly; returns the substrate event_id.
  • test_emit_kora_event_propagates_mcp_invocation_error — substrate IsoKronMCPInvocationError propagates unchanged.
  • test_emit_kora_event_rejects_none_mcp_client — defensive ValueError for caller misuse.
  • test_emit_kora_event_rejects_unexpected_response_shape — defensive RuntimeError if substrate response drifts.
  • test_chain_event_emit_not_available_error_still_importable_post_kr7 — deprecation runway intact; message now flags [kora.isokron.deprecated].

E2E test (test_provider_end_to_end.py) updated to post-KR-7 reality:

  • _FakeProviderConnection.get_mcp_client() returns a _FakeMcpClient recording invokes.
  • Asserts 3 scratchpad-write WARNINGs (D-kr2-st3 still open) + 2 [kora.chain.emit] INFO logs (on_delegation + on_session_end emits succeed).
  • Asserts the fake client received exactly 2 kora__append_event invokes with {workspace_id, event_type, payload} arg shape.

Gates

  • ty check7,337 diagnostics, zero-delta vs KR-7a baseline. All targeted-file diagnostics resolved.
  • pytest tests/plugins/memory/351/351 passing.
  • Full suite via xdist (-n auto): 24,561 passed / 203 failed / 52 errors / 129 skipped. Higher xdist isolation variance this run (kanban_tools test_board_param_* errors — not reproducible in isolation; same family as documented KR-2/KR-3/KR-7a noise). None touch plugins/memory/isokron/.

Rule-6 / BUILD_DEVIATIONS / Open asks

Closed: D-kr2-st4-no-chain-emit-mcp-tool (KR-7).

3 deferrals remain open:

Deviation Closes when
D-kr2-st2-capability-matrix-mirror KR-N swap (K-7 already merged; PM drafts)
D-kr2-st3-no-scratchpad-write-mcp-tool K-8 + follow-on KR-N
D-kr3-st2-no-relationlink-write-mcp-tool K-10 + follow-on KR-N

PM dispatch note flagged KR-7b (capability-row C2→MCP swap, closes D-kr2-st2) as the next parallel-dispatchable on this lane. Standing by for that dispatch (or the K-8 / K-10 follow-on swaps as their substrate buckets land).

🤖 Generated with Claude Code

…ll — closes D-kr2-st4

Mechanical follow-on swap unlocked by K-9 (substrate `f8487059`) +
KR-7a (`b6415692`). events.py:emit_kora_event body replaces
`raise ChainEventEmitNotAvailableError()` with
`await mcp_client.invoke('kora__append_event', {...})` returning the
substrate-assigned event_id. ~30 LOC across events.py + provider.py +
tools/iso_node.py + tests + docs.

Production-test posture per IsoKron PM #27:
* K-9's kora__append_event handler is a notImplementedHandler stub on
  substrate main; substrate-team's dispatch tier (queued) un-stubs +
  resolves Layer-A wsk_* auth → Layer-B actor_kind='kora'.
* KR-7's code shape is sound; mock tests verify correctness.
* Production deploys wait on substrate dispatch tier landing —
  identical posture to KR-7a (token-not-yet-issued; ships green).
* Verify-at-first-live-emit step per spec § 3: confirm
  event_log.actor_id resolves to the 0076-seeded canonical Kora actor
  (actor_kind='kora' AND workspace_id=<Flynn clerk_org_id>); if it
  resolves to a token-UUIDv5 instead (cowork-claude-pm precedent),
  small substrate patch needed.

events.py:
* emit_kora_event body: live call to mcp_client.invoke. K-9 contract
  output: {'event_id': '<uuid>'} — projected to a plain str return.
* Defensive: None mcp_client raises ValueError ("caller must resolve
  via IsoKronConnection.get_mcp_client() before calling"); unexpected
  response shape raises RuntimeError.
* mcp_client param is now required (was Optional with deferred-error
  default). Forward signature stays caller-stable from KR-2 ST4.
* ChainEventEmitNotAvailableError class kept exported tagged
  [kora.isokron.deprecated] for one release so any downstream pinned
  tests resolve. Will be removed when KR-N audits show no remaining
  references.

provider.py:_attempt_chain_event_emit:
* Stop catching ChainEventEmitNotAvailableError (no longer raised).
* Fetch IsoKronMCPClient via self._connection.get_mcp_client() (wired
  in KR-7a); failure to obtain the client is caught + logged at
  ERROR ([kora.chain.emit.failed] ... MCP client unavailable: ...)
  so lifecycle hooks (on_session_end / on_delegation / on_memory_write
  / sync_turn) stay alive on substrate downtime.
* Catch IsoKronMCPInvocationError + any defensive Exception at the
  lifecycle boundary — log at ERROR (not WARNING) since chain-event-
  emit failure is operator-visibility-worthy per PM-lean ("substrate-
  side issue worth surfacing"). Sessions don't crash; emits drop with
  a loud log line.
* Successful emits log INFO [kora.chain.emit] with the event_id and
  invalidate the per-workspace events cache so the next
  system_prompt_block §6 re-reads.

tools/iso_node.py:_handle_iso_node_supersede:
* The kora.node.superseded emit call site previously imported
  emit_kora_event directly and passed mcp_client=None. Refactored to
  route through provider._attempt_chain_event_emit so all chain-emit
  paths share the same error-handling + cache-invalidation logic.

Tests:
* test_events.py — replaced test_emit_kora_event_raises_deferred_
  write_error with four MCP-call-path tests:
  - invokes 'kora__append_event' with expected arg shape; returns
    the substrate event_id.
  - propagates IsoKronMCPInvocationError on substrate-side error.
  - rejects None mcp_client with ValueError (defensive).
  - rejects unexpected response shape with RuntimeError (defensive).
  Plus one deprecation-runway test asserting the
  ChainEventEmitNotAvailableError class is still importable post-KR-7.
* test_provider_end_to_end.py — _FakeProviderConnection now exposes
  get_mcp_client() returning a _FakeMcpClient that records invokes.
  E2E asserts both emits (on_delegation + on_session_end) hit
  kora__append_event with the spec-pinned tool name + arg shape; logs
  [kora.chain.emit] INFO each time. Scratchpad-write deferrals stay
  at 3 (D-kr2-st3 still open).
* test_isokron_provider_skeleton.py — unchanged (didn't reference
  D-kr2-st4 specifics).

BUILD_DEVIATIONS:
* D-kr2-st4-no-chain-emit-mcp-tool moved from Open to Closed with
  Rule-5 spec-quote + production-test posture + deprecation-runway
  note. 3 deferrals remain open (D-kr2-st2 / D-kr2-st3 / D-kr3-st2).

README "Operator pitfalls":
* Deferred-surface table trimmed 4 → 3 rows. Recently-closed list
  gains the KR-7 closure under D-kr2-st4.
* Individual "Chain event emission deferred" pitfall replaced with
  the KR-7-shape description: routes via kora__append_event;
  successful emits log [kora.chain.emit] INFO; substrate failures log
  [kora.chain.emit.failed] ERROR; production posture flagged.
* D-kr2-st2 row updated — K-7 already merged 2026-05-20; PM drafts
  the swap bucket.

Local gates:
* ty check — 7,337 diagnostics, zero-delta vs KR-7a baseline.
* pytest tests/plugins/memory/ — 351/351 passing.
* Full suite via xdist (-n auto): 24,561 / 203 failed / 52 errors /
  129 skipped. Higher xdist isolation variance this run (kanban_tools
  test_board_param errors aren't reproducible in isolation; none
  touch isokron).

3 deferrals still open pending CC#1 substrate buckets:
* D-kr2-st2-capability-matrix-mirror ← KR-N swap (K-7 merged; PM drafts)
* D-kr2-st3-no-scratchpad-write-mcp-tool ← K-8
* D-kr3-st2-no-relationlink-write-mcp-tool ← K-10

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit e7b3b4a into main May 20, 2026
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