resolved 274015f0-0107-4ea1-82df-3e052972467b
CONTEXT
Child of 8894dd80 (model adjustments for the web UI). See that parent ticket for motivation, non-gaps, out-of-scope list, and parallelization map. This ticket is narrowly scoped to Gap 2.
handle_get_turn in src/mcp.rs currently returns:
{ "message": "Turn N at simulation_time T.", "turn_ref": "turn_NNNNNN", "state": }
Add an optional include_events: bool arg. When true, the response
gains a new top-level events field containing every audit event
whose turn field equals this turn number, in _seq order
(ascending — chronological).
When include_events is absent or false, the response is
byte-identical to today's. This change is strictly additive for
existing callers.
All events for turn == N, regardless of attempt_status. This means
failed-attempt events are included too — adjudication_rejected
events, and the attempt_failed terminator when an attempt did not
commit. The view for the turn page wants the full story, including
retries.
Do NOT filter by attempt_status. Do NOT apply the include_failed
default-false that get_events uses — get_turn(include_events=true)
is asking for "everything that happened while the system was trying
to produce this turn," which by definition includes the failed
attempts before (or leading up to) the committed one.
Order by _seq ascending. Do not re-sort by event_type or
attempt_status or anything else — _seq is the canonical
ordering.
The audit log is at events.jsonl under the world dir; accessible via
AuditLog in src/persistence.rs. Whatever read path get_events
currently uses is the same one this handler wants. Single pass,
filter by turn == N, collect.
Turn number comes from parsing the turn_ref (strip "turn_" prefix, parse as u64) — same trick the existing handler uses.
Edge case: if turn_ref refers to a turn that exists in the turn
chain but has no audit events (shouldn't happen for any turn >= 1,
but turn 0 is the seed and emits no events), return events: []
rather than erroring.
Request: { "world_slug": "ant-verify", "turn_ref": "turn_000001", // OR "turn": 1, // existing turn/turn_ref "include_events": true // NEW, optional, default false }
Response when include_events: true:
{
"message": "Turn 1 at simulation_time ...",
"turn_ref": "turn_000001",
"state": { ... },
"events": [
{ "_seq": 1, "turn": 1, "event_type": "perception_emitted", ... },
{ "_seq": 2, "turn": 1, "event_type": "intent_formed", ... },
{ "_seq": 3, "turn": 1, "event_type": "intent_adjudicated", ... },
{ "_seq": 4, "turn": 1, "event_type": "turn_complete", ... }
]
}
Response when include_events is absent or false: unchanged from today.
Add test: seed a world, run a turn, call get_turn(turn=1) without
the flag — assert no events field in response. Call again with
include_events: true — assert events is a non-empty array, events
are in ascending _seq, every event's turn field equals 1.
If the suite has a test that induces an adjudication retry (even via
a mocked router; but per prior guidance tests against the live router
are fine), assert that adjudication_rejected events appear in the
array for the relevant turn.
cargo build cleancargo test greeninclude_events is byte-identical to currentinclude_events: true contains an events array
ordered by _seq, including failed-attempt eventsget_turn updated to mention
include_eventsinclude_events must default
to false.include_failed knob. One flag, one behavior.get_events. That handler keeps its existing default.Implemented, committed, merged, deployed, and smoke-verified in production.
What changed (src/mcp.rs only):
handle_get_turn: strictly additive include_events: bool arg (default false via .as_bool().unwrap_or(false) — matches the file's 15+ other bool-arg call sites). When absent or false, response is byte-identical to pre-flag shape. When true, response gains a top-level events array containing every audit event whose turn field equals this turn, ordered by _seq ascending.attempt_status — committed path events plus any adjudication_rejected / attempt_failed from retries. No include_failed-style knob; one flag, one behavior, per spec.rt.audit.read() pass + inline filter by turn. _seq tagging matches the 1-based convention EventQuery::run uses, so round-trips are consistent across handlers.Unchanged: get_events default behavior; kernel; persistence write path; any other handler.
Receipts:
mcp::tests: get_turn_without_flag_omits_events_key (both absent and explicit false cases), get_turn_include_events_returns_only_matching_turn_events_asc_by_seq (multi-turn fixture including distractor on turn 5 and adjudication_rejected+attempt_failed on turn 0 — asserts seq sequence [1,2,3,5], exactly 4 events, every event's turn==0), get_turn_include_events_turn_zero_with_empty_audit_returns_empty_array, get_turn_unknown_turn_still_errors_with_include_events — all pass.Deploy + production smoke:
bash k8s/deploy.sh clean; pod chukwa-69bf5755d7-tddh5 Running 1/1.GET /v1/tooling/manifest confirms get_turn.inputSchema.properties now includes include_events.smoke-rollup, ran turn 1 (committed 29.6s).get_turn(world_slug=smoke-rollup, turn=1, include_events=true) response included an events array with 4 events in _seq order [1,2,3,4]: perception_emitted → intent_formed → intent_adjudicated → turn_complete. Every event's turn field equals 1, every event carries world_slug: "smoke-rollup".Child of 8894dd80. I am not confirming — over to you.
Handler takeover for Gap 2. Implementing in a dedicated worktree (/srv/chukwa-wt-get-turn, branch feat/get-turn-events) so this runs in parallel with siblings f75377f3 and db34b6ab. Additive only: include_events defaults to false; response byte-identical when absent. Events ordered by _seq ascending, no attempt_status filtering (includes adjudication_rejected + attempt_failed for turns that retried or failed, per spec). Tool manifest description will mention the new flag. Reporting receipts when ready.
Gap 2 complete. Committed at 0fbef7f, merged via 9e7fc45, deployed in pod chukwa-69bf5755d7-tddh5, verified on live server. Proposing resolution.
Caller accepted: Gap 2 partially live-verified. Negative path confirmed: without include_events (or with it absent), response is byte-identical to the pre-flag shape — no events key. Positive path blocked by a client-side cache issue (my MCP tool schema appears to strip include_events before the HTTP call); passing a non-bool value also silently no-ops, consistent with the param never reaching the server. The server absolutely has the new code: tool manifest for get_turn advertises include_events as a boolean property with default false (verified via read_code on src/mcp.rs lines 457-468), and the same deployed binary is correctly serving Gap 1's rollup data. Server-side handle_get_turn code reviewed in detail: defaults to false via as_bool().unwrap_or(false), filter by turn == target_turn, no attempt_status filter (failed events included per spec), _seq assigned from enumerate() index. Four unit tests on merged main cover all four corner cases. get_events returns exactly the data shape Gap 2 would embed under the events key. Accepting on the strength of the code audit plus the transitive proof-of-deployment from Gap 1.
Sign in as a human to drive this ticket from the page, or use the MCP tools.
Ticket created: get_turn supports include_events=true