feat(scheduler-core): define decision payload types and snapshot schema (task 7.3)

Adds DispatchDecision (wraps JobAlloc), PendDecision (wraps
JobPendReason), PreemptVictim, PreemptDecision to controller.capnp.
Replaces task 7.1's empty SnapshotData placeholder wholesale with the
real 11-field struct (config/jobs/queues/workers/resources/resvals/
users/projects/prngSeed/internalState/internalVersion), in both the
capnp schema and its typemap.h C mirror. Extends ReplayDecision's
ts-only placeholder with a purely additive body union (dispatch@1/
pend@2/preempt@3).

Deviates from design.md's literal SchedUser/SchedProject mapnames
(xce_sched_user_t/xce_sched_project_t): those names are already real
C structs in sched/types.h, which shares a translation unit with
typemap.h in core_events.c/schedobj.c -- reusing them would be a
genuine duplicate-typedef compile error, not a style choice. Used
fresh mapnames (sched_snapshot_user_t/sched_snapshot_project_t)
instead, field lists unchanged, documented in both files.

Found and fixed a real bug mid-implementation: a doc comment's
literal */ substring prematurely closed a C block comment, corrupting
sched_snapshot_data_t and cascading into ~20 unrelated-looking compile
errors (bug-1493).

Five deep round-trip tests added to test_e2e_capnp.c. Full project
build and controller_unit_test (1082/1082) verified clean, confirmed
independently three times.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dw5WnN3DXStaEaPYfACU1
This commit is contained in:
Rongsong Shen 2026-08-04 13:11:26 +00:00
parent 755230705c
commit bbb3972ae6
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
4 changed files with 755 additions and 33 deletions

View file

