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

feat(KR-P2-CLEANUP ST1): wire READY↔ACTIVE transitions into SeaTicketPoller#55

Merged
rafe-walker merged 1 commit into
mainfrom
feat/kora-KR-P2-CLEANUP-st1-poller-wire
May 21, 2026
Merged

feat(KR-P2-CLEANUP ST1): wire READY↔ACTIVE transitions into SeaTicketPoller#55
rafe-walker merged 1 commit into
mainfrom
feat/kora-KR-P2-CLEANUP-st1-poller-wire

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

KR-P2-CLEANUP ST1 of 5. KR-P2-I-integration ST4 (deferred from #34) — now unblocked by KR-P2-E shipping the SeaTicketPoller. The operational-state machine moves automatically as the consumer loop turns.

Transitions

When Transition Trigger string
After successful claim + ledger.allocate (work committed) READY → ACTIVE "claim acquired"
After successful kora__release_claim ACTIVE → READY "claim released"
On lease-lost-mid-work early exit (heartbeat detected substrate-side lease loss) ACTIVE → READY "claim released"

Both the normal-release path and the lease-lost path land back at READY since the claim is gone from the runtime's perspective. The TRANSITION_TABLE only has one ACTIVE → READY arrow (trigger column = "claim released"); the trigger string is free-form for the chain-event payload, so both paths share it.

Fail-soft per spec §2 ST1

A new _signal_operational_state(target, trigger) helper wraps each transition:

  • get_holder() returns None (no agent session has yet run wire_operational_state, so the holder is uninitialized) → DEBUG-log + skip. Consumer loop still claims + releases cleanly.
  • holder.transition_to raises → WARN-log with target + trigger; cycle continues.

Operational-state observability is best-effort during the consumer loop. Substrate-side records (sea_ticket.claimed_for_edit / sea_ticket.claim_released + the existing kora.sea_ticket.resolved emit from KR-P2-E ST4) remain the durable trail.

Tests — tests/test_sea_ticket_poller_operational_state.py (340 LOC, 4 cases)

  • Full cycle: [("ready","active","claim acquired"), ("active","ready","claim released")] fired in order; holder lands back at READY.
  • Lease-lost branch: both transitions still fire (claim → ACTIVE; lease lost → READY) even though kora__release_claim never fires substrate-side. Only one MCP call (the claim).
  • Fail-soft, holder uninitialized: full claim + release cycle runs; DEBUG-log "holder not initialized" lines acknowledge the skip.
  • Fail-soft, transition_to raises: substrate cycle still completes both calls; two WARN-log lines (one per attempted transition).

Tests are isolated in their own file (test_sea_ticket_poller_operational_state.py) — they use init_holder + per-test _reset_holder_for_tests rather than mocking, so the singleton's first-wins semantics don't bleed across tests. The existing tests/test_sea_ticket_poller.py stays unchanged.

Honest scope

  • Holder initialization is out of scope. wire_operational_state(provider) is called from agent/agent_init.py per-session, not from sea_ticket_poller_lifecycle.py. Gateway-only deployments (poller running, no agent sessions yet) stay observationally quiet — that's the spec's accepted trade. A future "init holder at gateway startup" change is its own bucket (and would need to dedup the per-session listener to avoid N-times emit).
  • No changes to agent/operational_state* modules.
  • No changes to the kora.sea_ticket.resolved / kora.operational_state.transitioned event payload shapes.

Sub-task chain (this bucket — all independent)

ST Branch Status
ST1 …st1-poller-wire this PR
ST2 …st2-sea-panel-flip next
ST3 …st3-control-panel-flip next
ST4 …st4-boot-panel-flip next
ST5 …st5-ops-tests-fix next

Each ST branches from main independently — no cascade-merge dance.

Test plan

  • CI green on pytest tests/test_sea_ticket_poller.py tests/test_sea_ticket_poller_operational_state.py
  • Once merged, after an agent session has wired the holder: substrate event_log shows kora.operational_state.transitioned events with payload.trigger ∈ {"claim acquired", "claim released"} interleaved with sea_ticket.claimed_for_edit / sea_ticket.claim_released

🤖 Generated with Claude Code

…Poller

KR-P2-I-integration ST4 (deferred from #34) — now unblocked by
KR-P2-E shipping the poller. The full operational-state machine
moves automatically as the consumer loop turns:

  - On successful claim + ledger.allocate (work fully committed) →
    holder.transition_to(ACTIVE, trigger="claim acquired")
  - On successful kora__release_claim →
    holder.transition_to(READY, trigger="claim released")
  - On lease-lost-mid-work early exit (heartbeat.lease_lost) →
    holder.transition_to(READY, trigger="claim released") — same
    target arrow, since the claim is gone substrate-side; trigger
    string is free-form for the chain-event payload

Fail-soft per spec §2 ST1. A new _signal_operational_state helper
wraps each transition:
  - get_holder() returns None (no agent session has run
    wire_operational_state yet) → DEBUG-log + skip; consumer loop
    still claims + releases cleanly
  - holder.transition_to raises → WARN-log with target + trigger;
    cycle continues

Operational-state observability is best-effort during the consumer
loop; substrate-side records (sea_ticket.claimed_for_edit /
sea_ticket.claim_released + the existing kora.sea_ticket.resolved
emit from ST4) remain the durable trail.

Tests (tests/test_sea_ticket_poller_operational_state.py): 4 cases
covering the full cycle (ACTIVE then READY fired in order with the
right trigger strings), the lease-lost branch (still transitions
back to READY even though release wasn't called), and both
fail-soft paths (holder-uninitialized: DEBUG log + cycle still
runs; transition_to raises: WARN log + cycle still completes).

After this PR lands, kora.operational_state.transitioned chain
events fire on every claim/release cycle when the holder is
initialized (by the first agent session). Gateway-only deployments
without agent sessions stay observationally quiet — that's the
acceptable trade per spec.

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