feat(scheduler-core): implement REPL execution control (task 12.1)

Adds the interactive simulation REPL's execution-control slice (Req 5
AC 22): a single-threaded input loop (readline-based) supporting
step [N], continue, quit, and help, pausing before the first replay
event and blocking the driver loop while active.

Review round 1 raised two findings, both resolved here:

- GNU readline (GPL) was linked directly into xce_scheduler, a shipped
  production daemon. Switched the whole project to libedit (BSD) for
  line editing -- scheduler and controller (xce_test) both now use
  editline/readline.h, API-compatible (readline()/add_history()).

- Getting a clean regression run after the libedit swap required a
  full clean rebuild, which exposed a genuine pre-existing SIGSEGV
  (bug-1504): SnapshotData.internalState was a capnp Data field, and
  this codebase's capnpc-c backend silently no-ops Data-field
  encoding without zero-initializing the caller's stack struct,
  leaving an uninitialized capn_ptr written onto the wire. Fixed by
  switching internalState to List(UInt8) (with a new internalStateLen
  wire field), matching the working FetchFileReply.content/bytesRead
  precedent -- no hand-written accessor code needed.

A second, unrelated instance of the same bug class was found in the
controller's WAL-catchup path (WalCatchupEntry.payload) and
deliberately left unfixed as bug-1505, out of scheduler-core's
boundary; needs its own project-wide Data-field audit.

Scheduler suite: 34/34 clean. RED-phase reproduction recorded in
tasks.md (repl_should_pause() step-budget check disabled -> 2/4
tests fail as expected -> reverted -> 34/34 green).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JPPXeKSUXUqcqvzNXY1rzJ
This commit is contained in:
Rongsong Shen 2026-08-07 08:17:35 +00:00
parent de00ccff37
commit 02f7f48ff9
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
17 changed files with 1491 additions and 81 deletions

View file

@ -623,13 +623,19 @@ typedef struct {
* placeholder wholesale. Every domain-object pointer reuses a pre-existing
* native type (already used natively throughout the scheduler/controller)
* except users/projects (sched_snapshot_user_t/sched_snapshot_project_t
* above) and the two scheduler-internal-only opaque fields. internal_state
* follows the same raw uint8_t-pointer + size_t pattern as every other Data
* field in this file (e.g. replay_header_t.config_hash above) -- the generated
* encode_SnapshotData/decode_SnapshotData codec is a documented no-op for
* it; real extraction is task 9+ hand-written code. internal_version is a
* format tag for internal_state so an incompatible future internal layout
* is rejected rather than misinterpreted (this task's Observable). */
* above) and the two scheduler-internal-only opaque fields. internal_version
* is a format tag for internal_state so an incompatible future internal
* layout is rejected rather than misinterpreted (this task's Observable).
*
* internal_state/internal_state_len map controller.capnp's internalState,
* a List(UInt8) (task 12.1 review finding, fixed -- was Data, whose
* generated codec is a silent no-op in this codebase's capnpc-c setup and
* left a genuinely uninitialized capn_ptr on the wire, a real reproducible
* crash; see controller.capnp's SnapshotData comment for the full story).
* List(UInt8)'s generated encode_/decode_ actually runs (proven precedent:
* xce_fetchfile_reply_t.content/bytes_read below), so internal_state_len
* must be uint32_t to match internalStateLen's UInt32 wire type -- same
* maplistcount convention as content/bytes_read. */
typedef struct {
xce_conf4sched_t *config;
xce_jobs_data_t *jobs;
@ -644,7 +650,7 @@ typedef struct {
int n_projects;
uint64_t prng_seed;
uint8_t *internal_state;
size_t internal_state_len;
uint32_t internal_state_len;
uint32_t internal_version;
} sched_snapshot_data_t;