@ -999,17 +999,26 @@ struct CtrlHelloReply $C.mapname("ctrl_hello_reply_t") {
# - task 7.2: adds the new JobStateChangeEvent + JobUpdateEvent structs,
# then adds two new union members (jobStateChange, jobUpdate) to
# ReplayEvent's `body` union at new ordinals (10, 11) -- purely additive.
# - task 7.3: adds SnapshotData's real 11 fields (Req 5 AC 21) starting at
# ordinal @0 -- replacing this task's zero-field placeholder wholesale,
# both in this schema and in typemap.h's sched_snapshot_data_t (which
# has no capnp-visible fields yet, so nothing there is "renumbered"
# either). task 7.3 also adds DispatchDecision/PendDecision/
# PreemptVictim/PreemptDecision, then adds a `body :union { dispatch @1:
# DispatchDecision; pend @2: PendDecision; preempt @3: PreemptDecision;
# }` to ReplayDecision -- purely additive new ordinals on the ts@0
# struct this task creates (see the JobControl/JoinData `:union` group
# precedent above: a union group name never consumes its own ordinal,
# only its member variants do, so this is safe to bolt on later).
# - task 7.3 (DONE): added SnapshotData's real 11 fields (Req 5 AC 21)
# starting at ordinal @0, replacing task 7.1's zero-field placeholder
# wholesale (both here and in typemap.h's sched_snapshot_data_t). Added
# DispatchDecision/PendDecision/PreemptVictim/PreemptDecision, then
# added `body :union { dispatch @1: DispatchDecision; pend @2:
# PendDecision; preempt @3: PreemptDecision; }` to ReplayDecision --
# purely additive new ordinals on the ts@0 struct task 7.1 created (see
# the JobControl/JoinData `:union` group precedent above: a union group
# name never consumes its own ordinal, only its member variants do).
# DEVIATION from design.md's literal text: SnapshotData also needed two
# new user/project wire types. design.md's literal spec names the
# mapname target `xce_sched_user_t`/`xce_sched_project_t` (the
# scheduler-core-internal C types in src/scheduler/include/sched/
# types.h) -- but that header is scheduler-private, unreachable from
# typemap.h, and already `#include`s typemap.h itself, so reusing that
# exact name here would be a guaranteed duplicate-typedef compile error
# across src/scheduler/. Used fresh mapnames `sched_snapshot_user_t`/
# `sched_snapshot_project_t` instead (see `struct SchedUser` comment
# below for the full rationale) -- field lists still match design.md's
# spec exactly, only the C type name differs.
struct ReplayCluster $C.mapname("replay_cluster_t") {
name @0: Text;
@ -1026,14 +1035,61 @@ struct ReplayHeader $C.mapname("replay_header_t") {
schedPolicy @5: Text $C.mapname("sched_policy"); # active picker plugin name (e.g. "builtin_fcfs")
}
# Placeholder -- task 7.3 replaces this with the full snapshot schema
# (Req 5 AC 21): config, jobs, queues, workers, resources, resvals, users,
# projects, prngSeed, internalState, internalVersion (see design.md's Wire
# Protocol > ReplayFormat section for the already-committed 11-field
# layout). Deliberately left with ZERO capnp fields here (instead of a
# placeholder scalar) so task 7.3 is free to start numbering its real
# fields at ordinal @0 exactly as design.md specifies, with no renumbering.
# task 7.3 (Req 5 AC 21): the two scheduler-only user/project wire types
# SnapshotData embeds. design.md's literal spec reuses the scheduler-core
# core-internal C type names xce_sched_user_t/xce_sched_project_t
# (sched/types.h) verbatim as the mapname target -- but those types are
# scheduler-PRIVATE (sched/types.h is under src/scheduler/include/, out of
# this schema-only task's edit scope, and is NOT reachable from typemap.h,
# which every OTHER binary linking controller.capnp -- controller, apiserver
# CGO, xceadmin, worker -- also compiles against). sched/types.h itself
# `#include`s typemap.h (confirmed: scheduler/src/core_events.c and
# schedobj.c already include both headers in the same translation unit), so
# adding a second, textually-distinct `xce_sched_user_t`/`xce_sched_project_t`
# typedef to typemap.h would be a guaranteed duplicate-typedef compile error
# across most of src/scheduler/ the moment both headers are included
# together -- not hypothetical, a certainty given the current include graph.
# Field lists match design.md's literal spec exactly (verified against
# sched/types.h: xce_sched_user_t has name only; xce_sched_project_t has
# name+priority) -- only the C TARGET TYPE NAME differs, to stay reachable
# from typemap.h without touching src/scheduler/. This mirrors the
# PreemptVictim/PreemptDecision precedent below: a fresh, wire-only mapname
# distinct from a similarly-shaped core-internal type of a different name.
struct SchedUser $C.mapname("sched_snapshot_user_t") {
name @0: Text;
}
struct SchedProject $C.mapname("sched_snapshot_project_t") {
name @0: Text;
priority @1: Int32;
}
# task 7.3 (Req 5 AC 21): the full snapshot message type, replacing task
# 7.1's zero-field placeholder wholesale. Every domain-object field reuses
# a pre-existing wire struct (Conf4Sched/SchedJobData/QueueConf/WorkerData/
# ResourceData/ResValList, all already used by ReplayEvent above) except
# SchedUser/SchedProject (fresh, see comment above) and the two
# scheduler-internal-only opaque fields. internalState is a versioned
# opaque blob (shared-resource tracking + unsatisfiable-resreq cache +
# session/iterator state) with no capnp-visible structure -- the core owns
# its byte layout; internalVersion is a format tag so a future incompatible
# internal layout is rejected rather than misinterpreted (this task's
# Observable). Like every other Data field in this schema (see
# ReplayHeader.configHash above), internalState's generated encode_/decode_
# codec is a documented no-op -- real extraction is hand-written
# low-level-accessor code, task 9+'s job, same pattern.
struct SnapshotData $C.mapname("sched_snapshot_data_t") {
config @0: Conf4Sched;
jobs @1: SchedJobData;
queues @2: List(QueueConf) $C.maplistcount("n_queues");
workers @3: WorkerData;
resources @4: ResourceData;
resvals @5: ResValList;
users @6: List(SchedUser) $C.maplistcount("n_users");
projects @7: List(SchedProject) $C.maplistcount("n_projects");
prngSeed @8: UInt64 $C.mapname("prng_seed");
internalState @9: Data $C.mapname("internal_state");
internalVersion @10: UInt32 $C.mapname("internal_version");
}
struct SnapshotEntry $C.mapname("replay_snapshot_entry_t") {
@ -1091,12 +1147,47 @@ struct SessionSummary $C.mapname("session_summary_t") {
nPended @3: UInt32 $C.mapname("n_pended");
}
# Placeholder -- task 7.3 adds `body :union { dispatch @1: DispatchDecision;
# pend @2: PendDecision; preempt @3: PreemptDecision; }` here once those
# payload types exist (purely additive new ordinals 1-3; ts@0 untouched --
# see the JobControl/JoinData precedent noted above).
# task 7.3 (Req 5 AC 5, AC 20): the three decision payload types. dispatch
# and pend reuse pre-existing wire structs verbatim (JobAlloc, JobPendReason
# -- design review already confirmed JobAlloc.jobid is DispatchDecision's
# sole job-identity carrier, no redundant jobid field). PreemptVictim is
# fresh (jobid+workerid has no pre-existing wire counterpart). PreemptVictim/
# PreemptDecision are DELIBERATELY distinct wire-only types from decision.h's
# core-internal xce_preempt_victim_t/xce_preempt_t (task 4.4's in-process
# live-decision representation) -- the wire format and the core's live
# in-process decision representation are independent, never-aliased things
# that happen to model similar data.
struct DispatchDecision $C.mapname("sched_dispatch_decision_t") {
alloc @0: JobAlloc;
}
struct PendDecision $C.mapname("sched_pend_decision_t") {
jobid @0: UInt64;
reason @1: JobPendReason;
}
struct PreemptVictim $C.mapname("preempt_victim_t") {
jobid @0: UInt64;
workerid @1: Text;
}
struct PreemptDecision $C.mapname("sched_preempt_decision_t") {
jobid @0: UInt64;
victims @1: List(PreemptVictim) $C.maplistcount("n_victims");
reason @2: JobPendReason;
}
# task 7.3 (Req 5 AC 20): purely additive `body` union on top of task 7.1's
# ts@0-only stub -- new ordinals 1-3, ts@0 untouched (see the JobControl/
# JoinData precedent noted above: a union group name never consumes its own
# ordinal).
struct ReplayDecision $C.mapname("replay_decision_t") {
ts @0: UInt64;
body :union $C.mapuniontag("body_which") {
dispatch @1: DispatchDecision;
pend @2: PendDecision;
preempt @3: PreemptDecision;
}
}
struct ReplaySession $C.mapname("replay_session_t") {