Adds 7 tests in a new test_repl_round_rollup.c covering per-round decision summaries, collapsing of repetitive rounds, and the shared repl_round_collapse_flush() entry point (Requirements 2.1-2.4, 3.1-3.4, 3.6). repl_round_collapse_flush() itself is not yet implemented (task 2.2) - build fails at link time, confirming RED. Split into its own file rather than the originally-planned test_repl_execution_control.c: that file was already at the project's 800-line cap before this task, and landing these tests there would have pushed it to 1563 lines. design.md's File Structure Plan is corrected accordingly (mirrors the existing test_repl_snapshot_params.c /test_repl_breakpoints_inspection.c split in this same file family). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YAWMhfRJFnmmnG8h97yUDd
51 KiB
Design Document
Overview
Purpose: This feature reduces and reorganizes the console output the xce_scheduler --interactive simulation REPL prints while executing continue, so an operator can watch a long replay's progress without wading through repetitive, low-information lines.
Users: XCE scheduler engineers and operators debugging scheduling behavior via xce_scheduler -i -s <replay>, a capability shipped by the scheduler-core spec.
Impact: Changes the text xce_repl_on_event() (repl_break.c) and sim_print_decision() (sim_loop.c) print while sim->repl_state->mode == XCE_REPL_MODE_CONTINUE; the REPL's command prompt string (repl_command_loop(), repl.c); info worker's/info resource's resource-value rendering and the set command (repl_info.c, repl_params.c); and help's listing plus break's unrecognized-event-type error text (repl.c, repl_break.c). No change to scheduling decisions, replay determinism, or step mode's per-event/per-decision detail; every other aspect of every REPL command's parsing and behavior is unchanged.
Goals
- Suppress routine per-event echo during
continueexecution, while still surfacing structural changes (Requirement 1). - Replace unconditional per-decision echo with one summary line per round during
continue(Requirement 2). - Collapse consecutive idle or unchanged rounds into a single range notification (Requirement 3).
- Compress array-job identifier lists into index ranges everywhere the REPL displays multiple job identifiers together, including
info worker's assigned-job listing (Requirement 4). - Leave
stepmode's per-event/per-decision detail, and every other aspect of the REPL's existing commands, unchanged except where this feature's own requirements explicitly say otherwise (Requirement 5). - Show the current session and timestamp in the REPL's command prompt, in both
stepandcontinuemodes (Requirement 5.5). - Render byte/megabyte-denominated resource values in a human-readable unit under operator control (
set units), and lay outinfo worker's/info resource's listings as a grouped table (Requirement 6). - Reorganize
help's command listing into labeled, consistently-aligned sections, and makebreak's valid event types discoverable in bothhelpand its error message (Requirement 7).
Non-Goals
- New REPL commands or newly inspectable data (limit usage, pend-reason detail) — tracked separately under the
scheduler-sim-diagnosticsbrief. - Any change to scheduling/allocation logic or to the deterministic outcome of a replay.
- Non-interactive (headless, no
-i) scheduler output. - Resolving bug-1611 (whether
DISPATCHdecisions are structurally invisible in replay mode) — this spec only guarantees Requirement 2.4 holds regardless of that investigation's outcome (see Design Decisions,research.md). - Unit-aware formatting for any resource outside the confirmed canonical-bytes list (
mem,tmp,maxmem,maxtmp,maxswap) and the confirmed canonical-megabytesswp—xce_resource_t(src/include/xce/types.h) carries no units metadata at all, so an arbitrary operator-defined or plugin resource is displayed as a plain unitless number, exactly as today.
Boundary Commitments
This Spec Owns
- Console output formatting/verbosity logic inside
xce_repl_on_event()(repl_break.c), thecontinue-mode gate onsim_print_decision()'s call site (sim_loop.c), and the new round-boundary rollup/collapse logic added atxce_repl_post_session_check()(repl.c). - The new jobid-range-compression helper and every call site that adopts it, including
info worker's assigned-job listing (repl_info.c). - New
xce_sched_repl_state_sfields required to track collapse state across rounds. - The REPL prompt string construction in
repl_command_loop()(repl.c). - Resource-value unit classification/scaling helpers, the
units_modedisplay setting, and the table/grouping layout ofinfo worker's andinfo resource's resource listings (repl_info.c, repl_params.c). repl_print_help()'s listing structure (repl.c) andrepl_cmd_break()'s unrecognized-event-type error text (repl_break.c).
Out of Boundary
- Any new
info/break/settop-level subcommand or newly inspectable scheduler data (set unitsis a new option of the existingsetcommand, not a new command). - Scheduling/allocation/decision logic itself (
scheduler.c,src/plugins/). sim_print_decision()'s behavior whensim->interactiveis false (headless mode) — unchanged.- Root-causing bug-1611 — a separate investigation; this spec only ensures its own requirements hold regardless of the answer.
- Unit-aware formatting for resources outside the confirmed bytes/megabytes list (see Non-Goals) — no generic units-metadata system is added to
xce_resource_tor the wire protocol.
Allowed Dependencies
- Existing
xce_sched_repl_state_sfields (mode,last_n_dispatch/last_n_pend/last_n_preempt,session_count) — repl_internal.h. - The existing
core->jobsiteration idiom (iter_make/iter_next,JOB_IS_PEND) already used byrepl_info_jobs(). - Existing helpers
fmt_jobid_ex()(lib/utils.c) andrepl_format_ts_human()(repl.c). - Must not add a new call site to
sim_loop.c's main loop beyond gating the two calls that already exist there (sim_print_decision, thexce_repl_check_eventwrapper) — that file is documented at its line-budget ceiling (repl.h's own header comment). xce_sched_repl_state_s.session_countandxce_core_now()/repl_format_ts_human()(repl.c) for the prompt's session/timestamp content — no new state needed for that part.repl_break.c's existingBP_KIND_TABLE(currentlystatic/file-local) — exposed via one new shared accessor declared inrepl_internal.h, rather than duplicated as a second hardcoded list, sohelpand the unrecognized-event-type error can never drift out of sync with each other or with the table itself.- The existing
repl_command_loop()call site'sreadline(XCE_REPL_PROMPT)(repl.c:439) — this feature makes the prompt argument dynamic in place; no new call site. src/controller/testsuite/test.c'sis_bytes_valued_resource()/format_bytes_human()as a design precedent only (same classification list, same adaptive B/KB/MB/GB/TB/PB algorithm) — not a shared symbol, sincexce_testandxce_schedulerare separate binaries; the scheduler gets its ownrepl_*-named port of the same logic.
Revalidation Triggers
- If
scheduler-sim-diagnosticsadds pend-reason data, the round-rollup collapse fingerprint (Design Decisions,research.md) may need to incorporate it — revisit Requirement 3.2's implementation then. - If bug-1611 is confirmed a real dispatch-visibility gap and separately fixed, the rollup's dispatch count source should switch from the decision tally to the same state-derived approach already used for the pend-jobid set.
- Any change to
xce_sched_repl_state_s's existingmode/tally fields by another spec should re-check this feature's rollup logic. - If a future spec adds real units metadata to
xce_resource_t(see Non-Goals), the hardcoded bytes/megabytes classification lists added here should be replaced by reading that metadata instead of the name-match lists. - If
repl_break.c'sBP_KIND_TABLEgains or loses an entry (e.g. a new breakpoint-able event type),helpandbreak's error message pick it up automatically via the shared accessor — no separate list to update, by construction.
Architecture
Existing Architecture Analysis
repl.c/repl_break.c/repl_info.c/repl_params.cform the REPL command family underscheduler-core.repl.hfolds thesim->interactivegate intostatic inlinewrapper functions sosim_loop.c's own diff stays to single-line, brace-free call expressions — this feature'ssim_loop.cchange must follow that same convention rather than adding a bareifblock to the loop body.xce_sched_repl_state_s(repl_internal.h) is the REPL's own session-scoped state struct, zero-initialized viacallocinrepl_state_ensure()— new fields need no separate lifecycle.xce_repl_post_session_check()(repl.c) already runs exactly once per completed round, immediately after that round's decisions are drained, purely to check thestep-budget pause condition today.
Architecture Pattern & Boundary Map
Correction (post-review): breakpoint-hit detection and structural-event classification both live in repl_break.c (xce_repl_on_event, xce_repl_on_begin_round, xce_repl_on_decision — each calls repl_command_loop() directly on a match), not in repl.c. Requirement 3.5 ("a structural event ends the active range") and Requirement 3.6 ("a breakpoint hit ends the active range") therefore cannot be satisfied by round-boundary logic in repl.c alone — repl_break.c needs a way to force-flush the Round Rollup's active range from its own call sites. The Round Rollup component now exposes an explicit flush entry point for this, called from both files.
flowchart TD
RoundEnd[Round decisions drained] --> Query[Query core jobs for PEND state]
Query --> Fingerprint[Build fingerprint: dispatch count, pend jobid set, preempt count]
Fingerprint --> Compare{Matches active collapsed range}
Compare -->|Yes| Extend[Extend active range silently]
Compare -->|No| FlushCall[Call round rollup flush]
FlushCall --> Begin[Begin new collapsed range with this round]
Extend --> NextRound[Advance to next round]
Begin --> NextRound
StructuralEvent[Structural event classified in repl_break.c] --> FlushCall
BreakpointHit[Breakpoint hit detected in repl_break.c] --> FlushCall
ReplayComplete[Replay completion in repl.c] --> FlushCall
FlushCall --> PrintRange[Print the flushed range, if one was open]
Architecture Integration:
- Selected pattern: a state accumulator attached to the existing round-boundary hook (
xce_repl_post_session_check), exposing one shared flush entry point thatrepl_break.c's event/breakpoint/decision hooks andrepl.c's replay-completion hook all call — no new hook call sites added tosim_loop.c. - Domain/feature boundaries: the Round Rollup's state and flush/print logic live in
repl.c;repl_break.ccalls the flush entry point but does not own or mutate the collapse state directly;sim_loop.conly gains a mode gate on its two existing calls. - Existing patterns preserved:
repl.h'ssim->interactive-gated inline-wrapper convention;repl.c's shared-helper convention (repl_format_ts_human,repl_split_tokens) extended with two new shared helpers (range compression, and the round-rollup flush entry point declared inrepl_internal.hforrepl_break.cto call). - New components rationale: deciding "print now vs. extend silently" requires comparing against the previous round's fingerprint, which must persist across separate
xce_repl_post_session_check()invocations — hence new persistent fields onxce_sched_repl_state_srather than function-local state. The flush entry point exists because three independent call sites (round boundary, breakpoint hit, replay completion) across two files all need to end an active range the same way. - Steering compliance: one domain per file preserved; no new production
.cfile needed;snake_case/repl_*naming conventions followed throughout.
Technology Stack
| Layer | Choice / Version | Role in Feature | Notes |
|---|---|---|---|
| Scheduler daemon (C, gnu17) | existing src/scheduler/ toolchain, no new dependency |
All logic lives in existing REPL/sim-driver translation units | Matches tech.md; no new library, no CGO/Go involvement |
File Structure Plan
Modified Files
src/scheduler/src/repl_internal.h— extendxce_sched_repl_state_swith round-collapse state fields (see Data Models), a newunits_modefield (Requirement 6), and declare the new shared jobid-range-compression helper, resource-formatting helpers, and theBP_KIND_TABLEaccessor (Requirement 7).src/scheduler/src/repl_break.c—xce_repl_on_event()gains routine-vs-structural event classification gated onrs->mode(Requirement 1);repl_print_event_summary()'sJOB_PUSH-style cases adopt the jobid-range-compression helper (Requirement 4);xce_repl_on_event(),xce_repl_on_begin_round(), andxce_repl_on_decision()each call the Round Rollup's flush entry point before printing a structural-event line or before re-enteringrepl_command_loop()on a breakpoint hit (Requirement 3.5, 3.6);repl_cmd_break()'s unrecognized-event-type error gains the full valid-event-type listing, rendered via a new shared accessor over the existingBP_KIND_TABLE(Requirement 7.5);repl_cmd_delete()already fixed independently (bug-1761,strtol+endptr validation) — no further change needed here for this spec.src/scheduler/src/repl.c—xce_repl_post_session_check()gains the round-boundary fingerprint/collapse logic (Requirements 2, 3); implements the new shared jobid-range-compression helper (Requirement 4) and the Round Rollup's flush entry point (Requirement 3.5, 3.6), following the file's existing shared-helper convention;xce_repl_on_replay_complete()calls the same flush entry point before printing the existing replay-complete banner;repl_command_loop()'sreadline(XCE_REPL_PROMPT)call gains a per-iteration dynamically-built prompt (Requirement 5.5);repl_print_help()is rewritten into labeled, consistently-aligned sections,info configmoves next to the otherinfolines, and thebreakline grows the sameBP_KIND_TABLE-derived event-type listingrepl_break.c's error message uses (Requirement 7).src/scheduler/src/repl_info.c— new resource-value classification/scaling helpers (byte-denominated list,swp's megabyte special case, whole-number formatting) and a table/grouping rewrite ofrepl_info_worker()'s andrepl_info_resource()'s resource-listing loops, both readingrs->units_mode(Requirement 6); the assigned-job listing inrepl_info_worker()adopts the jobid-range-compression helper (Requirement 4.5).src/scheduler/src/repl_params.c—repl_cmd_set()gains aunits raw|human|bothbranch alongside the existingspeed/output/verifybranches, storing the selection on the newrs->units_modefield (Requirement 6.2, 6.3).src/scheduler/src/sim_loop.c— the existing call site invokingsim_print_decision()gains acontinue-mode gate, mirroringrepl.h's inline-wrapper convention (Requirements 2, 5).src/scheduler/include/repl.h— no functional change expected; existing inline wrappers already cover the gating points this feature needs.
New/Modified Test Files
Correction (post-review, task 2.1): test_repl_execution_control.c was already over this project's 800-line cap (937 lines) before this feature's work began. Landing task 2.1's Round Rollup tests there per the original plan below pushed it to 1563 lines — nearly double the cap, and past the ~1000-line threshold this exact spec family already treats as a split trigger (test_repl_snapshot_params.c was split out of test_repl_breakpoints_inspection.c for precisely this reason; see that file's own meson.build comment). Round Rollup's tests (task 2.1, and any Round-Rollup-internal portion of later tasks, e.g. Requirement 3.5's flush-on-structural-event/breakpoint interaction) now live in a new src/scheduler/testsuite/test_repl_round_rollup.c instead, duplicating (not sharing) whatever pre-existing static fixture helpers it needs — the established convention this same file pair (test_repl_breakpoints_inspection.c/test_repl_snapshot_params.c) already follows, rather than factoring out a shared test-helper header. test_repl_execution_control.c remains the target for Requirement 1's Event Echo Gate tests, the sim_print_decision gate tests, the Requirement 5 no-regression checks, the new prompt-content tests (Requirement 5.5), and help-listing structure tests (Requirement 7.1-7.4) — but re-check its line count before landing each of those; if it approaches the cap again, split further rather than growing past it.
src/scheduler/testsuite/test_repl_execution_control.c— child-process output-capture tests covering Requirement 1, the Requirement 5 no-regression checks (this file already drivessched_sim_driver_run()end-to-end and captures real process output viachild_continue_then_inspect-style helpers); also gains the new prompt-content tests (Requirement 5.5) andhelp-listing structure tests (Requirement 7.1-7.4) — the natural home given it already exercisesreadline()/prompt-adjacent behavior and nohelptest exists anywhere today.src/scheduler/testsuite/test_repl_round_rollup.c(new, per the Correction above) — child-process output-capture tests for Requirements 2 and 3 (per-round decision summary, collapsing, the shared flush entry point, and its call fromxce_repl_on_replay_complete).src/scheduler/testsuite/test_repl_output_compression.c(new) — focused unit tests for the jobid-range-compression helper (Requirement 4): a pure function, tested without the process harness.src/scheduler/testsuite/test_repl_resource_formatting.c(new) — focused unit tests for the byte/megabyte classification and adaptive human-scaling helpers (Requirement 6.1, 6.2, 6.5): pure functions, tested without the process harness, mirroringtest_repl_output_compression.c's pattern.src/scheduler/testsuite/test_repl_breakpoints_inspection.c— extended withinfo worker/info resourcetable-layout and units-mode-display tests (Requirement 6.6, alongside its existinginfo worker/info resourcetests) andbreak's enriched unrecognized-event-type error test (Requirement 7.5, alongside the existingtest_break_unrecognized_event_type_rejected).src/scheduler/testsuite/test_repl_snapshot_params.c— extended withset units raw|human|bothparsing/validation tests (Requirement 6.2, 6.3), alongside its existingset speed/set output/set verifytests.
System Flows
See the Architecture Pattern & Boundary Map diagram above — it is the only non-trivial flow this feature introduces (a per-round branching decision with two exit points: silent extension, or flush-and-restart). Per-event classification (Requirement 1) is a simple, non-branching gate and does not warrant a separate diagram. The three later-added components (REPL Prompt Context, Resource Value Formatter, Help & Breakpoint Discoverability) are likewise simple, single-purpose lookups/formatters with no multi-step state machine of their own — a name-list check, an adaptive-unit loop, and a table lookup, respectively — so none of them warrant a separate diagram either.
Requirements Traceability
| Requirement | Summary | Components | Interfaces | Flows |
|---|---|---|---|---|
| 1.1, 1.2, 1.3, 1.4 | Mode-aware event echo | Event Echo Gate | xce_repl_on_event |
n/a (per-event, upstream of round boundary) |
| 2.1, 2.2, 2.3, 2.4 | Per-round decision summary | Round Rollup | xce_repl_post_session_check, state-derived pend query |
Round-boundary flow |
| 3.1, 3.2, 3.3, 3.4 | Collapsing repetitive rounds (idle/unchanged detection, range content) | Round Rollup (collapse state) | fingerprint compare / flush | Round-boundary flow |
| 3.5, 3.6 | Ending an active collapsed range on outcome change, structural event, breakpoint hit, or replay completion | Round Rollup (flush entry point, and its own call from xce_repl_on_replay_complete), Event Echo Gate (calls it on structural events/breakpoint hits) |
repl_round_collapse_flush |
Round-boundary flow |
| 4.1, 4.2, 4.3, 4.4, 4.5 | Job identifier range compression | Jobid Range Compressor | repl_fmt_jobid_range |
n/a (pure formatting) |
| 5.1, 5.2, 5.3, 5.4 | No regression to step mode / other commands, outside this feature's explicit exceptions | Event Echo Gate, sim_print_decision gate |
mode checks | n/a |
| 5.5 | REPL prompt shows session/timestamp | REPL Prompt Context | repl_build_prompt |
n/a (per-iteration, upstream of dispatch) |
| 6.1, 6.2, 6.3, 6.4, 6.5 | Human-readable resource value formatting, set units option |
Resource Value Formatter | repl_is_bytes_valued_resource, repl_format_bytes_human, repl_cmd_set's units branch |
n/a (pure formatting + state) |
| 6.6 | info worker/info resource table/grouping layout |
Resource Value Formatter | repl_info_worker, repl_info_resource |
n/a (rendering only) |
| 7.1, 7.2, 7.3 | help listing reorganized into labeled, aligned sections |
Help & Breakpoint Discoverability | repl_print_help |
n/a (rendering only) |
| 7.4, 7.5, 7.6 | break event types discoverable in help and its error message; no other command's existence, parsing, or output changes |
Help & Breakpoint Discoverability | repl_bp_kind_names (shared accessor over BP_KIND_TABLE) |
n/a (pure formatting) |
Components and Interfaces
| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies (P0/P1) | Contracts |
|---|---|---|---|---|---|
| Event Echo Gate | REPL / repl_break.c | Classify each event as routine or structural and gate the per-event echo by mode | 1.1-1.4 | rs->mode (P0), Round Rollup flush entry point (P0) |
State |
| Round Rollup | REPL / repl.c | Build a per-round fingerprint, print it, or extend/flush a collapsed range; expose a flush entry point callable from breakpoint/structural-event/replay-complete sites | 2.1-2.4, 3.1-3.6 | core->jobs query (P0), existing decision tally (P0) |
State, Service |
| Jobid Range Compressor | REPL / repl.c (shared helper) | Compress array-job identifier lists into index ranges | 4.1-4.5 | fmt_jobid_ex decomposition (P0) |
Service |
sim_print_decision gate |
Sim driver / sim_loop.c | Suppress the direct per-decision print when the Round Rollup will report it instead | 2.3, 5.1 | REPL mode state (P0) | State |
| REPL Prompt Context | REPL / repl.c | Build the readline() prompt string from the current session and timestamp, falling back to the plain prompt before any session begins |
5.5 | rs->session_count (P0), xce_core_now/repl_format_ts_human (P0) |
Service |
| Resource Value Formatter | REPL / repl_info.c, repl_params.c | Classify a resource's canonical wire unit (bytes/megabytes/unitless), render it per the operator's units_mode, and lay out info worker/info resource's listings as a grouped table |
6.1-6.6 | is_bytes_valued_resource/format_bytes_human (test.c) as design precedent (P0), rs->units_mode (P0) |
Service, State |
| Help & Breakpoint Discoverability | REPL / repl.c, repl_break.c | Group and align help's listing, and expose BP_KIND_TABLE's full valid-event-type set to both help and break's error message via one shared accessor |
7.1-7.6 | BP_KIND_TABLE (P0) |
Service |
REPL Output (repl.c / repl_break.c / repl_info.c / repl_params.c / sim_loop.c)
Event Echo Gate
| Field | Detail |
|---|---|
| Intent | Classify each decoded event and suppress its individual echo line under continue, while breakpoint matching still evaluates every event |
| Requirements | 1.1, 1.2, 1.3, 1.4 |
Responsibilities & Constraints
- Structural events (always echoed, any mode):
CONFIG_PUSH,JOBS_PUSH,WORKERS_PUSH,RESOURCES_PUSH,RESVALS_FULL— population/config arrivals an operator needs to see even whilecontinueis otherwise quiet. Before printing a structural event's line, this gate calls the Round Rollup's flush entry point so any active collapsed range is closed and reported first (Requirement 3.5) — a structural event and a stale collapsed range must never interleave out of order. - Routine events (echoed only in
stepmode):RESVALS_DELTA,JOB_STATE_CHANGE,JOB_UPDATE,QUEUE_UPDATE,JOB_RUSAGE.JOB_STATE_CHANGEis classified routine deliberately — the Round Rollup (Requirement 2.4) already surfaces job status transitions at round granularity, so an individualJOB_STATE_CHANGEline undercontinuewould duplicate that information. - Breakpoint matching (
repl_find_event_breakpoint, plus the equivalent checks inxce_repl_on_begin_round/xce_repl_on_decision) runs on every event regardless of echo suppression — Requirement 1.4 is unaffected by this gate. On a match, before re-enteringrepl_command_loop(), the matching hook calls the Round Rollup's flush entry point so the operator sees the collapsed range's final state before the prompt returns (Requirement 3.6).
Dependencies
- Inbound:
xce_repl_on_event— calls this gate before its existing breakpoint check (P0) - Outbound:
repl_print_event_summary— invoked only when the gate allows it (P0); Round Rollup's flush entry point — called before a structural-event line or a breakpoint-triggered command-loop re-entry (P0)
Contracts: Service [ ] / API [ ] / Event [ ] / Batch [ ] / State [x]
State Management
- State model: reads
sim->repl_state->mode(existing field); no new state owned by this component. - Persistence & consistency: none — a per-call decision.
- Concurrency strategy: none required — single-threaded sim driver, same as every other REPL hook.
Round Rollup
| Field | Detail |
|---|---|
| Intent | Once per completed round in continue mode, compute a fingerprint and either extend an active collapsed range or flush it and begin reporting the new state |
| Requirements | 2.1, 2.2, 2.3, 2.4, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6 |
Responsibilities & Constraints
- Active only while
sim->interactive && rs->mode == XCE_REPL_MODE_CONTINUE;stepmode keeps today's full per-decision path unchanged (Requirement 2.3, 5.1). - Pend-jobid set is sourced by directly querying
core->jobsforJOB_IS_PENDstate (same iteration idiom asrepl_info_jobs()) — not solely from the decision tally, so Requirement 2.4 holds independent of bug-1611 (seeresearch.mdDesign Decisions). - Dispatch/preempt counts are sourced from the existing
last_n_dispatch/last_n_preempttally. - Must flush (print) any active collapsed range before: a breakpoint-triggered re-entry into
repl_command_loop(), replay completion, or a fingerprint change from the active range (Requirement 3.5, 3.6). Because the breakpoint and structural-event triggers originate inrepl_break.c, notrepl.c, this component exposes a flush entry point other files call rather than owning every trigger site itself.
Dependencies
- Inbound:
xce_repl_post_session_check— the round-boundary call site the fingerprint/collapse logic attaches to (P0); the Event Echo Gate (xce_repl_on_event) and the breakpoint checks inxce_repl_on_begin_round/xce_repl_on_decision(all repl_break.c) — call the flush entry point (P0);xce_repl_on_replay_complete(repl.c) — calls the flush entry point before its existing banner (P0) - Outbound:
core->jobsiterator (P0),xce_repl_on_decision's existing tally fields (P0), Jobid Range Compressor for rendering the pend-jobid list (P0)
Contracts: Service [x] / API [ ] / Event [ ] / Batch [ ] / State [x]
Service Interface
/*
* Ends the active collapsed round range, if one is open, printing its
* summary line immediately. Safe to call when no range is active (a
* no-op). Called from the round boundary on a fingerprint change, and
* from repl_break.c before a structural-event line or a breakpoint-
* triggered command-loop re-entry, and from xce_repl_on_replay_complete
* before its existing banner.
*/
void repl_round_collapse_flush(xce_sched_sim_driver_t* sim);
- Preconditions:
sim/sim->repl_statenon-NULL (same "safe no-op on NULL" discipline every other REPL hook in this file family already follows). - Postconditions: no collapsed range is active after the call; if one was active, its summary line has been printed exactly once.
- Invariants: idempotent — calling it again immediately afterward with no intervening round is a no-op, never a duplicate print.
State Management
- State model: new fields on
xce_sched_repl_state_shold the active collapsed range (see Data Models) — whether one is open, its round/timestamp bounds, and its cached fingerprint. - Persistence & consistency: in-memory, scoped to one REPL session; zero-initialized for free by
repl_state_ensure()'s existingcalloc. - Concurrency strategy: none required — single-threaded sim driver.
Jobid Range Compressor
| Field | Detail |
|---|---|
| Intent | Group a list of jobids by array base and render contiguous index runs as compressed ranges |
| Requirements | 4.1, 4.2, 4.3, 4.4, 4.5 |
Responsibilities & Constraints
- Pure, read-only formatting function — never mutates its input list or any scheduler/job state.
- Shared by
repl_print_event_summary()'s job-arrival listings, the Round Rollup's pend-jobid display, andrepl_info_worker()'s assigned-job listing (Requirement 4.5) — the same helper, no per-caller variant.
Dependencies
- Inbound:
repl_print_event_summary(P0), Round Rollup (P0),repl_info_worker(repl_info.c) (P0) - Outbound:
fmt_jobid_ex(lib/utils.c) — reuses its existing(cluster, apiserver, timestamp, job[, idx])decomposition (P0)
Contracts: Service [x] / API [ ] / Event [ ] / Batch [ ] / State [ ]
Service Interface
/*
* Formats `njobids` raw jobids into `buf` (bufsz-bounded): groups by
* array base (cluster, apiserver, timestamp, job) and compresses
* contiguous [idx] runs into [lo-hi] form, comma-joining non-contiguous
* runs within one base; non-array jobids are listed individually.
* Returns the number of bytes written (excluding the NUL terminator),
* or a negative value if buf was too small to hold the full listing.
*/
int repl_fmt_jobid_range(char* buf, size_t bufsz,
const uint64_t* jobids, int njobids);
- Preconditions:
jobidsnon-NULL whenevernjobids > 0;buf/bufszdescribe a valid, writable buffer. - Postconditions:
bufholds a NUL-terminated, comma-separated listing; distinct array bases appear in input order, indices ascend within each base's compressed ranges. - Invariants: never fabricates or silently drops a jobid — the compressed listing always represents exactly the same id set the uncompressed listing would show.
sim_print_decision Gate
| Field | Detail |
|---|---|
| Intent | Suppress sim_print_decision()'s direct per-decision stdout line when the Round Rollup will report the round instead |
| Requirements | 2.3, 5.1 |
Responsibilities & Constraints
- Skip the direct print only when
sim->interactive && sim->repl_state->mode == XCE_REPL_MODE_CONTINUE;stepmode and non-interactive (headless) runs are unaffected (Requirement 5.1, 5.4). - Implemented as an inline-wrapper-style gate at the existing call site in
sched_sim_driver_run(), consistent withrepl.h's established convention, rather than a bareifblock added to the loop body.
Contracts: Service [ ] / API [ ] / Event [ ] / Batch [ ] / State [x]
State Management
- State model: reads
sim->interactiveandsim->repl_state->mode(both existing fields); owns no new state.
REPL Prompt Context
| Field | Detail |
|---|---|
| Intent | Build the string passed to readline() each loop iteration so it shows the current session and timestamp instead of the static "(sim) " prompt |
| Requirements | 5.5 |
Responsibilities & Constraints
- Applies in both
stepandcontinuemodes — it changes the prompt, not any command's output, so it sits outside Requirement 5's "leave unchanged" boundary by design (Requirement 5.5 is one of that requirement's own named exceptions). - Before any session has begun (
rs->session_count == 0, the same zero-initialized valuerepl_state_ensure()'scallocalready guarantees), falls back to the original plain"(sim) "prompt — there is no session/timestamp to show yet, and a fabricated placeholder value would be misleading. - Once at least one session has begun, renders
"(sim session=<N> t=<human-ts>) "usingrs->session_countandxce_core_now(&sim->core)formatted through the existingrepl_format_ts_human()— no new timestamp source, no raw-epoch-nanosecond component (unlike the four existingt=%llu (...)print sites elsewhere in this file family, which are out of this feature's scope per Boundary Commitments).repl_format_ts_human()'s existing output is the full ISO8601 form (strftime's"%Y-%m-%dT%H:%M:%SZ", e.g.2026-09-02T03:29:38Z), not a time-only string — the prompt reuses that format exactly rather than inventing a shorter one, since Allowed Dependencies commits to no new timestamp source. - Uses bare
rs->session_count, matchinginfo session's/info config's existing convention (repl_info.c:848,898) — notsession_count + 1. That+1exists only inrepl_print_event_summary()(repl_break.c:611) to label events echoed mid-stream, before the session containing them has finished and incremented the count; the prompt, by contrast, is only ever shown after astep/continuehas returned control (i.e. afterxce_repl_on_begin_round()for the just-finished session has already run), so the bare count is the session that just completed — the same numberinfo sessionwould report if queried at that exact prompt. Copying the+1convention here would make the prompt disagree withinfo sessionby one. - Built fresh on every
repl_command_loop()iteration, immediately before thereadline()call it feeds — never cached across iterations, since the session/timestamp can change between prompts.
Dependencies
- Inbound:
repl_command_loop()(repl.c) — calls this immediately before its existingreadline(XCE_REPL_PROMPT)line - Outbound:
repl_format_ts_human()(repl.c, existing),xce_core_now()(core_clock.c, existing)
Contracts: Service [x] / API [ ] / Event [ ] / Batch [ ] / State [ ]
Service Interface
/*
* Builds this iteration's REPL prompt into `buf` (bufsz-bounded):
* "(sim) " if no session has begun yet (rs->session_count == 0), else
* "(sim session=<N> t=<ISO8601-ts>) " using the bare current session
* count (NOT +1 -- see repl_print_event_summary()'s different mid-stream
* convention) and xce_core_now()/repl_format_ts_human() (same
* "%Y-%m-%dT%H:%M:%SZ" form used elsewhere, not a shortened one). Called
* fresh from repl_command_loop() before every readline() -- never cached.
*/
void repl_build_prompt(xce_sched_sim_driver_t* sim, char* buf, size_t bufsz);
- Preconditions:
sim/sim->repl_statenon-NULL (same discipline every other REPL hook in this file family follows);buf/bufszdescribe a valid, writable buffer sized for the longest possible rendering (session count width + fixed-width human timestamp). - Postconditions:
bufholds a NUL-terminated prompt string; identical across repeated calls within the same session/timestamp (a pure function ofsim's current state). - Invariants: never blocks, never reads input, never mutates
sim/sim->repl_state— a read-only formatter, same class as the Jobid Range Compressor.
Resource Value Formatter
| Field | Detail |
|---|---|
| Intent | Classify a resource's canonical wire unit, render its value per the operator's selected display mode, and lay out info worker's/info resource's listings as a grouped table instead of a flat line-per-resource dump |
| Requirements | 6.1, 6.2, 6.3, 6.4, 6.5, 6.6 |
Responsibilities & Constraints
- Classification is a small, explicit, hardcoded name list —
repl_is_bytes_valued_resource(name)returns true for exactlymem,tmp,maxmem,maxtmp,maxswap(confirmed canonical-bytes on the wire:node_information.c'snode_info_get_mem()/node_info_get_tmp()docstrings,xcecd.c'sinit_host_attrs()bug-1554 comment). This mirrorssrc/controller/testsuite/test.c'sis_bytes_valued_resource()exactly (same 5 names, same rationale: "a small, explicit, client-side list rather than a wire field"). swpis a confirmed special case, deliberately not added to the bytes list:node_info_get_swap()'s own docstring states its canonical wire unit is megabytes, not bytes (unlike itsmaxswapcounterpart, which the bug-1554 comment confirms IS bytes). Before formatting,swp's value is converted MB→bytes (× 1024.0 × 1024.0) and then passed through the same human-scaling formatter as the bytes-native resources, so it renders on the same adaptive B/KB/MB/GB/TB/PB scale rather than staying a raw MB float.repl_format_bytes_human(bytes, buf, bufsz)adaptively scales to the smallest unit the value fits (B/KB/MB/GB/TB/PB), mirroringtest.c'sformat_bytes_human()algorithm exactly (divide by 1024 while>= 1024.0, cap at PB).- Display mode (
raw/human/both) is a newxce_sched_repl_state_sfield,units_mode, defaulting toboth(Requirement 6.3) and settable viaset units raw|human|both(repl_params.c, alongside the existingspeed/output/verifybranches) —bothrenders"<raw> (<human>)", matchingxce_test's owncmd_resvaldisplay convention exactly (test.c's"%f (%s)\n"). - Numeric resources with no canonical unit (load averages, factors, counts — everything not in the bytes list and not
swp) print without a unit suffix, and without a trailing.00when the value has no fractional part — replaces the current unconditional"%.2f"inrepl_info.c'sValue_u_fvaluebranch with a conditional format. info worker's andinfo resource's resource-listing loops are rewritten from an unstructured per-resourcefprintfinto an aligned table, grouped into labeled sections where applicable to that listing (consumable/capacity resources with aused=split; load/utilization metrics; static hardware attributes such astype/model/cpuf/maxcpus/ndisks—info resource's cluster-wide GLOBAL/SHARED scope, per bug-1593, may not populate every section). Column widths are computed once (a shared field-width format), not hand-padded per line.- Never alters the underlying stored/wire value or the deterministic outcome of a replay (Requirement 6.4) — display-only, same discipline
test.c's ownformat_bytes_human()doc comment states ("the stored/wire value stays raw bytes always, this never feeds back into anything").
Dependencies
- Inbound:
repl_info_worker(),repl_info_resource()(repl_info.c) (P0);repl_cmd_set()(repl_params.c) (P0) - Outbound: none beyond standard C formatting — a self-contained classification/formatting pair, same shape as the Jobid Range Compressor
Contracts: Service [x] / API [ ] / Event [ ] / Batch [ ] / State [x]
Service Interface
/* Returns true iff `name`'s canonical wire unit is bytes (mem, tmp,
* maxmem, maxtmp, maxswap only -- swp is handled separately, see above). */
bool repl_is_bytes_valued_resource(const char* name);
/* Adaptively scales `bytes` to the smallest unit it fits (B/KB/MB/GB/TB/
* PB), writing e.g. "20.56 GB" into `buf` (bufsz-bounded). Display-only;
* never touches the underlying stored value. */
void repl_format_bytes_human(double bytes, char* buf, size_t bufsz);
- Preconditions:
namenon-NULL for the classifier;buf/bufszdescribe a valid, writable buffer for the formatter. - Postconditions: the classifier returns a stable, name-only-dependent boolean; the formatter writes a NUL-terminated, human-readable string.
- Invariants: both are pure functions — no side effects, no dependency on REPL session state.
State Management
- State model: new
units_modefield (enumraw/human/both) onxce_sched_repl_state_s, defaulting toboth; read by the table-rendering code, written only byset units. - Persistence & consistency: in-memory, scoped to one REPL session. Unless the
bothenumerator is deliberately assigned value 0 in itsenumdeclaration,calloc's zero-initialization will not select it —repl_state_ensure()must explicitly setunits_mode = XCE_REPL_UNITS_BOTHafter allocation regardless of member order, the one field in this struct that cannot rely on zero-initialization alone (called out explicitly since every other field in this struct today deliberately relies oncalloc's zero value being the correct default). - Concurrency strategy: none required — single-threaded sim driver.
Help & Breakpoint Discoverability
| Field | Detail |
|---|---|
| Intent | Reorganize help's listing into labeled, aligned sections, and expose break's complete valid-event-type set to both help and its own error message from one shared source |
| Requirements | 7.1, 7.2, 7.3, 7.4, 7.5, 7.6 |
Responsibilities & Constraints
repl_print_help()(repl.c) is rewritten into four labeled sections — Execution Control (step/continue/quit/help), Breakpoints (break/info break/delete), State Inspection (info session/job/jobs/queue/worker/resource/summary/config—info configmoved here, next to its siblings, per Requirement 7.3), Snapshot & Parameters (snapshot/set speed/set output/set verify/set units) — each section using one shared field-widthfprintfformat (e.g." %-38s%s\n") instead of independently hand-counted spaces per line, so a future command addition can't silently break alignment the wayinfo config's append did.BP_KIND_TABLE(repl_break.c,static/file-local today) is exposed through one new shared accessor,repl_bp_kind_names(), that renders the complete comma-joined list of valid<EVENT_TYPE>tokens once. Bothrepl_print_help()'sbreakline (repl.c) andrepl_cmd_break()'s unrecognized-event-type error (repl_break.c, same file as the table — a trivial local call) use this accessor rather than each hardcoding the list — the two can never drift out of sync with each other or withBP_KIND_TABLEitself (Revalidation Triggers).- No change to which commands exist, how any command parses its arguments, or any other command's output (Requirement 7.6) — this component only touches
help's rendering and the two places that now surfaceBP_KIND_TABLE's contents.
Dependencies
- Inbound:
repl_print_help()(repl.c) (P0),repl_cmd_break()'s error path (repl_break.c) (P0) - Outbound:
BP_KIND_TABLE(repl_break.c, existing) (P0)
Contracts: Service [x] / API [ ] / Event [ ] / Batch [ ] / State [ ]
Service Interface
/*
* Renders BP_KIND_TABLE's complete set of valid break <EVENT_TYPE> tokens
* as one comma-joined, NUL-terminated string into `buf` (bufsz-bounded) --
* e.g. "CONFIG_PUSH, JOB_PUSH, ..., DECISION". Used by both
* repl_print_help() and repl_cmd_break()'s unrecognized-event-type error
* so the two can never disagree with each other or with BP_KIND_TABLE.
*/
void repl_bp_kind_names(char* buf, size_t bufsz);
- Preconditions:
buf/bufszdescribe a valid, writable buffer sized for the full table (12 entries at the time of writing). - Postconditions:
bufholds everyBP_KIND_TABLEtoken, comma-joined, in table order. - Invariants: pure function of
BP_KIND_TABLE's current contents — adding/removing a table entry changes this function's output with no other code change required.
Data Models
Domain Model
This feature adds two concepts to the REPL's existing session-scoped state (xce_sched_repl_state_s, repl_internal.h) — not a new top-level entity: a "round collapse state" (Requirements 2, 3) and an independent "resource display preference" (units_mode, Requirement 6). It follows the same pattern prior REPL task revisions used to add output_path/replay_complete to that struct.
Logical Data Model
New fields on xce_sched_repl_state_s (illustrative — exact types finalized at implementation time):
| Field | Type | Purpose |
|---|---|---|
collapse_active |
bool |
Whether a collapsed range is currently open |
collapse_start_round / collapse_end_round |
uint64_t |
Round-number bounds of the active range |
collapse_start_ts / collapse_end_ts |
uint64_t |
Sim-time bounds, for the collapsed range's time span (Requirement 3.4) |
collapse_dispatch_count / collapse_preempt_count |
int |
Cached fingerprint counts |
collapse_pend_jobids / collapse_n_pend_jobids |
pointer + count | Cached fingerprint pend-jobid set, in whatever list form the Jobid Range Compressor consumes |
units_mode |
enum (raw/human/both) |
Requirement 6's resource-value display mode, settable via set units; defaults to both |
Consistency & Integrity: all fields reset together whenever a range flushes (print, then clear) or a new REPL session begins — repl_state_ensure()'s existing calloc zero-initializes new fields with no additional lifecycle code required, except units_mode: unless both is deliberately given enum value 0, calloc's zero-initialization will not select it, so repl_state_ensure() must explicitly set it to both after allocation regardless of the enum's member order — the one field in this struct where zero-initialization alone cannot be relied upon to be the correct default.
No Physical Data Model, API Data Transfer, or Event Schema subsections apply: this feature has no persistent storage, no network wire format, and adds no new wire-level contract — it stays inside scheduler-core's existing "simulation isolation" boundary (repl.h's own header comment: no scheduler.h, no client.h, no capnp types).
Error Handling
Error Strategy
Consistent with existing repl.c/repl_break.c conventions: allocation failures print an existing-style "[sim] ..." message and degrade to the safest available behavior rather than crashing or losing information, matching repl_state_ensure()'s own "never load-bearing, degrade to unattended" precedent.
Error Categories and Responses
- Allocation failure while building a round's fingerprint or growing the collapsed-range pend-jobid buffer: fall back to printing that round's line directly (uncollapsed) rather than losing the round's information — preserves Requirement 2.1's obligation to report every round with decisions.
repl_fmt_jobid_range()buffer too small: returns a negative value; the caller falls back to the existing uncompressed per-id listing for that call — never truncates silently, never crashes, preserves the "never drop a jobid" invariant even in a buffer-constrained edge case.
Monitoring
No new logging framework is introduced — the existing [sim] ... stdout convention remains the only user-facing signal; this is an interactive CLI tool, not a daemon, so no zap/WARN-level logging applies here.
Testing Strategy
Unit Tests (new test_repl_output_compression.c)
- Contiguous indices from one array base compress to a single
[lo-hi]range (Requirement 4.1). - Non-contiguous indices compress to comma-joined sub-ranges within one base (Requirement 4.2).
- Multiple array bases in one listing compress independently (Requirement 4.3).
- A mixed array + non-array listing shows non-array ids individually and compresses only the array ids (Requirement 4.4).
- A buffer too small to hold the full listing returns a negative value and writes nothing past
bufsz(Error Handling).
Unit Tests (new test_repl_resource_formatting.c)
repl_is_bytes_valued_resource()returns true for exactlymem,tmp,maxmem,maxtmp,maxswap, and false forswp, unitless resources (e.g.r1m), and an unknown/custom name (Requirement 6.1).repl_format_bytes_human()adaptively scales across the full B/KB/MB/GB/TB unit range, including exact boundary values (e.g. exactly 1024 bytes renders as1.00 KB, not1024.00 B) (Requirement 6.2).swp's value is converted MB→bytes before formatting, so a rawswpvalue renders on the same adaptive scale as a byte-native resource of equivalent magnitude (Requirement 6.1).- A whole-number unitless value (e.g.
22.0) renders with no trailing.00; a fractional one (e.g.0.57) still renders with its decimal places (Requirement 6.5).
Integration Tests (extend test_repl_breakpoints_inspection.c, test_repl_snapshot_params.c)
set units raw/set units human/set units botheach select the corresponding rendering for a subsequentinfo worker/info resourcecall; an unrecognizedset unitsvalue is rejected with the existingset: unrecognized parametererror shape (Requirement 6.2, 6.3).info worker's resource listing renders as a grouped, aligned table withused=shown only for consumable/capacity resources, matching the hand-validated shape against a real captured worker's resource dump (Requirement 6.6).info worker's assigned-job listing, given a job array's worth of contiguous indices, renders as a single compressed range via the same helper Requirement 4 already covers (Requirement 4.5).break's unrecognized-event-type error message lists the complete, currentBP_KIND_TABLEtoken set (Requirement 7.5).
Integration Tests (extend test_repl_execution_control.c, child-process output capture)
continueacross multiple idle rounds (zero decisions) produces one collapsed range line, not one line per round (Requirement 3.1).continueacross rounds sharing an identical nonzero pend set produces one collapsed "unchanged" range line — the exact shape hand-validated against the real 108-round replay that motivated this spec (Requirement 3.2).- A structural event mid-
continueends any active collapsed range and prints its own line before continuing (Requirement 1.3, 3.5). - A breakpoint hit mid-collapse flushes the active range before entering the command loop (Requirement 3.6).
- Replay completion (EOF) flushes any active collapsed range before the existing "replay complete" banner (Requirement 3.6).
step-mode output is unchanged versus this feature's pre-implementation baseline — regression check alongside the existingtest_should_pause_fires_on_exactly_the_nth_session-style fixtures (Requirement 5.1, 5.3).- A job's tracked status changing between rounds is reflected in the next round summary even under a fixture that never emits a synthetic
DISPATCHdecision — exercises the state-derived-pend-set design decision independent of bug-1611's resolution (Requirement 2.4). - The REPL prompt is the plain
"(sim) "before any session begins, then includes the current session count and a human-readable timestamp once at least one session has run, in bothstepandcontinuemodes (Requirement 5.5). help's output groups commands into the four labeled sections, uses one consistent column width throughout, listsinfo configalongside the otherinfosubcommands, and includes the completebreakevent-type list (Requirement 7.1-7.4).
E2E
Not applicable beyond the integration tests above — this feature has no HTTP/API surface; the child-process harness already exercises the real xce_scheduler binary end-to-end at the process level.
Optional Sections
Security Considerations, Performance & Scalability, and Migration Strategy are omitted: this is a local, single-operator interactive CLI output change with no new attack surface, no persistent or high-load data path (round counts are bounded by replay length, already tolerated by existing step output), and no schema or data migration.
Supporting References
None beyond research.md — no external API, vendor library, or long schema requires separate documentation.