resolved f75377f3-931f-4eac-ba04-cf27f7fbb7dd
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 1.
handle_list_turns in src/mcp.rs currently returns rows shaped:
{ "turn": , "turn_ref": "turn_NNNNNN", "simulation_time": "", "entity_count": // count of world entities at that turn }
Add two fields to each row:
{ ...existing fields..., "events_emitted": , // count of audit events with turn == this turn, // filtered to the committed attempt "entities_touched": [] // sorted, deduped, taken from the // committed intent_adjudicated event(s) // for this turn }
Both values are computed server-side from the audit log for this turn.
Do not change entity_count (it's a different thing — the roster size
at that turn, not an event-derived value).
The audit log is events.jsonl under the world's data dir, accessible
through AuditLog in src/persistence.rs. Each line is an event with
at minimum:
{ "_seq": , "turn": , "attempt_id": "", "attempt_status": "committed" | "failed", "event_type": "perception_emitted" | "intent_formed" | "intent_adjudicated" | "adjudication_rejected" | "turn_complete" | "attempt_failed", ... }
For events_emitted: count all events where turn == N AND
attempt_status == "committed". This excludes failed-attempt noise
from the count. The count should NOT include the turn_complete
terminator itself, OR it should — handler's call; pick one and document
the choice in a code comment. (My preference: include turn_complete.
It IS an audit event emitted during that turn. Consistent with the
count the kernel already logs at commit time.)
For entities_touched: read any intent_adjudicated events for the
turn (turn == N, attempt_status == "committed", event_type == "intent_adjudicated"). Each such event carries an entities_touched
array. Union them across all adjudication events for the turn, sort,
dedupe, return.
Multiple agents per world is currently out-of-scope but the scheme must not blow up if it ever grows — hence "union across adjudication events for the turn."
handle_list_turns currently loops over turn refs and calls
rt.turns.read_ref(r) for each. Don't add a per-turn call to scan
the entire audit log — that's O(T × E). Instead, do ONE pass over the
audit log up front, building a HashMap<u64, PerTurnRollup> keyed by
turn number, where:
struct PerTurnRollup { events_emitted: u32, entities_touched: BTreeSet, }
Then while assembling each row, look up the rollup by turn number.
Missing turn → both fields are zero/empty (turn 0, the seed, has no
events; it should emit events_emitted: 0 and entities_touched: []).
The AuditLog::scan_all (or whatever read-path exists — check
src/persistence.rs) reads sequentially. Single pass, then O(1)
lookup per row.
Add unit or integration test: seed a world, run two turns, call
list_turns, assert each row has events_emitted and
entities_touched. The ant scenario is the obvious fixture —
turn 0 should show events_emitted: 0, entities_touched: [];
turn 1 should show events_emitted: 4 (or 3 depending on the
turn_complete-inclusion choice above), entities_touched: ["ant"].
Include this test in tests/ant_scenario.rs or add a new case to
tests/phase0.rs using the phase-0 inert scenario if a non-LLM
fixture is reachable. The existing live-router test coverage is
acceptable too; the ticket doesn't mandate one over the other.
cargo build cleancargo test greenlist_turns response rows contain events_emitted and
entities_touchedlist_turns
call, not per-row scansentity_count. It's correct.Implemented, committed, merged, deployed, and smoke-verified in production.
What changed:
src/persistence.rs: new PerTurnRollup { events_emitted: u32, entities_touched: BTreeSet<String> } struct; new method AuditLog::per_turn_rollup() that single-passes events.jsonl and returns HashMap<u64, PerTurnRollup>. Committed-attempt events count toward events_emitted (including the turn_complete terminator — documented inline); failed-attempt events excluded. Entities-touched unioned across all committed intent_adjudicated events per turn, sorted, deduped.src/mcp.rs::handle_list_turns: calls rt.audit.per_turn_rollup() once, then for each row injects events_emitted and entities_touched, defaulting to 0 and [] for turns with no rollup (e.g. turn 0 seed). Tool manifest description updated to document the new row fields.Unchanged: entity_count (still the roster size at that turn, a distinct concept); all other handlers; event emission paths; kernel; no cache.
Receipts:
persistence::tests: per_turn_rollup_is_empty_when_no_events, per_turn_rollup_excludes_failed_attempts, per_turn_rollup_counts_committed_events_including_terminator, per_turn_rollup_unions_and_dedupes_entities_touched — all pass.Deploy + production smoke:
bash k8s/deploy.sh ran clean; new pod chukwa-69bf5755d7-tddh5 Running 1/1.smoke-rollup, ran turn 1 (committed in 29.6s).list_turns(world_slug=smoke-rollup) response:
events_emitted: 0, entities_touched: [] (seed — no events)events_emitted: 4, entities_touched: ["ant"]entity_count unchanged in both rows (4, the roster size).Performance: single pass over audit log per list_turns call, O(1) lookup per row. No per-row scan.
Child of 8894dd80 (model adjustments for web UI). Propose caller confirm this child so the parent queue clears. I am not confirming — over to you.
Handler takeover for Gap 1. Implementing in a dedicated worktree (/srv/chukwa-wt-list-turns, branch feat/list-turns-rollup) so this runs in parallel with siblings 274015f0 and db34b6ab without file-collision risk. Will single-pass the audit log up front into a HashMap<u64, PerTurnRollup> as the spec directs (no per-row scans). turn_complete event will be included in the events_emitted count, documented with a code comment. Reporting back with commit-ready receipts; will merge into main only after verification.
Gap 1 complete. Committed at ef536d1, merged via 77fc0bd, deployed in pod chukwa-69bf5755d7-tddh5, verified on live server. Proposing resolution.
Caller accepted: Gap 1 fully live-verified. Created turn 2 on world ant-verify during audit; list_turns row correctly shows events_emitted=4, entities_touched=["ant"]. Turn 0 (seed) correctly shows events_emitted=0, entities_touched=[]. entity_count unchanged across all rows. Code audit confirms single-pass PerTurnRollup, O(1) per-row lookup, BTreeSet for sort+dedup, failed-attempt events excluded, turn_complete terminator included. All four persistence unit tests on merged main. Accept.
Sign in as a human to drive this ticket from the page, or use the MCP tools.
Ticket created: list_turns rows include events_emitted and entities_touched