feat(raft-apply-decoupling): split fact receipt to durable-append-only (task 4.1)

Convert on_job_start/on_job_finish/on_job_fail/on_job_postdone to
validate and durably append only -- BEFORE_EVENT validation hook
unchanged, AFTER_EVENT (post-mutation) hook no longer fires at
receipt, live state mutation removed. Adds persist_submit_job_fact()
(persist.c) since the durable append previously lived inside the
very AFTER_EVENT hook this task silences; wire format is unchanged
and identical to what apply_committed_entry() already routes.

mark_job_start/mark_job_finish (single caller each) renamed in place
to record_job_start_fact/record_job_finish_fact. mark_job_fail/
mark_job_update (shared with job_ctrl's client-command path, out of
boundary) left untouched; new durable-append-only siblings
record_job_fail_fact/record_job_update_fact added instead.

Accepts a narrow, permanent regression: on_job_postdone's finish_time
anomaly backstop has no wire-format equivalent and is dropped
(affects only a rare no-prior-exit-status path).

Two critical hazards discovered by review, documented in tasks.md,
MUST be resolved before task 6 wires apply_committed_entry() into a
live path: (1) persist_job_hook_fun still fires on HOOK_DETAIL_JOB_UPDATE
and will double-append every live-applied fact; (2) record_job_fail_fact/
record_job_update_fact precompute a final state that can be silently
clobbered by a concurrent intervening fact once apply is deferred by
the commit round-trip.
This commit is contained in:
Rongsong Shen 2026-07-04 07:00:09 +00:00
parent f357215a6d
commit 23cbfb48e1
5 changed files with 492 additions and 116 deletions

View file

@ -832,6 +832,24 @@ int persist_job_hook_fun(int type, int situation, int detail,
void* cls, va_list ap);
int persist_queue_hook_fun(int type, int situation, int detail,
void* cls, va_list ap);
/* Durable-append-only entry point for a worker-reported job-state fact
* (raft-apply-decoupling task 4.1, Req 5.1): encodes {jobid, state} into the
* shared wal_jobstate_t wire format and appends it via the same
* wal_hook_append_classified() path persist_job_hook_fun already uses for
* these WAL types (always WAL_MODE_RETAIN for WAL_JOB_{START,FINISH,FAIL,
* UPDATE} — no commit timeout, never blocks) — but bypasses the hook fan-out
* entirely, so calling this fires NO AFTER_EVENT notification. The
* mutation this fact eventually drives, and the AFTER_EVENT notification
* itself, both happen later from apply_committed_entry() once this entry
* commits to a majority (task 2.2's existing dispatch). `state` is the
* caller-computed resultant state (e.g. JOB_STAT_RUN for a START, or the
* PERR-merged value for a FAIL) — this function performs no guard checks or
* merge logic of its own, it only encodes and appends.
* Returns the local-append rc (0 on success); facts have no commit timeout
* to report, mirroring wal_hook_append_classified's own contract. */
int persist_submit_job_fact(persist_t* p, uint32_t wal_type, uint64_t jobid,
int state);
int persist_worker_hook_fun(int type, int situation, int detail,
void* cls, va_list ap);