@0xdefcdf6377db2e8e; using C = import "/c.capnp"; $C.fieldgetset; $C.codecgen; $C.extraheader("#include \"clusterconfig.h\""); $C.extraheader("#include \"typemap.h\""); enum OPCode { nop @0; reply @1; hello @2; join @3; shutdown @4; get @5; push @6; update @7; jobdispatch @8; startjob @9; notify @10; jobsub @11; jobstatus @12; jobrusage @13; hostcontrol @14; jobcontrol @15; newschedjobs @16; clusterconf @17; queuecontrol @18; runtasks @19; setpreason @20; clrpreason @21; replEntry @22; replAck @23; redirect @24; heartbeat @25; steppingDown @26; replSnapshot @27; replCatchUpDone @28; replConfig @29; replConfigPush @30; upgradeConfig @31; requestVote @32; probeConfigTag @33; walAck @34; upgradeConfigAck @35; snapshotTransfer @36; walCatchupEntry @37; preVoteRequest @38; preVoteReply @39; ctrlstatus @40; fetchfile @41; schedAdmin @42; } struct PeerInfo $C.mapname("xce_peerinfo_t") { host @0: Text $C.mapname("name"); key @1: Text; } struct HostBasedReason $C.mapname("xce_host_reason_t") { host @0: Text; reason @1: UInt32; } struct ResourceBasedReason $C.mapname("xce_res_reason_t") { resource @0: Text; host @1: Text; reason @2: UInt32; } struct JobPendReason $C.mapname("xce_job_preason_t") { mainReason @0: UInt32 $C.mapname("main_reason"); hostReasons @1: List(HostBasedReason) $C.mapname("host_based_reasons") $C.maplistcount("nhost_reasons"); resReasons @2: List(ResourceBasedReason) $C.mapname("res_based_reasons") $C.maplistcount("nres_reasons"); } struct StrList $C.mapname("xce_strlist_t") { data @0: List(Text) $C.mapname("data") $C.maplistcount("count"); } struct TimeWindowTime $C.mapname("xce_timewindow_time_t") { day @0: UInt32 $C.mapname("day"); hour @1: UInt32 $C.mapname("hour"); min @2: UInt32 $C.mapname("min"); } struct TimeWindowSeg $C.mapname("xce_timewindow_seg_config_t") { timezone @0: Text $C.mapname("tz"); open @1: TimeWindowTime $C.mapname("open"); close @2: TimeWindowTime $C.mapname("close"); } struct TimeWindow $C.mapname("xce_timewindow_config_t") { segs @0: List(TimeWindowSeg) $C.mapname("segs") $C.maplistcount("nsegs"); } struct GroupConfig $C.mapname("xce_group_config_t") { name @0: Text $C.mapname("name"); members @1: List(Text) $C.mapname("members.data") $C.maplistcount("members.count"); exmembers @2: List(Text) $C.mapname("ex_members.data") $C.maplistcount("ex_members.count"); } # This data structure will not be transfer in wire # directly, it will be assembled by multiple Data packet struct Dataset $C.mapname("dataset_t") { table @0: UInt32; nrows @1: UInt32; rows @2: Void; # do NOT change this, it's place holder } struct ResKey $C.mapname("xce_reskey_t") { resname @0: Text; hostname @1: Text; } struct MatchCondition $C.mapname("xce_match_t") { condition :union $C.mapuniontag("type") { matchall @0: Void; matchjid @1: UInt64 $C.mapname("jobid"); matchwname @2: Text $C.mapname("wname"); matchreskey @3: ResKey $C.mapname("reskey"); } } struct HostAttrs $C.mapname("hostattrs_t") { type @0: Text $C.mapname("hosttype"); model @1: Text $C.mapname("hostmodel"); cpufactor @2: Float64; maxcpus @3: Int32; maxmem @4: Float64; maxswap @5: Float64; maxtmp @6: Float64; ndisks @7: Int32; } struct Worker $C.mapname("xce_worker_t"){ name @0: Text; attrs @1: HostAttrs; status @2: UInt32; dispatchWindow @3: TimeWindow $C.mapname("dispatch_window_config"); thresholds @4: List(Threshold) $C.mapname("thresholds") $C.maplistcount("nthresholds"); resvals @5: List(NameValue) $C.mapname("resvals_to_sched") $C.maplistcount("nresvals_to_sched"); deadSince @6: UInt64 = 0 $C.mapname("dead_since"); # epoch seconds; set when worker becomes UNAVAILABLE } enum HostRole { client @0; worker @1; scheduler @2; controller @3; apiserver @4; } struct JoinData $C.mapname("xce_joindata_t") { role @0: HostRole; data :union { nodata @1: Void; attrs @2: HostAttrs; } } struct Value $C.mapname("xce_val_t"){ u :union $C.mapuniontag("which") { ivalue32 @0: Int32; uvalue32 @1: UInt32; ivalue64 @2: Int64; uvalue64 @3: UInt64; svalue @4: Text; fvalue @5: Float64; bvalue @6: Bool; } } struct LimitValue $C.mapname("xce_limit_val_t") { resname @0: Text $C.mapname("resname"); percentage @1: Bool $C.mapname("percent"); value @2: Float64 $C.mapname("val"); } struct ValueWithIdx $C.mapname("xce_val_with_idx_t") { idx @0: UInt32; val @1: Value $C.mapname("val"); } struct NameValue $C.mapname("xce_name_val_t") { name @0: Text $C.mapname("name"); val @1: Value $C.mapname("val"); } struct ConfigVar $C.mapname("xce_config_t") { name @0: Text; value @1: Text; } struct ResourceDistribution $C.mapname("res_distribution_t") { value @0: Value; hosts @1: List(Text) $C.maplistcount("nhosts"); } struct Resource $C.mapname("xce_resource_t"){ name @0: Text; desc @1: Text; idx @2: Int32; valtype @3: Int32; ordtype @4: Int32; flags @5: Int32; interval @6: Int32; lastReport @7: UInt64 $C.mapname("last_report"); # distributions @8: List(ResourceDistribution) $C.maplistcount("ndistributions"); # hostBased @9: Value $C.mapname("hbased_defined"); } struct Threshold $C.mapname("xce_res_threshold_t"){ name @0: Text; sched @1: Float64; stop @2: Float64; } struct QueueConf $C.mapname("xce_queue_t") { name @0: Text $C.mapname("queue"); attrs @1: UInt32; status @2: UInt32; priority @3: Int32; acceptIntervals @4: Int32 $C.mapname("accept_interval"); schedDelay @5: Int32 $C.mapname("sched_delay"); resreq @6: Text; users @7: GroupConfig $C.mapname("users"); hosts @8: GroupConfig $C.mapname("hosts"); thresholds @9: List(Threshold) $C.maplistcount("nthresholds"); preExec @10: Text $C.mapname("pre_exec"); postExec @11: Text $C.mapname("post_exec"); runWindow @12: TimeWindow $C.mapname("runwindow_config"); dispatchWindow @13: TimeWindow $C.mapname("dispatchwindow_config"); } struct Conf4Sched $C.mapname("xce_conf4sched_t"){ clustername @0: Text; configvars @1: List(ConfigVar) $C.mapname("confvars") $C.maplistcount("n_confvars"); resources @2: List(Resource) $C.maplistcount("n_resources"); queues @3: List(QueueConf) $C.maplistcount("n_queues"); ugroups @4: List(GroupConfig) $C.maplistcount("n_ugroups"); hgroups @5: List(GroupConfig) $C.maplistcount("n_hgroups"); resvals @6: List(NameValue) $C.mapname("resvals") $C.maplistcount("nresvals"); hproviders @7: List(ScalerConfig) $C.mapname("hproviders") $C.maplistcount("nhproviders"); limits @8: List(LimitConfig) $C.mapname("limits") $C.maplistcount("nlimits"); } struct Conf4Worker $C.mapname("xce_conf4worker_t") { clustername @0: Text; configvars @1: List(ConfigVar) $C.mapname("confvars") $C.maplistcount("n_confvars"); } struct ConfigData $C.mapname("xce_configdata_t") { data :union $C.mapuniontag("data_which") { schedconfig @0: Conf4Sched $C.mapname("conf4sched"); workerconfig @1: Conf4Worker $C.mapname("conf4worker"); clusterconfig @2: ClusterConfig $C.mapname("clusterconfig"); } } struct JobPIDInfo $C.mapname("job_pidinfo_t"){ jobId @0: UInt64 $C.mapname("jobid"); pids @1: List(UInt32) $C.mapname("pids") $C.maplistcount("npids"); pgids @2: List(UInt32) $C.mapname("pgids") $C.maplistcount("npgids"); exdata @3: Text $C.mapname("exdata"); } # bug-1690: xce_job_runusage_t (xce/types.h) previously had NO Cap'n Proto # mapping at all -- unlike JobAlloc/JobPIDInfo above, which already existed # as Job sub-fields the WAL_JOB_DISPATCH/WAL_JOB_START fixes (bug-1678/1687) # could reuse standalone. WAL_JOB_RUSAGE needs one to carry the client- # reported per-host CPU/mem usage in its fact payload so a restart/catch-up # replay can reconstruct job.run_usage instead of leaving it permanently # NULL. NOT the same type as JobRusages/SchedJobRusages below (xce_job_ # rusages_t) -- that is the client's WIRE UPDATE REQUEST shape # ({jobid, pids[], pgids[], usages[]}); this is the CONTROLLER'S ACCUMULATED # per-job/per-host usage shape, a different struct entirely. struct HostRunusage $C.mapname("xce_host_runusage_t"){ hostName @0: Text $C.mapname("host_name"); cpuTime @1: Int32 $C.mapname("cpu_time"); memUsage @2: Float64 $C.mapname("mem_usage"); memSwap @3: Float64 $C.mapname("mem_swap"); tasksCurrent @4: Int32 $C.mapname("tasks_current"); } struct JobRunusage $C.mapname("xce_job_runusage_t"){ maxMem @0: Float64 $C.mapname("max_mem"); avgMem @1: Float64 $C.mapname("avg_mem"); usedMem @2: Float64 $C.mapname("used_mem"); cpuTime @3: Int32 $C.mapname("cpu_time"); peakCpuTime @4: Int32 $C.mapname("peak_cpu_time"); peakCpuDuration @5: Int32 $C.mapname("peak_cpu_duration"); memEfficiency @6: Float32 $C.mapname("mem_efficiency"); avgCpuEfficiency @7: Float32 $C.mapname("avg_cpu_efficiency"); peakCpuEfficiency @8: Float32 $C.mapname("peak_cpu_efficiency"); hostRunusage @9: List(HostRunusage) $C.mapname("host_runusage") $C.maplistcount("num_hosts"); } struct JobFail $C.mapname("job_failinfo_t"){ jobId @0: UInt64 $C.mapname("jobid"); code @1: Int32 $C.mapname("code"); desc @2: Text $C.mapname("detail"); stage @3: UInt32 $C.mapname("stage"); } struct JobStartInfo $C.mapname("job_startinfo_t"){ exechome @0: Text; execcwd @1: Text; execuser @2: Text; cmdline @3: Text; exdata @4: Text; launchtype @5: Text $C.mapname("launch_type"); qPreExec @6: Text $C.mapname("q_pre_exec"); qPostExec @7: Text $C.mapname("q_post_exec"); preExec @8: Text $C.mapname("job_pre_exec"); postExec @9: Text $C.mapname("job_post_exec"); } struct Job $C.mapname("xce_job_t"){ jobid @0: UInt64; submitData @1: JobSubmitData $C.mapname("submit"); subtime @2: UInt64 $C.mapname("sub_time"); queuetime @3: UInt64 $C.mapname("queue_time"); starttime @4: UInt64 $C.mapname("start_time"); finishtime @5: UInt64 $C.mapname("finish_time"); state @6: UInt32; allocs @7: JobAlloc $C.mapname("allocs"); runtime @8: UInt32 $C.mapname("run_time"); pidinfo @9: JobPIDInfo; pendreason @10: JobPendReason $C.mapname("pend_reasons"); transientSince @11: UInt64 = 0 $C.mapname("transient_since"); # epoch seconds; set when job enters JOB_STAT_TRANSIENT } struct SchedJob $C.mapname("xce_job_t"){ jobid @0: UInt64; submitData @1: JobSubmitData $C.mapname("submit"); subtime @2: UInt64 $C.mapname("sub_time"); queuetime @3: UInt64 $C.mapname("queue_time"); starttime @4: UInt64 $C.mapname("start_time"); finishtime @5: UInt64 $C.mapname("finish_time"); state @6: UInt32; allocs @7: JobAlloc $C.mapname("allocs"); runtime @8: UInt32 $C.mapname("run_time"); } struct SchedJobRusages $C.mapname("xce_job_rusages_t") { jobid @0: UInt64 $C.mapname("jobid"); usages @1: List(ResUsage) $C.mapname("usages") $C.maplistcount("count"); } struct JobData $C.mapname("xce_jobs_data_t") { ntotal @0: UInt32 $C.mapname("n_total"); pos @1: UInt32; jobs @2: List(Job) $C.maplistcount("count"); } struct SchedJobData $C.mapname("xce_jobs_data_t") { ntotal @0: UInt32 $C.mapname("n_total"); pos @1: UInt32; jobs @2: List(SchedJob) $C.maplistcount("count"); } struct WorkerData $C.mapname("xce_workers_data_t"){ ntotal @0: UInt32 $C.mapname("n_total"); pos @1: UInt32; workers @2: List(Worker) $C.maplistcount("count"); } struct ResourceData $C.mapname("xce_resource_data_t") { ntotal @0: UInt32 $C.mapname("n_total"); pos @1: UInt32; resources @2: List(Resource) $C.maplistcount("count"); } struct ResourceVal $C.mapname("xce_resval_t") { name @0: Text $C.mapname("name"); host @1: Text $C.mapname("host"); val @2: Value $C.mapname("val"); } struct ResourceVals $C.mapname("xce_resval_data_t") { ntotal @0: UInt32 $C.mapname("n_total"); pos @1: UInt32; vals @2: List(ResourceVal) $C.mapname("vals") $C.maplistcount("count"); } struct XceData $C.mapname("xce_data_t"){ data :union $C.mapuniontag("table") { schedjobs @0: SchedJobData $C.mapname("jobs"); schedworkers @1: WorkerData $C.mapname("workers"); jobs @2: JobData $C.mapname("jobs"); workers @3: WorkerData $C.mapname("workers"); resources @4: ResourceData $C.mapname("resources"); resvals @5: ResourceVals $C.mapname("resvals"); jobArray @6: JobSubmit $C.mapname("job_array"); } } struct Get $C.mapname("xce_get_t") { table @0: UInt32; condition @1: MatchCondition; } struct ResValList $C.mapname("xce_resval_list_t") { vals @0: List(ResourceVal) $C.mapname("vals") $C.maplistcount("count"); } struct ResUsage $C.mapname("xce_rusage_t") { name @0: Text $C.mapname("resname"); value @1: Float64 $C.mapname("value"); } struct JobRusages $C.mapname("xce_job_rusages_t") { jobid @0: UInt64 $C.mapname("jobid"); pids @1: List(Int32) $C.mapname("pids") $C.maplistcount("npids"); pgids @2: List(Int32) $C.mapname("pgids") $C.maplistcount("npgids"); usages @3: List(ResUsage) $C.mapname("usages") $C.maplistcount("count"); } struct Update $C.mapname("xce_update_data_t") { data :union $C.mapuniontag("table") { nodata @0: Void; resvals @1: ResValList $C.mapname("resvals"); jobrusages @2: JobRusages $C.mapname("jobrusages"); } } struct ResAlloc $C.mapname("xce_alloc_t"){ name @0: Text $C.mapname("resname"); resvId @1: Text $C.mapname("reserve"); instid @2: UInt64; value @3: Float64; from @4: UInt64; # alloc start time duration @5: Int64; } struct HostAlloc $C.mapname("xce_host_alloc_t"){ host @0: Text; allocs @1: List(ResAlloc) $C.mapname("allocs") $C.maplistcount("nallocs"); } struct JobAlloc $C.mapname("xce_job_alloc_t"){ jobid @0: UInt64; allocs @1: List(HostAlloc) $C.mapname("allocs") $C.maplistcount("nhosts"); allocsShared @2: List(ResAlloc) $C.mapname("allocs_shared") $C.maplistcount("nres_shared"); } struct JobDispatch $C.mapname("xce_job_dispatch_t"){ allocs @0: List(JobAlloc) $C.maplistcount("njobs"); } struct HostLaunch $C.mapname("xce_host_launch_t") { host @0: Text $C.mapname("host"); nranks @1: UInt32 $C.mapname("nranks"); } struct JobLaunch $C.mapname("xce_launch_t") { hosts @0: List(HostLaunch) $C.mapname("hosts") $C.maplistcount("nhosts"); } struct JobExecInfo $C.mapname("xce_job_exec_t"){ jobid @0: UInt64; startinfo @1: JobStartInfo $C.mapname("start_info"); launchinfo @2: JobLaunch $C.mapname("launchinfo"); } struct JobFinishInfo $C.mapname("xce_jobfinish_t") { jobid @0: UInt64; code @1: Int32 $C.mapname("exit_code"); } struct TaskPidInfo $C.mapname("xce_task_pidinfo_t") { jobid @0: UInt64 $C.mapname("jobid"); host @1: Text $C.mapname("host"); taskidx @2: Int32 $C.mapname("taskidx"); pids @3: List(UInt32) $C.mapname("pids") $C.maplistcount("npids"); pgids @4: List(UInt32) $C.mapname("pgids") $C.maplistcount("npgids"); exdata @5: Text $C.mapname("exdata"); clientidx @6: UInt32 $C.mapname("clientidx"); } struct TaskFailInfo $C.mapname("xce_task_failinfo_t") { jobid @0: UInt64 $C.mapname("jobid"); host @1: Text $C.mapname("host"); taskidx @2: Int32 $C.mapname("taskidx"); code @3: Int32 $C.mapname("code"); detail @4: Text $C.mapname("detail"); clientidx @5: Int32 $C.mapname("clientidx"); } struct TaskFinish $C.mapname("xce_task_finish_t") { jobid @0: UInt64 $C.mapname("jobid"); host @1: Text $C.mapname("host"); taskidx @2: UInt32 $C.mapname("taskidx"); exitcode @3: Int32 $C.mapname("exit_code"); clientidx @4: Int32 $C.mapname("clientidx"); } struct NotifyData $C.mapname("xce_notify_data_t"){ data :union $C.mapuniontag("event") $C.mapname("data"){ jobPidInfo @0: JobPIDInfo $C.mapname("pidinfo"); jobFailInfo @1: JobFail $C.mapname("failinfo"); jobDispatchFail @2: JobFail $C.mapname("dispatchfail"); jobFinishInfo @3: JobFinishInfo $C.mapname("finishinfo"); schedStartJob @4: UInt64 $C.mapname("sched_start_job"); # bug-1737: was a bare UInt64 jobid -- carried no exit_code, so # sched_handle_notify() (scheduler/src/handler.c) could not distinguish # a successful finish (DONE) from a failed one (EXIT) and unconditionally # assumed EXIT. Reuses the existing JobFinishInfo shape (jobid + code) # apiserver_job_exit_fun (apiserver.c) already sends for this identical # HOOK_DETAIL_JOB_EXIT hook, via NotifyData_data_jobFinishInfo. schedExitJob @5: JobFinishInfo $C.mapname("sched_exit_job"); schedFailJob @6: UInt64 $C.mapname("sched_fail_job"); schedUpdateJob @7: SchedJob $C.mapname("sched_update_job"); schedUpdateResvals @8: ResValList $C.mapname("sched_update_resvals"); stoppedJob @9: UInt64 $C.mapname("stopped_job"); resumedJob @10: UInt64 $C.mapname("resumed_job"); ckptinfo @11: CheckpointInfo $C.mapname("ckptinfo"); schedUpdateQueue @12: QueueConf $C.mapname("sched_update_queue"); taskPidInfo @13: TaskPidInfo $C.mapname("taskinfo"); taskFailInfo @14: TaskFailInfo $C.mapname("taskfailinfo"); taskFinish @15: TaskFinish $C.mapname("taskfinish"); postDoneJob @16: UInt64 $C.mapname("pdone_job"); schedJobRusages @17: SchedJobRusages $C.mapname("sched_job_rusages"); updateJob @18: Job $C.mapname("update_job"); } } struct CtrlPeer $C.mapname("xce_ctrlpeer_t") { name @0: Text; host @1: Text; port @2: UInt32; } struct CtrlStatus $C.mapname("xce_ctrlstatus_t") { clusterName @0: Text; primaryName @1: Text; responderName @2: Text; responderRole @3: UInt8; term @4: UInt64; configVersion @5: UInt64; configChecksum @6: Text; committedConfigVersion @7: UInt64; committedLsn @8: UInt64; appliedLsn @9: UInt64; fenced @10: Bool; peers @11: List(CtrlPeer) $C.mapname("peers.list") $C.maplistcount("peers.count"); peerCount @12: UInt32; responderTimeMs @13: UInt64 $C.mapname("responder_time_ms"); # responder's wall-clock at sample time (epoch milliseconds) } struct ClusterConf $C.mapname("xce_clusterconf_t") { name @0: Text; resources @1: List(Resource) $C.mapname("reslist.res") $C.maplistcount("reslist.count"); queues @2: List(QueueConf) $C.mapname("qlist.queues") $C.maplistcount("qlist.count"); workers @3: List(Worker) $C.mapname("wlist.workers") $C.maplistcount("wlist.count"); } struct JobSubmitData $C.mapname("job_submit_data_t") { queue @0: Text; user @1: Text; mintasks @2: UInt32; maxtasks @3: UInt32; jobname @4: Text $C.mapname("job_name"); project @5: Text $C.mapname("project_name"); ugroup @6: Text $C.mapname("user_group"); description @7: Text $C.mapname("job_description"); resreq @8: Text; startinfo @9: JobStartInfo $C.mapname("start_info"); fromHost @10: Text $C.mapname("from_host"); } struct JobSubmit $C.mapname("xce_job_submit_t") { jobid @0: UInt64; arraySize @1: UInt64 $C.mapname("array_size"); submit @2: JobSubmitData; } struct JobSignal $C.mapname("xce_signal_t") { signo @0: UInt32 $C.mapname("signo"); force @1: Int32 $C.mapname("force"); } struct CheckpointOptions $C.mapname("xce_ckpt_options_t"){ kill @0: UInt32 $C.mapname("kill"); method @1: Text $C.mapname("method"); } struct CheckpointInfo $C.mapname("xce_ckptinfo_t") { jobid @0: UInt64; ckptinfo @1: Text; } struct JobControl $C.mapname("xce_jobctrl_t") { jobid @0: UInt64 $C.mapname("jobid"); u :union $C.mapuniontag("action") $C.mapname("u") { siginfo @1: JobSignal; ckptoption @2: CheckpointOptions; } } struct QueueControl $C.mapname("xce_queuectrl_t") { action @0: UInt32 $C.mapname("action"); queue @1: Text $C.mapname("queue"); } struct HostControl $C.mapname("xce_hostctrl_t") { action @0: UInt32 $C.mapname("action"); hostname @1: Text $C.mapname("hostname"); } struct RequestReply $C.mapname("xce_reply_t"){ seq @0: UInt64; opcode @1: OPCode; status @2: Int32; data : union { nodata @3: Void; debugmsg @4: Text; getdata @5: XceData; jobid @6: UInt64; clusterconf @7: ClusterConf; replconfigreplymsg @8: ReplConfigReply; replconfigpushreplymsg @9: ReplConfigPushReply; upgradeconfigreplymsg @10: UpgradeConfigReply; ctrlhelloreplymsg @13: CtrlHelloReply; requestvotereplymsg @14: RequestVoteReply; stateReportMsg @15: StateReport $C.mapname("state_report"); # heartbeat reply: standby state walackreplymsg @16: WalAckReply $C.mapname("walackreplymsg"); # WAL apply ACK from standby ctrlstatus @17: CtrlStatus; # ctrlstatus reply: identity/health snapshot fetchfilereply @18: FetchFileReply; schedadminreplymsg @19: SchedAdminReply; probeconfigtagreplymsg @20: ProbeConfigTagReply; } redirectHost @11: Text $C.mapname("redirect_host"); redirectPort @12: UInt16 $C.mapname("redirect_port"); } struct TaskHost $C.mapname("xce_taskhost_t") { host @0: Text; ntasks @1: UInt32; exechome @2: Text; execcwd @3: Text; execuser @4: Text; cmdline @5: Text; exdata @6: Text; launchtype @7: Text $C.mapname("launch_type"); } struct RunTask $C.mapname("xce_taskrun_t") { clientidx @0: Int32 $C.mapname("clientidx"); hosts @1: List(TaskHost) $C.mapname("taskhosts") $C.maplistcount("nhosts"); jobid @2: UInt64 $C.mapname("jobid"); } struct PendReason $C.mapname("xce_pendreason_t") { jobid @0: UInt64 $C.mapname("jobid"); reasons @1: JobPendReason $C.mapname("reasons"); } struct PendReasonSet $C.mapname("xce_preason_set_t") { reason @0: List(PendReason) $C.mapname("reasons") $C.maplistcount("nreasons"); } struct ClearPReason $C.mapname("xce_clear_reason_t") { jobs @0: List(UInt64) $C.mapname("jobs") $C.maplistcount("njobs"); } struct FetchFileMsg $C.mapname("xce_fetchfile_t") { jobid @0: UInt64 $C.mapname("jobid"); filepath @1: Text $C.mapname("filepath"); offset @2: UInt64 $C.mapname("offset"); maxBytes @3: UInt32 $C.mapname("max_bytes"); } struct FetchFileReply $C.mapname("xce_fetchfile_reply_t") { error @0: Int32 $C.mapname("error"); content @1: List(UInt8) $C.mapname("content") $C.maplistcount("bytes_read"); fileSize @2: UInt64 $C.mapname("file_size"); bytesRead @3: UInt32 $C.mapname("bytes_read"); jobid @4: UInt64 $C.mapname("jobid"); } # ── Scheduler admin opcode (scheduler-core task 7.4, Req 5 AC 10) ──── # OPCode_schedAdmin request/reply pair: start/stop/query a scheduler-side # `.replay` recording session. The actual controller-side dispatch # (cluster.c's myreq_handler forwarding this opcode to the scheduler) is a # LATER task, out of this schema-only task's scope -- this struct trio is # purely the wire contract. enum SchedAdminAction { recordStart @0; recordStop @1; recordStatus @2; } struct SchedAdminRequest $C.mapname("sched_admin_req_t") { action @0: SchedAdminAction; recordFile @1: Text; # filename (not a path) for recordStart; empty = auto-generate. # See AC 10 "Record file path validation" — the scheduler # resolves this beneath a configured recording directory; # it is never used as a caller-controlled absolute path. } struct SchedAdminReply $C.mapname("sched_admin_reply_t") { ok @0: Bool; error @1: Text; # human-readable error if !ok # Fields populated for recordStatus (also returned by recordStart/recordStop # for confirmation — caller can verify state after each action): recording @2: Bool; # true between start and stop file @3: Text; # record file path (auto-generated name returned by start) eventCount @4: UInt64; # events written since recording started startedAt @5: UInt64; # logical time recording started (nanoseconds since epoch) } struct Request $C.mapname("xce_request_t"){ opcode @0: OPCode; seq @1: UInt64; from @2: PeerInfo; body : union { nomsg @3: Void; replymsg @4: RequestReply; hellomsg @5: Text; joinmsg @6: JoinData; configpushmsg @7: ConfigData; pushmsg @8: XceData; getmsg @9: Get; datamsg @10: Dataset; dispatchmsg @11: JobDispatch; jobstartmsg @12: JobExecInfo; notifymsg @13: NotifyData; jobsubmsg @14: JobSubmit; updatemsg @15: Update; jobctrlmsg @16: JobControl; queuectrlmsg @17: QueueControl; runtaskmsg @18: RunTask; preasonmsg @19: PendReasonSet; clrpreasonmsg @20: ClearPReason; hostctrlmsg @21: HostControl; replconfigrequestmsg @22: ReplConfigRequest; replconfigpushrequestmsg @23: ReplConfigPushRequest; upgradeconfigrequestmsg @24: UpgradeConfigRequest; heartbeatmsg @25: HeartbeatMsg; ctrlhellomsg @26: CtrlHelloRequest; requestvotemsg @27: RequestVoteRequest; walcatchupentrymsg @28: WalCatchupEntry; snapshottransfermsg @29: SnapshotTransfer; fetchfilemsg @30: FetchFileMsg; schedadminmsg @31: SchedAdminRequest; probeconfigtagrequestmsg @32: ProbeConfigTagRequest; } } struct HostConfig $C.mapname("xce_hostconfig_t") { name @0: Text $C.mapname("name"); model @1: Text $C.mapname("hostmodel"); type @2: Text $C.mapname("hosttype"); mxj @3: UInt32 $C.mapname("mxj"); resources @4: List(ConfigVar) $C.mapname("resources") $C.maplistcount("nresources"); thresholds @5: List(Threshold) $C.mapname("thresholds") $C.maplistcount("nthresholds"); dispatchwindows @6: TimeWindow $C.mapname("dispatchwindow"); } struct ScalerConfig $C.mapname("scaler_config_t") { name @0: Text $C.mapname("name"); type @1: Text $C.mapname("prov_type"); confPath @2: Text $C.mapname("conf_path"); scriptPath @3: Text $C.mapname("script_path"); scriptOption @4: Text $C.mapname("script_option"); } struct LimitConfig $C.mapname("xce_limit_config_t") { name @0: Text $C.mapname("name"); users @1: GroupConfig $C.mapname("users"); queues @2: GroupConfig $C.mapname("queues"); projects @3: GroupConfig $C.mapname("projects"); hosts @4: GroupConfig $C.mapname("hosts"); val @5: LimitValue $C.mapname("val"); perUser @6: Bool $C.mapname("per_user"); perQueue @7: Bool $C.mapname("per_queue"); perProject @8: Bool $C.mapname("per_project"); perHost @9: Bool $C.mapname("per_host"); } struct ClusterConfig $C.mapname("cluster_config_t") { clusterName @0: Text $C.mapname("clustername"); heartbeatInterval @1: UInt32 $C.mapname("heartbeat_interval"); inactiveHeartbeatCount @2: UInt32 $C.mapname("inactive_heartbeat_count"); fetchJobInterval @3: UInt32 $C.mapname("fetch_job_interval"); netioTimeout @4: UInt32 $C.mapname("netio_timeout"); resources @5: List(Resource) $C.mapname("resources") $C.maplistcount("nres"); queues @6: List(QueueConf) $C.mapname("queues") $C.maplistcount("nqueues"); ugroups @7: List(GroupConfig) $C.mapname("ugroups") $C.maplistcount("nugroups"); hgroups @8: List(GroupConfig) $C.mapname("hgroups") $C.maplistcount("nhgroups"); hostConfigs @9: List(HostConfig) $C.mapname("hconfigs") $C.maplistcount("nhconfigs"); hproviders @10: List(ScalerConfig) $C.mapname("hproviders") $C.maplistcount("nhproviders"); limits @11: List(LimitConfig) $C.mapname("limits") $C.maplistcount("nlimits"); configGeneration @12: UInt64 $C.mapname("config_generation"); } # ── Persistence configuration (Part 2.6) ──────────────────────────── # $C.mapname uses capnp_persist_cfg_t (not persist_config_t) to avoid # collision with the plain-C struct already defined in persist.h. struct PersistConfig $C.mapname("capnp_persist_cfg_t") { walSyncMs @0 :UInt32 = 10; snapIntervalS @1 :UInt32 = 60; snapWalSizeMb @2 :UInt32 = 64; finishedRetainS @3 :UInt32 = 3600; workerRetainS @4 :UInt32 = 86400; syncTimeoutMs @5 :UInt32 = 50; snapKeepN @6 :UInt32 = 2; transientJobTimeoutS @7 :UInt32 = 300; } # ── Snapshot header (Part 2.5) ─────────────────────────────────────── struct SnapshotHeader $C.mapname("capnp_snap_hdr_t") { lsn @0 :UInt64; term @1 :UInt64; timestamp @2 :UInt64; version @3 :UInt32 = 1; nJobs @4 :UInt32; nQueues @5 :UInt32; nWorkers @6 :UInt32; checksum @7 :UInt32; headerChecksum @8 :UInt32; } # ── Upgrade snapshot admin-state frames (config-upgrade-snapshot task 1, # Req 1.9/5.7) ────────────────────────────────────────────────────────── # Independently-extensible Cap'n Proto frames for the on-disk checkpoint's # host/queue admin-state sections (SNAPSHOT_FORMAT_VERSION 2, persist.c). # New admin-state fields (e.g. a lock reason or tag) get new ordinals under # capnp's own schema-evolution rules -- no future SNAPSHOT_FORMAT_VERSION # bump needed per field addition. Deliberately NOT mapname'd onto # Worker/QueueConf (design.md Design Invariant 5): those live runtime # structs bundle static attrs and dynamic state together, which a snapshot # frame must never carry (Requirement 1.7 excludes dynamic fields). Each # maps instead onto its own frame-only C counterpart in typemap.h, matching # the existing capnp_snap_hdr_t/capnp_persist_cfg_t/capnp_repl_envelope_t # convention already established below for schema-only wire concepts with # no corresponding "live" C struct. struct HostAdminState $C.mapname("capnp_host_admin_state_t") { name @0: Text; status @1: UInt32; } struct QueueAdminState $C.mapname("capnp_queue_admin_state_t") { name @0: Text; status @1: UInt32; } # ── HA heartbeat ──────────────────────────────────────────────────── struct HeartbeatMsg $C.mapname("heartbeat_msg_t") { senderPriority @0: UInt32 $C.mapname("sender_priority"); term @1: UInt64 $C.mapname("term"); committedLsn @2: UInt64 $C.mapname("committed_lsn"); committedConfigTag @3: UInt64 $C.mapname("committed_config_tag"); } # ── Config replication message structs ────────────────────────────── struct ReplConfigRequest $C.mapname("repl_config_req_t") { versionTag @0: UInt64 $C.mapname("version_tag"); } struct ReplConfigReply $C.mapname("repl_config_reply_t") { upToDate @0: Bool $C.mapname("up_to_date"); versionTag @1: UInt64 $C.mapname("version_tag"); sha256 @2: Data $C.mapname("sha256"); bytes @3: Data $C.mapname("bytes"); } struct ReplConfigPushRequest $C.mapname("repl_config_push_req_t") { versionTag @0: UInt64 $C.mapname("version_tag"); sha256 @1: Data $C.mapname("sha256"); bytes @2: Data $C.mapname("bytes"); } struct ReplConfigPushReply $C.mapname("repl_config_push_reply_t") { accepted @0: Bool $C.mapname("accepted"); versionTag @1: UInt64 $C.mapname("version_tag"); # raft-adapter-conformance Requirement 7.4: the replying member's current # term, so a higher term can be discovered through this reply channel the # same way it already is through heartbeat and WAL-NACK replies. Additive # field (ordinal 2) — an old-code standby's reply decodes as term=0 on a # new-code primary, which can never look like a higher term (terms start # at 1), so no spurious adoption occurs under mixed-version rollout. term @2: UInt64 $C.mapname("term"); } struct UpgradeConfigRequest $C.mapname("upgrade_config_req_t") { sha256 @0: Data $C.mapname("sha256"); bytes @1: Data $C.mapname("bytes"); } struct UpgradeConfigReply $C.mapname("upgrade_config_reply_t") { versionTag @0: UInt64 $C.mapname("version_tag"); error @1: Text $C.mapname("error"); ackedCount @2: UInt32 $C.mapname("acked_count"); } # ── WAL replication envelope (Part 6 — NOT the on-disk format) ─────── # Named ReplEnvelope to avoid collision with the on-disk wal_entry_hdr_t. struct ReplEnvelope $C.mapname("capnp_repl_envelope_t") { term @0 :UInt64; lsn @1 :UInt64; entryType @2 :UInt32; payload @3 :Data; crc @4 :UInt32; prevCrc @5 :UInt32; } # ── HA election and replication message structs (Task 1.1) ─────────── # Heartbeat reply state report — standby piggybacks its replication state # onto every OPCode_nop reply so the primary can drive convergence decisions. struct StateReport $C.mapname("state_report_t") { appliedLsn @0 :UInt64 $C.mapname("applied_lsn"); # standby's last applied WAL LSN configTag @1 :UInt64 $C.mapname("config_tag"); # standby's current config version tag term @2 :UInt64 $C.mapname("term"); # standby's current raft term (bug-1421 term-discovery) } struct RequestVoteRequest $C.mapname("request_vote_req_t") { term @0 :UInt64 $C.mapname("term"); candidateId @1 :Text $C.mapname("candidate_id"); configTag @2 :UInt64 $C.mapname("config_tag"); lastLogLsn @3 :UInt64 $C.mapname("last_log_lsn"); # Raft (lastLogTerm, lastLogIndex) freshness tuple (ha-term-safety task 1.2, # Req 2.3/2.4/2.6). Distinct from the XCE freshness fields above: configTag / # lastLogLsn drive the application-policy freshness gate (on_vote_eligible), # while these two feed the library's logOk gate (raft.tla HandleRequestVoteRequest # logOk). lastLogIndex == candidate WAL log_len; lastLogTerm == term of that entry. lastLogTerm @4 :UInt64 $C.mapname("last_log_term"); lastLogIndex @5 :UInt64 $C.mapname("last_log_index"); } struct RequestVoteReply $C.mapname("request_vote_reply_t") { term @0 :UInt64 $C.mapname("term"); voteGranted @1 :Bool $C.mapname("vote_granted"); peerConfigTag @2 :UInt64 $C.mapname("peer_config_tag"); peerLastLogLsn @3 :UInt64 $C.mapname("peer_last_log_lsn"); } struct ProbeConfigTagRequest $C.mapname("probe_config_tag_req_t") { name @0 :Text $C.mapname("name"); } struct ProbeConfigTagReply $C.mapname("probe_config_tag_reply_t") { configTag @0 :UInt64 $C.mapname("config_tag"); } struct WalAckRequest $C.mapname("wal_ack_req_t") { term @0 :UInt64 $C.mapname("term"); lsn @1 :UInt64 $C.mapname("lsn"); } struct WalAckReply $C.mapname("wal_ack_reply_t") { term @0 :UInt64 $C.mapname("term"); lsn @1 :UInt64 $C.mapname("lsn"); success @2 :Bool $C.mapname("success"); } struct UpgradeConfigAck $C.mapname("upgrade_config_ack_t") { term @0 :UInt64 $C.mapname("term"); ackedTag @1 :UInt64 $C.mapname("acked_tag"); success @2 :Bool $C.mapname("success"); } # ── Snapshot chunk transfer (Req 4.1–4.2) ─────────────────────────── # Used for OPCode_snapshotTransfer: full state snapshot transferred as # a sequence of fixed-size chunks (64 KB max each). Restart-on-reconnect # semantics — no resumption state needed. Fields @0 and @1 are preserved # for wire-protocol continuity; @2–@5 carry the chunk envelope. struct SnapshotTransfer $C.mapname("snapshot_transfer_t") { snapshotLsn @0 :UInt64 $C.mapname("snapshot_lsn"); # checkpoint LSN this snapshot corresponds to payload @1 :Data $C.mapname("payload"); # chunk payload (64 KB max) chunkIndex @2 :UInt32 $C.mapname("chunk_index"); # 0-based chunk number chunkOffset @3 :UInt64 $C.mapname("chunk_offset"); # byte offset in the full snapshot totalSize @4 :UInt64 $C.mapname("total_size"); # total snapshot size in bytes isLast @5 :Bool $C.mapname("is_last"); # true for the final chunk # raft-ha-core-safety task 3.2 (Req 3.1/5.1): the leader's term for the # checkpoint LSN (persist_entry_term(p, snapshotLsn) at send time). The # receiver reconciles this into its own per-LSN term index after install so # the snapshot boundary establishes a verifiable (term, lsn) pair and any # divergent stale-term pre-snapshot entry at this LSN is authoritatively # overwritten. Append-only field number (@6) — no collision with @0-@5 # above. 0 = unset (legacy sender that never stamped this field) — the # receiver treats 0 as WAL_LEGACY_TERM, the same convention persist's # existing legacy-0 term mapping already uses. checkpointTerm @6 :UInt64 $C.mapname("checkpoint_term"); } struct WalCatchupEntry $C.mapname("wal_catchup_entry_t") { lsn @0 :UInt64 $C.mapname("lsn"); entryType @1 :UInt8 $C.mapname("entry_type"); # payload is List(UInt8), NOT Data (bug-1505 fix, same class as bug-1504 on # SnapshotData.internalState -- see that struct's own comment for the full # root cause: this codebase's capnpc-c backend silently no-ops every Data # field's encoding in generated encode_XXX() helpers without zero- # initializing the caller's local struct first, leaving a genuinely # uninitialized capn_ptr written onto the wire -- a real, reproducible # SIGSEGV in write_WalCatchupEntry, hit by # test_wal_catchup_append_term_fields_round_trip even though that test # never touches payload content at all (src.payload stays NULL -- the # crash is from the OUTPUT struct's uninitialized field, independent of # the input). List(UInt8) gets a real generated encode/decode from this # same backend (proven precedent: FetchFileReply.content/bytesRead), so # no hand-written accessor is needed. Reuses wirePayloadLen (@9, below) # as its maplistcount companion -- already exactly "this sub-entry's # payload byte count" for the bug-847 slicing use below, same value. # NOTE: production (xce_wal_batch_encode_raw/wal_entry_to_wire, request.c) # never goes through this generic encode_WalCatchupEntry() path at all -- # it memset()s its own struct and calls write_WalCatchupEntry() directly # with every field hand-set (payload deliberately left at its post-memset # zero value, per bug-847's own comment below) -- so this fix has zero # production behavior impact, it only stops the generic codec (used by # this one test) from crashing. payload @2 :List(UInt8) $C.mapname("payload") $C.maplistcount("wire_payload_len"); entries @3 :List(WalCatchupEntry) $C.mapname("entries") $C.maplistcount("n_entries"); # Raft AppendEntries term fields (ha-term-safety task 1.1, Req 1.3/1.4). # This envelope is the replication-carrying message (WAL catch-up); the four # log callbacks (tasks 2.x) need prevLog{Index,Term} for the AppendEntries # logOk / conflict-truncation decision and `term` to stamp the entry. lsn == # this entry's 1-based log index; prevLogIndex == lsn-1; prevLogTerm == term of # the entry at prevLogIndex (0 if absent); term == this entry's term stamp. term @4 :UInt64 $C.mapname("term"); prevLogIndex @5 :UInt64 $C.mapname("prev_log_index"); prevLogTerm @6 :UInt64 $C.mapname("prev_log_term"); # ha-term-safety task 3.2 (Req 2.5/4.1): the leader's commit index, carried so # the standby intake passes it into raft_on_append_entries_request. Append-only # field number (@7) — no collision with the @4/@5/@6 term fields above. leaderCommit @7 :UInt64 $C.mapname("leader_commit"); # bug-1067: the streaming leader's CURRENT term — the Raft AppendEntries header # term (`term` arg of raft_on_append_entries_request), DISTINCT from `term` @4 # which is the entry's STORED term. For a live append the two are equal; for # catch-up of a HISTORICAL entry written by a prior leader, the entry's stored # term is LOWER than the current leader's term. Using the stored term as the AE # header term made the library stale-reject every historical entry # (ae_reject_stale), wedging cross-term catch-up. The standby intake uses this # as the AE header term (UpdateTerm/stale-check) and keeps `term` @4 as the # per-entry term (logOk / on_persist_entry). 0 = unset (legacy sender) → the # intake falls back to the entry's stored term (the prior, live-only behaviour). leaderCurrentTerm @8 :UInt64 $C.mapname("leader_current_term"); # bug-847: a PER-ELEMENT `payload` (@2) Data blob loses its pointer when a # multi-entry catch-up batch spans more than one capnp segment (the # underlying capnp-c library's far-pointer handling for a List(UInt8) whose # segment differs from its referencing struct's segment) -- the receiver # decodes raw_payload_len=0 for entries at/after the boundary even though # the sender held the real bytes, silently persisting empty WAL entries. # A SINGLE top-level Data blob does not exhibit this loss (only scattered # per-element Data does), so the batch encoder (xce_wal_batch_encode_raw) # now carries every sub-entry's payload concatenated in ONE top-level # batchPayloads blob, sliced back apart on decode using each sub-entry's # own wirePayloadLen (a plain scalar -- already immune, decoded like # lsn/entryType/term by the generic generated decode, no manual extraction # needed). Populated for every xce_wal_batch_encode_raw send regardless of # n_entries (0/1/N); sub-entries no longer carry their own @2 payload on # this path (kept for any other/legacy sender; decode still handles it # defensively as a fallback when batchPayloads is absent). # # NOTE (bug-1505 fix, same as `payload` above): this note's own "cross- # segment loss" concern is about scattered PER-ELEMENT Data (or # List(UInt8) -- identical wire representation either way in this # codegen; the Data vs List(UInt8) schema keyword only changes whether # THIS backend's generic encode_XXX() composite helper populates the # field automatically, not the underlying wire type). batchPayloads # itself is the single TOP-LEVEL blob this fix already routes around that # exact problem with -- so switching ITS OWN keyword to List(UInt8) does # not reintroduce bug-847's issue; it only stops the generic codec's # SnapshotData.internalState-class no-op/uninitialized-capn_ptr crash # (bug-1504) for this field too. batchPayloadsLen (@11, below) is its # maplistcount companion, distinct from raw_batch_payloads_len # (typemap.h) -- that field is populated by request.c's OWN hand-rolled # xce_decode() extraction (a completely separate, already-correct code # path this fix does not touch), not by this generic decode. wirePayloadLen @9 :UInt32 $C.mapname("wire_payload_len"); batchPayloads @10 :List(UInt8) $C.mapname("batch_payloads") $C.maplistcount("batch_payloads_wire_len"); batchPayloadsLen @11 :UInt32 $C.mapname("batch_payloads_wire_len"); } struct CtrlHelloRequest $C.mapname("ctrl_hello_req_t") { name @0 :Text $C.mapname("name"); key @1 :Data $C.mapname("key"); appliedLsn @2 :UInt64 $C.mapname("applied_lsn"); configTag @3 :UInt64 $C.mapname("config_tag"); } struct CtrlHelloReply $C.mapname("ctrl_hello_reply_t") { committedLsn @0 :UInt64 $C.mapname("committed_lsn"); needsSnapshot @1 :Bool $C.mapname("needs_snapshot"); configTag @2 :UInt64 $C.mapname("config_tag"); } # ── Replay stream envelope schema (scheduler-core Group 7 task 7.1, # Req 5 AC 20) ──────────────────────────────────────────────────────────── # `.replay` file format: a single stream of consecutive capnp messages -- # ReplayHeader (msg 1) -> SnapshotEntry (msg 2, MUST be the first data # message) -> ReplayEvent/ReplaySession cycles -> periodic SnapshotEntry # checkpoints -> final ReplayEvent{endSession}. See requirements.md Req 5 # AC 20 for the full framing prose; this schema captures only the message # *shapes* -- the file-level reader/writer that produces/consumes the # stream is task 9+, entirely out of Group 7's (schema-only) scope. # # Struct-ownership split across Group 7 tasks, so a fresh implementer on # 7.2/7.3/7.4 knows exactly what to add and where. Every later addition is a # NEW ordinal on a struct THIS task already created -- nothing below is ever # renumbered or restructured by a later task: # - task 7.1 (this task): ReplayCluster, ReplayHeader, SnapshotData # (zero-field placeholder), SnapshotEntry, ReplayEvent (9 members, all # reusing pre-existing structs), SessionSummary (fully real), # ReplayDecision (ts-only stub), ReplaySession. # - 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 (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; controllers @1: List(Text) $C.maplistcount("n_controllers"); # ["host1:9991", "host2:9992", "host3:9993"] recordingHost @2: Text $C.mapname("recording_host"); # controller hostname that made the recording } struct ReplayHeader $C.mapname("replay_header_t") { recorderVersion @0: Text $C.mapname("recorder_version"); # scheduler binary version that wrote this file configHash @1: Data $C.mapname("config_hash"); # sha256 of xce_conf4sched_t cluster @2: ReplayCluster; recordingStartTs @3: UInt64 $C.mapname("recording_start_ts"); # logical time recording started (ns) recordingStartWall @4: Text $C.mapname("recording_start_wall"); # wall-clock time recording started (ISO 8601) schedPolicy @5: Text $C.mapname("sched_policy"); # active picker plugin name (e.g. "builtin_fcfs") } # 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). # # internalState is List(UInt8), NOT Data (task 12.1 review finding, fixed): # every OTHER Data field in this schema (e.g. ReplayHeader.configHash) has # its generated encode_/decode_ codec silently no-op'd by this codebase's # capnpc-c setup -- the generated encode_SnapshotData_ptr()'s local `struct # SnapshotData d;` is never zero-initialized, so a no-op'd Data field left # capn_setp() writing an UNINITIALIZED stack capn_ptr onto the wire (real, # reproducible SIGSEGV in write_SnapshotData, deterministic per call site's # stack layout -- configHash happened to get lucky garbage, internalState # did not). List(UInt8) sidesteps the whole no-op class: this same capnpc-c # backend DOES correctly auto-generate real encode/decode for List(UInt8) # (proven precedent: FetchFileReply.content/bytesRead above -- a genuine # capn_new_list8()/capn_set8() loop, not a comment placeholder), so no # hand-written low-level accessor code is needed here at all. Same # maplistcount convention as content/bytesRead: internalStateLen (@11, # below) carries the byte count as its own real wire scalar. 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: List(UInt8) $C.mapname("internal_state") $C.maplistcount("internal_state_len"); internalVersion @10: UInt32 $C.mapname("internal_version"); internalStateLen @11: UInt32 $C.mapname("internal_state_len"); } struct SnapshotEntry $C.mapname("replay_snapshot_entry_t") { ts @0: UInt64; # logical time of snapshot data @1: SnapshotData; # full core state } # task 7.2 (Req 5 AC 20): the two genuinely new event payload types -- # every other ReplayEvent.body variant reuses a pre-existing wire struct # (see ReplayEvent's comment block below). jobStateChange/jobUpdate are # added as ReplayEvent.body union members at ordinals 10/11 below. struct JobStateChangeEvent $C.mapname("job_state_change_event_t") { jobid @0: UInt64; oldState @1: UInt32 $C.mapname("old_state"); newState @2: UInt32 $C.mapname("new_state"); exitCode @3: Int32 $C.mapname("exit_code"); } struct JobUpdateEvent $C.mapname("job_update_event_t") { jobid @0: UInt64; # bitmask: JOB_F_PRIORITY, JOB_F_QUEUE, JOB_F_RESREQ -- no JOB_F_* constants # exist anywhere in the codebase yet (confirmed by task 5.1's grep); concrete # bit assignments are task 9+ event-translation/recording code's job, not # this schema-only task's. changedFields @1: UInt32 $C.mapname("changed_fields"); job @2: Job; # full updated record; reuses existing Job struct } # ReplayEvent.body reuses 7 pre-existing wire structs verbatim (Conf4Sched, # SchedJobData, WorkerData, ResourceData, ResValList x2, QueueConf, # SchedJobRusages -- verified present in controller.capnp, research.md) plus # a Void end-of-recording marker, plus the two new JobStateChangeEvent/ # JobUpdateEvent structs (task 7.2) above. struct ReplayEvent $C.mapname("replay_event_t") { ts @0: UInt64; # logical time body :union $C.mapuniontag("body_which") { configPush @1: Conf4Sched $C.mapname("config_push"); jobsPush @2: SchedJobData $C.mapname("jobs_push"); workersPush @3: WorkerData $C.mapname("workers_push"); resourcesPush @4: ResourceData $C.mapname("resources_push"); resvalsFull @5: ResValList $C.mapname("resvals_full"); resvalsDelta @6: ResValList $C.mapname("resvals_delta"); queueUpdate @7: QueueConf $C.mapname("queue_update"); jobRusage @8: SchedJobRusages $C.mapname("job_rusage"); endSession @9: Void; # recording stop marker jobStateChange @10: JobStateChangeEvent $C.mapname("job_state_change"); jobUpdate @11: JobUpdateEvent $C.mapname("job_update"); } } struct SessionSummary $C.mapname("session_summary_t") { durationNs @0: UInt64 $C.mapname("duration_ns"); nDecisions @1: UInt32 $C.mapname("n_decisions"); nDispatched @2: UInt32 $C.mapname("n_dispatched"); nPended @3: UInt32 $C.mapname("n_pended"); } # 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") { beginRound @0: UInt64 $C.mapname("begin_round"); # logical time (ts value) decisions @1: List(ReplayDecision) $C.maplistcount("n_decisions"); summary @2: SessionSummary; # optional diagnostic metadata }