myxce/.kiro/specs/scheduler-sim-step-verbosity/design.md
Rongsong Shen 1374540954
docs(scheduler-sim-step-verbosity): add approved spec (requirements/design/tasks)
Follow-on to scheduler-sim-repl-verbosity: collapses the REPL's
per-event "[sim] t=... session=..." preamble to print once per
distinct rendered (timestamp, session) run of consecutive echoed
events, instead of once per event, in both step mode and continue
mode's structural-event echo. Requirement 1.3 deliberately keys the
dedup on the rendered, second-granularity timestamp rather than the
raw nanosecond clock value, which ticks on every event and would
otherwise never repeat -- confirmed against a real user-supplied
transcript before writing requirements (see research.md). Requirement
2 preserves event selection, ordering, content, every other REPL
command, and all scheduling decisions/state exactly as
scheduler-sim-repl-verbosity's Requirement 5 already established.

Generated via kiro-spec-quick (single-pass, no approval gates) with
an inline requirements review gate, an inline design review gate, and
an independent task-graph sanity review (one NEEDS_FIXES round,
repaired: added file-size watch bullets to tasks 1.1/1.2 per
design.md's own housekeeping directives).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YAWMhfRJFnmmnG8h97yUDd
2026-09-03 03:53:24 +00:00

146 lines
15 KiB
Markdown

# Design Document
## Overview
`repl_print_event_summary()` (`repl_break.c`) prints a `"[sim] t=<ns> (<ISO8601>) session=<N>"` preamble line unconditionally before every echoed event's own content line — this is its only call site, `xce_repl_on_event()`, which already gates WHICH events reach it per `scheduler-sim-repl-verbosity`'s Event Echo Gate (structural events always; routine events only in `step` mode). Because the preamble is a fixed prefix printed on every call regardless of what the previous call printed, an operator watching several consecutive events that land in the same displayed second and session sees that context restated once per event, diluting the actual event content it's meant to introduce.
This feature makes `repl_print_event_summary()` preamble-aware of its own immediately-preceding output: it prints the preamble only when the rendered (second-granularity) timestamp or the displayed session number has changed since the last time a preamble was actually printed; otherwise it prints only the event's content line. No other REPL behavior changes.
**Users**: Operators running `step` or `continue` (structural-event) transcripts through `xce_scheduler --sim <file> --interactive`.
**Impact**: `repl_print_event_summary()` gains a short-circuit at its top; `xce_sched_repl_state_s` gains two small cache fields recording what was last actually printed.
### Goals
- Print the timestamp/session preamble once per distinct (rendered timestamp, session) run of consecutive echoed events, not once per event.
- Base "distinct" on the rendered, second-granularity timestamp and displayed session number — not the underlying raw nanosecond clock value, which changes on every event and would otherwise defeat deduplication entirely.
- Leave every echoed event's own content line, its ordering, and whether it is echoed at all completely unchanged.
### Non-Goals
- Changing the Event Echo Gate's routine/structural classification (`repl_event_is_structural()`) — which events reach `repl_print_event_summary()` at all is unaffected.
- Changing the Round Rollup / collapsing behavior `continue` mode already uses for routine events and per-round decision summaries.
- Changing decision output (`sim_print_decision`/`sim_print_decision_gate`), which has no preamble of its own and is untouched.
- Reformatting the preamble's own content (timestamp precision, session-number convention) — only whether it is printed, not what it looks like when it is.
## Boundary Commitments
### This Spec Owns
- The preamble-print decision inside `repl_print_event_summary()`'s single call site.
- Two new cache fields on `xce_sched_repl_state_s` (`repl_internal.h`) recording the last preamble actually printed.
### Out of Boundary
- The Event Echo Gate's classification logic (`repl_event_is_structural()`, `repl_break.c`) — unmodified, this spec only changes what happens after an event has already been selected for echoing.
- The Round Rollup / collapsing logic (`repl_rollup.c`) and `sim_print_decision_gate()` (`sim_loop.c`) — both unmodified.
- Every REPL command's parsing, dispatch, or output other than this one preamble line.
### Allowed Dependencies
- `xce_sched_repl_state_s` (`repl_internal.h`, existing) — the two new cache fields live here, alongside this file's other REPL-session-scoped state.
- `repl_format_ts_human()` (`repl_fmt.c`, existing, from `scheduler-sim-repl-verbosity`) — reused as-is to render the human-readable timestamp string used for the dedup comparison; not modified.
### Revalidation Triggers
- If `repl_format_ts_human()`'s rendered granularity or format ever changes, re-verify the dedup key still reflects what an operator actually sees as "the same displayed context."
- If `repl_print_event_summary()` ever gains a second call site, revisit whether a single "last printed" cache still correctly models "immediately preceding echoed event" across both sites.
## Architecture
### Existing Architecture Analysis
`repl_print_event_summary()` is `static` and has exactly one caller, `xce_repl_on_event()` (`repl_break.c`), itself reached only when `sim->interactive` and `sim->repl_state != NULL` — both already guaranteed by the time this function runs, so no new NULL-handling is introduced. The function currently: (1) unconditionally builds and prints the preamble, then (2) switches on `ev->type` to print the event's own content. This design touches only step (1); step (2) and the surrounding gate in `xce_repl_on_event()` are unchanged.
`xce_sched_repl_state_s` already carries several REPL-session-scoped caches added by prior tasks (the Round Rollup's collapse-state fields, `units_mode`) — the two new fields here follow that same established pattern: session-lifetime state, zero-initialized by `repl_state_ensure()`'s existing `calloc`, with no additional lifecycle code required (an empty string and a `0` session number can never equal a real echoed event's rendered values, so the "no preceding echoed event" case, AC 1.2, is satisfied by zero-initialization alone — the same convention every field in this struct except `units_mode` already relies on).
**Architecture Integration**:
- Selected pattern: extend one existing function's own early logic; no new component, no new file.
- Domain/feature boundaries: strictly inside the REPL Output domain (`repl_break.c`/`repl_internal.h`) `scheduler-sim-repl-verbosity` already established; no other domain (Round Rollup, Resource Formatter, Help/Breakpoint) is touched.
- Existing patterns preserved: `xce_sched_repl_state_s` as the single home for REPL-session-scoped display state; `repl_format_ts_human()` as the sole human-timestamp renderer.
- New components rationale: none — this is a localized behavior change to one existing function plus two state fields, not a new component.
- Steering compliance: `src/scheduler/CLAUDE.md`'s 800-line-per-file cap applies to `repl_break.c`, which is already at 949 lines (an accepted residual from `scheduler-sim-repl-verbosity` task 9.2). This spec's addition is small (a handful of lines inside one existing function), but implementation must re-check the file's line count after landing it and, per this file family's established practice, empirically test extracting `repl_print_event_summary()` (and its Event Echo Gate neighbors) into a new file if the total meaningfully exceeds the residual already accepted — not a decision to make speculatively before the actual diff size is known.
## File Structure Plan
### Modified Files
- `src/scheduler/src/repl_break.c` — `repl_print_event_summary()` gains a preamble-print decision at its top: render the candidate human timestamp and displayed session number, compare against `rs->last_echo_ts_human`/`rs->last_echo_session`; print the preamble and update both cache fields only when either differs (or the cache is still at its zero-initialized default), otherwise skip straight to the event's own content switch.
- `src/scheduler/src/repl_internal.h` — `xce_sched_repl_state_s` gains `last_echo_ts_human` (fixed-size char buffer, same size as the existing `repl_format_ts_human()` output buffers elsewhere in this file family) and `last_echo_session` (`uint64_t`), both doc-commented as this feature's cache, zero-initialized by the existing `repl_state_ensure()` allocation path with no additional lifecycle code.
### New/Modified Test Files
- `src/scheduler/testsuite/test_repl_event_echo.c` (extend, existing home for Event Echo Gate behavior this feature builds directly on top of) — the new preamble-dedup tests (see Testing Strategy). This file is at 705 lines going into this spec; re-check its line count after landing these tests and split into a new `test_repl_step_verbosity.c` (following this file family's established "duplicate fixture helpers, don't share" convention — see `scheduler-sim-repl-verbosity` design.md's own Corrections for the precedent) if it approaches this project's ~1000-line split trigger, rather than growing past it.
## Requirements Traceability
| Requirement | Summary | Components | Interfaces | Flows |
|-------------|---------|------------|------------|-------|
| 1.1 | Omit preamble when rendered timestamp+session unchanged | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 1.2 | Print preamble when rendered timestamp+session changed, or no prior echo | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 1.3 | Dedup key is rendered (second-granularity) timestamp + session, not raw ns | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 1.4 | Event content/ordering/echo-selection unaffected | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 2.1 | No event newly suppressed or added | Out of Boundary (Event Echo Gate unmodified) | n/a | n/a |
| 2.2 | Ordering preserved | Event Echo Preamble Deduplicator | `repl_print_event_summary()` | n/a |
| 2.3 | Every other command's behavior unchanged | Out of Boundary | n/a | n/a |
| 2.4 | No change to scheduling decisions/state | Out of Boundary (display-only change) | n/a | n/a |
## Components and Interfaces
| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies (P0/P1) | Contracts |
|-----------|--------------|--------|---------------|---------------------------|-----------|
| Event Echo Preamble Deduplicator | REPL / repl_break.c | Print the timestamp/session preamble once per distinct rendered context instead of once per event | 1.1-1.4, 2.2 | `repl_format_ts_human()` (P0), `xce_sched_repl_state_s` (P0) | State |
### REPL Output (repl_break.c)
#### Event Echo Preamble Deduplicator
| Field | Detail |
|-------|--------|
| Intent | Suppress the timestamp/session preamble on an echoed event when it would exactly restate the immediately preceding echoed event's rendered context |
| Requirements | 1.1, 1.2, 1.3, 1.4, 2.2 |
**Responsibilities & Constraints**
- Computes the candidate human-readable timestamp via the existing `repl_format_ts_human(xce_core_now(&sim->core), ...)` call already present in `repl_print_event_summary()`, and the candidate displayed session number (`rs->session_count + 1`, the existing expression already used) — no new value sources.
- Compares the candidate pair against `rs->last_echo_ts_human`/`rs->last_echo_session`. Prints the preamble and updates both cache fields when they differ, or when the cache is still at its zero-initialized "never printed" default (empty string / session `0`, values no real echoed event can legitimately produce). Skips the preamble entirely, with no cache update, when they match (Requirement 1.1).
- The comparison is on the RENDERED values only — the underlying raw nanosecond `xce_core_now()` return value is never part of the dedup key, since it changes on every event and two events sharing the same displayed second still have different raw values (Requirement 1.3).
- The event's own content line (the existing `switch (ev->type)` block) is printed unconditionally regardless of the preamble decision — this component only ever suppresses the preamble, never the content (Requirement 1.4, 2.2).
**Dependencies**
- Inbound: `repl_print_event_summary()` (repl_break.c, existing) (P0)
- Outbound: `repl_format_ts_human()` (repl_fmt.c, existing) (P0)
**Contracts**: Service [ ] / API [ ] / Event [ ] / Batch [ ] / State [x]
##### State Management
- State model: two new fields on `xce_sched_repl_state_s` — `last_echo_ts_human` (the last rendered human timestamp actually printed in a preamble) and `last_echo_session` (the last displayed session number actually printed).
- Persistence & consistency: in-memory, scoped to one REPL session, written only by this component, read only by this component. Zero-initialized by `repl_state_ensure()`'s existing `calloc` — no dedicated default-assignment step is needed (unlike `units_mode`'s documented exception in `scheduler-sim-repl-verbosity`), because the zero defaults (empty string, session `0`) can never collide with a real echoed event's rendered values.
- Concurrency strategy: none required — single-threaded sim driver, same as every other REPL state field.
**Implementation Notes**
- Integration: no change to `repl_print_event_summary()`'s call site or signature — purely internal to the function body.
- Validation: covered entirely by the child-process stdout-capture technique `test_repl_event_echo.c` already establishes for Requirement 1 (Event Echo Gate) — no new test harness needed.
- Risks: `repl_break.c`'s existing 949-line accepted residual (see Architecture Integration above) — watch, don't pre-split.
## Data Models
### Domain Model
This feature adds no new domain concept — it extends the existing "REPL session-scoped display state" already modeled on `xce_sched_repl_state_s` (`repl_internal.h`), the same struct the Round Rollup and Resource Value Formatter components added fields to in `scheduler-sim-repl-verbosity`.
### Logical Data Model
| Field | Type | Purpose |
|-------|------|---------|
| `last_echo_ts_human` | `char[32]` | The human-readable timestamp last actually printed in an event's preamble (same buffer size `repl_format_ts_human()`'s other call sites already use) |
| `last_echo_session` | `uint64_t` | The displayed session number last actually printed in an event's preamble |
**Consistency & Integrity**: both fields reset together only at REPL session start, via `repl_state_ensure()`'s existing `calloc` zero-initialization — no explicit reset code path exists or is needed, since no other event in this struct's lifecycle should cause "what was last displayed" to become stale mid-session.
## Error Handling
### Error Strategy
No new failure modes: this feature performs only in-memory string/integer comparisons and a `fprintf` that already exists at this call site — no allocation, no I/O beyond the pre-existing `stdout` write.
### Monitoring
No new logging — the existing `[sim] ...` stdout convention is the only output surface, and this feature only changes how often one of its existing lines appears.
## Testing Strategy
### Integration Tests (extend `test_repl_event_echo.c`, child-process output capture — see File Structure Plan for the split-if-needed note)
- Two consecutive echoed events with identical rendered timestamp and session: the second event's preamble line is absent, its content line still appears (1.1, 1.4).
- Two consecutive echoed events whose session differs (a session boundary occurs between them): both events print their own full preamble (1.2).
- Two consecutive echoed events whose raw high-resolution timestamps differ but whose rendered (rounded) timestamp and session are identical: the second event's preamble is still omitted — the critical case distinguishing this feature's actual dedup key from a naive raw-timestamp comparison (1.3).
- The very first echoed event in a fresh REPL session prints its preamble (no prior echo exists to compare against) (1.2).
- A structural event echoed under `continue` mode after a stretch of suppressed routine events: its preamble prints or is suppressed based on the same rendered-context rule, exercising the dedup logic outside `step` mode too (1.1, 1.2).
- `step` mode's full event/content ordering and count are unchanged versus this feature's baseline — regression check alongside this file's existing Event Echo Gate assertions (2.1, 2.2).
### Regression (existing suites, re-run, no new tests)
- `test_repl_execution_control.c` and `test_repl_equivalence.c`: confirm no other REPL command's output changed and the REPL/batch decision-equivalence guarantee still holds (2.3, 2.4) — the same spot-check discipline `scheduler-sim-repl-verbosity`'s task 10.1/10.2 already established for this exact concern.