docs(scheduler-sim-repl-verbosity): add approved spec (requirements/design/tasks)
Full spec scaffold (requirements/design/research/tasks all approved, ready_for_implementation=true) plus a roadmap backlog item recorded from the dirty-content-fingerprint spec's task 4.3 findings (Makefile/meson fingerprint-computation duplication risk). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
1c906e955c
commit
979cd24f6c
6 changed files with 519 additions and 0 deletions
291
.kiro/specs/scheduler-sim-repl-verbosity/design.md
Normal file
291
.kiro/specs/scheduler-sim-repl-verbosity/design.md
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
# 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 only 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`. No change to scheduling decisions, replay determinism, `step`-mode output, or any other REPL command.
|
||||
|
||||
### Goals
|
||||
- Suppress routine per-event echo during `continue` execution, 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 (Requirement 4).
|
||||
- Leave `step` mode and every other existing REPL command byte-for-byte unchanged (Requirement 5).
|
||||
|
||||
### Non-Goals
|
||||
- New REPL commands or newly inspectable data (limit usage, pend-reason detail) — tracked separately under the `scheduler-sim-diagnostics` brief.
|
||||
- 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 `DISPATCH` decisions 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`).
|
||||
|
||||
## Boundary Commitments
|
||||
|
||||
### This Spec Owns
|
||||
- Console output formatting/verbosity logic inside `xce_repl_on_event()` (repl_break.c), the `continue`-mode gate on `sim_print_decision()`'s call site (sim_loop.c), and the new round-boundary rollup/collapse logic added at `xce_repl_post_session_check()` (repl.c).
|
||||
- The new jobid-range-compression helper and every call site that adopts it.
|
||||
- New `xce_sched_repl_state_s` fields required to track collapse state across rounds.
|
||||
|
||||
### Out of Boundary
|
||||
- Any new `info`/`break`/`set` subcommand or newly inspectable scheduler data.
|
||||
- Scheduling/allocation/decision logic itself (`scheduler.c`, `src/plugins/`).
|
||||
- `sim_print_decision()`'s behavior when `sim->interactive` is false (headless mode) — unchanged.
|
||||
- Root-causing bug-1611 — a separate investigation; this spec only ensures its own requirements hold regardless of the answer.
|
||||
|
||||
### Allowed Dependencies
|
||||
- Existing `xce_sched_repl_state_s` fields (`mode`, `last_n_dispatch`/`last_n_pend`/`last_n_preempt`, `session_count`) — repl_internal.h.
|
||||
- The existing `core->jobs` iteration idiom (`iter_make`/`iter_next`, `JOB_IS_PEND`) already used by `repl_info_jobs()`.
|
||||
- Existing helpers `fmt_jobid_ex()` (lib/utils.c) and `repl_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`, the `xce_repl_check_event` wrapper) — that file is documented at its line-budget ceiling (repl.h's own header comment).
|
||||
|
||||
### Revalidation Triggers
|
||||
- If `scheduler-sim-diagnostics` adds 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 existing `mode`/tally fields by another spec should re-check this feature's rollup logic.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Existing Architecture Analysis
|
||||
- `repl.c`/`repl_break.c`/`repl_info.c`/`repl_params.c` form the REPL command family under `scheduler-core`. `repl.h` folds the `sim->interactive` gate into `static inline` wrapper functions so `sim_loop.c`'s own diff stays to single-line, brace-free call expressions — this feature's `sim_loop.c` change must follow that same convention rather than adding a bare `if` block to the loop body.
|
||||
- `xce_sched_repl_state_s` (repl_internal.h) is the REPL's own session-scoped state struct, zero-initialized via `calloc` in `repl_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 the `step`-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.
|
||||
|
||||
```mermaid
|
||||
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 that `repl_break.c`'s event/breakpoint/decision hooks and `repl.c`'s replay-completion hook all call — no new hook call sites added to `sim_loop.c`.
|
||||
- Domain/feature boundaries: the Round Rollup's state and flush/print logic live in `repl.c`; `repl_break.c` calls the flush entry point but does not own or mutate the collapse state directly; `sim_loop.c` only gains a mode gate on its two existing calls.
|
||||
- Existing patterns preserved: `repl.h`'s `sim->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 in `repl_internal.h` for `repl_break.c` to 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 on `xce_sched_repl_state_s` rather 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 `.c` file 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` — extend `xce_sched_repl_state_s` with round-collapse state fields (see Data Models) and declare the new shared jobid-range-compression helper.
|
||||
- `src/scheduler/src/repl_break.c` — `xce_repl_on_event()` gains routine-vs-structural event classification gated on `rs->mode` (Requirement 1); `repl_print_event_summary()`'s `JOB_PUSH`-style cases adopt the jobid-range-compression helper (Requirement 4); `xce_repl_on_event()`, `xce_repl_on_begin_round()`, and `xce_repl_on_decision()` each call the Round Rollup's flush entry point before printing a structural-event line or before re-entering `repl_command_loop()` on a breakpoint hit (Requirement 3.5, 3.6).
|
||||
- `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.
|
||||
- `src/scheduler/src/sim_loop.c` — the existing call site invoking `sim_print_decision()` gains a `continue`-mode gate, mirroring `repl.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
|
||||
- `src/scheduler/testsuite/test_repl_execution_control.c` — extended with child-process output-capture tests covering Requirements 1, 2, 3, and the Requirement 5 no-regression checks (this file already drives `sched_sim_driver_run()` end-to-end and captures real process output via `child_continue_then_inspect`-style helpers).
|
||||
- `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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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 | Event Echo Gate, `sim_print_decision` gate | mode checks | n/a |
|
||||
|
||||
## 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 Output (repl.c / repl_break.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 while `continue` is 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 `step` mode): `RESVALS_DELTA`, `JOB_STATE_CHANGE`, `JOB_UPDATE`, `QUEUE_UPDATE`, `JOB_RUSAGE`. `JOB_STATE_CHANGE` is classified routine deliberately — the Round Rollup (Requirement 2.4) already surfaces job status transitions at round granularity, so an individual `JOB_STATE_CHANGE` line under `continue` would duplicate that information.
|
||||
- Breakpoint matching (`repl_find_event_breakpoint`, plus the equivalent checks in `xce_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-entering `repl_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`; `step` mode keeps today's full per-decision path unchanged (Requirement 2.3, 5.1).
|
||||
- Pend-jobid set is sourced by directly querying `core->jobs` for `JOB_IS_PEND` state (same iteration idiom as `repl_info_jobs()`) — not solely from the decision tally, so Requirement 2.4 holds independent of bug-1611 (see `research.md` Design Decisions).
|
||||
- Dispatch/preempt counts are sourced from the existing `last_n_dispatch`/`last_n_preempt` tally.
|
||||
- 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 in `repl_break.c`, not `repl.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 in `xce_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->jobs` iterator (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
|
||||
```c
|
||||
/*
|
||||
* 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_state` non-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_s` hold 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 existing `calloc`.
|
||||
- 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 and the Round Rollup's pend-jobid display (Requirement 4.5).
|
||||
|
||||
**Dependencies**
|
||||
- Inbound: `repl_print_event_summary` (P0), Round Rollup (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
|
||||
```c
|
||||
/*
|
||||
* 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: `jobids` non-NULL whenever `njobids > 0`; `buf`/`bufsz` describe a valid, writable buffer.
|
||||
- Postconditions: `buf` holds 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`; `step` mode 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 with `repl.h`'s established convention, rather than a bare `if` block added to the loop body.
|
||||
|
||||
**Contracts**: Service [ ] / API [ ] / Event [ ] / Batch [ ] / State [x]
|
||||
|
||||
##### State Management
|
||||
- State model: reads `sim->interactive` and `sim->repl_state->mode` (both existing fields); owns no new state.
|
||||
|
||||
## Data Models
|
||||
|
||||
### Domain Model
|
||||
This feature adds a "round collapse state" concept to the REPL's existing session-scoped state (`xce_sched_repl_state_s`, repl_internal.h) — not a new top-level entity. 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 |
|
||||
|
||||
**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.
|
||||
|
||||
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).
|
||||
|
||||
### Integration Tests (extend `test_repl_execution_control.c`, child-process output capture)
|
||||
- `continue` across multiple idle rounds (zero decisions) produces one collapsed range line, not one line per round (Requirement 3.1).
|
||||
- `continue` across 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-`continue` ends 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 existing `test_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 `DISPATCH` decision — exercises the state-derived-pend-set design decision independent of bug-1611's resolution (Requirement 2.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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue