feat(raft-apply-decoupling): convert job submit to continuation-based commit (task 3.2)
Replace handle_jobsubmit()'s blocking commit call (task 3.1) with a real async registration against commit_submit_async(). On FAILED/timeout, replies failure immediately with no job constructed. On COMMITTED, hands off to a new pending-apply registration (persist_register_pending_apply) for the ordered apply pump (task 6.1, not yet built) to consume -- does not apply or reply directly, per design. Deviates from design.md's stated integration site: appends via the same ha_primary_log_append/persist_wal_async primitive the RETAIN-fact path already uses and calls commit_submit_async directly on p->repl.ce, bypassing repl_wait_majority_ack/repl_submit_majority_commit in repl.c. Verified safe -- majority-ack detection is driven entirely by (ce, lsn) events inside the commit engine, agnostic to which wrapper registered the continuation -- but narrows the reusable primitive design.md intended; documented in tasks.md. Deletes (not preserves-dead) task 3.1's bug-1408 notification fix -- apply_committed_entry()'s WAL_JOB_SUBMIT case (task 2.2) already fires the notification per-entry, immune to the batch-suppression class bug-1408 fixed. IMPORTANT: no live path applies a successfully-committed job submission until task 6 lands (only restart replay does) -- every successful submission commits durably but gets zero reply until then. This commit range must not be soak-tested or deployed against live traffic before task 6 completes.
This commit is contained in:
parent
6f1fa97fab
commit
df4d2fa0ed
5 changed files with 689 additions and 299 deletions
|
|
@ -834,13 +834,23 @@ static void test_worker_join_no_hostconfig_error_reply_immediate(void)
|
|||
printf("test_worker_join_no_hostconfig_error_reply_immediate: PASS\n");
|
||||
}
|
||||
|
||||
/* ── handle_jobsubmit (raft-apply-decoupling task 3.1: apply-after-commit) ──
|
||||
/* ── handle_jobsubmit (raft-apply-decoupling task 3.1/3.2) ───────────────
|
||||
*
|
||||
* Req 2.1/2.3: a job submission whose commit fails or times out must leave
|
||||
* NO job object in c->jobmgr — construction is deferred until after the
|
||||
* commit barrier succeeds. Req 2.2: a successful submission still replies
|
||||
* synchronously with the jobid, and by the time that reply is observable the
|
||||
* job is already visible in c->jobmgr. */
|
||||
* task 3.1 (Req 2.1/2.3): a job submission whose commit fails or times out
|
||||
* must leave NO job object in c->jobmgr — construction is deferred until
|
||||
* after the commit barrier succeeds.
|
||||
*
|
||||
* task 3.2 (Req 1.1, 1.4, 2.1, 2.3): the synchronous commit call from 3.1 is
|
||||
* replaced with a registration against the commit engine's continuation
|
||||
* mechanism (commit_submit_async, via persist_submit_job_payloads_async).
|
||||
* handle_jobsubmit now returns BEFORE any commit outcome is known. A
|
||||
* failed/timed-out outcome still replies failure directly, with no job ever
|
||||
* constructed (same observable as 3.1). A COMMITTED outcome is handed off to
|
||||
* the not-yet-built ordered apply pump (task 6.1/6.2) via
|
||||
* persist_register_pending_apply() instead of applying or replying directly
|
||||
* — so a successful submission has NO independently observable behavior
|
||||
* (no reply, no job in c->jobmgr) until task 6 exists; the test below
|
||||
* asserts only that the hand-off itself was registered for the correct lsn. */
|
||||
|
||||
static job_startinfo_t g_jobsub_startinfo;
|
||||
static job_submit_data_t g_jobsub_data;
|
||||
|
|
@ -902,121 +912,24 @@ static void test_jobsubmit_withheld_acks_no_phantom_job(void)
|
|||
printf("test_jobsubmit_withheld_acks_no_phantom_job: PASS\n");
|
||||
}
|
||||
|
||||
static void test_jobsubmit_success_reply_and_job_visible_after_commit(void)
|
||||
/* raft-apply-decoupling task 3.2: supersedes the pre-3.2
|
||||
* test_jobsubmit_success_reply_and_job_visible_after_commit (which drove
|
||||
* handle_jobsubmit through cbrp_drive_acked, a helper that asserts the
|
||||
* handler blocks until its reply is observable — no longer true here by
|
||||
* this task's own explicit contract). This test asserts ONLY what task 3.2
|
||||
* itself makes observable on the committed-outcome path: the calling
|
||||
* thread returns before the commit resolves, and the continuation hands
|
||||
* the entry off to persist's pending-apply registry keyed by the correct
|
||||
* lsn — NOT a reply, NOT a constructed job (task 6 owns both; neither
|
||||
* exists yet). */
|
||||
static void test_jobsubmit_returns_before_commit_and_registers_pending_apply(void)
|
||||
{
|
||||
cbrp_setup(1500);
|
||||
uint64_t lsn_before, watermark, t0, join_elapsed_ms, t1;
|
||||
cbrp_reply_obs_t obs = { 0 };
|
||||
bool has_pending, job_exists;
|
||||
|
||||
cbrp_setup(2000); /* generous timeout: must never be needed */
|
||||
cbrp_arrange_jobsubmit(9101, 1);
|
||||
cbrp_drive_acked(handle_jobsubmit, &g_jobsub_req);
|
||||
TEST_ASSERT_TRUE_MESSAGE(cbrp_job_exists(9101),
|
||||
"a successful job submission must construct the job object once "
|
||||
"its commit succeeds, and still reply synchronously with the "
|
||||
"jobid (raft-apply-decoupling task 3.1, Req 2.2)");
|
||||
cbrp_teardown();
|
||||
printf("test_jobsubmit_success_reply_and_job_visible_after_commit: PASS\n");
|
||||
}
|
||||
|
||||
/* Captures every jobid delivered via the AFTER_EVENT/HOOK_DETAIL_JOB_NEW
|
||||
* notification hook (job.c's post-commit notify list). Needed by the
|
||||
* array-partial-construction-failure remediation test below to observe
|
||||
* that a durably committed AND successfully constructed sibling is still
|
||||
* notified even when another array element in the same submission fails to
|
||||
* construct (raft-apply-decoupling task 3.1 remediation). xce_run_hooks
|
||||
* also fires this same hook TYPE for HOOK_BEFORE_EVENT with a different
|
||||
* vararg shape (xce_job_submit_t*, not xlist_t*) — filtered out below
|
||||
* before touching ap. */
|
||||
#define CBRP_MAX_JOBNEW_NOTIFY 8
|
||||
static int g_jobnew_notify_count;
|
||||
static uint64_t g_jobnew_notify_jobids[CBRP_MAX_JOBNEW_NOTIFY];
|
||||
|
||||
static void cbrp_jobnew_capture_reset(void)
|
||||
{
|
||||
g_jobnew_notify_count = 0;
|
||||
memset(g_jobnew_notify_jobids, 0, sizeof(g_jobnew_notify_jobids));
|
||||
}
|
||||
|
||||
static int cbrp_jobnew_capture_fn(int type, int situation, int detail,
|
||||
void *data, va_list ap)
|
||||
{
|
||||
(void)type; (void)data;
|
||||
|
||||
if (detail != HOOK_DETAIL_JOB_NEW || situation != HOOK_AFTER_EVENT)
|
||||
return 0;
|
||||
|
||||
{
|
||||
xlist_t *jlist;
|
||||
int i;
|
||||
|
||||
(void)va_arg(ap, cluster_t *);
|
||||
jlist = va_arg(ap, xlist_t *);
|
||||
|
||||
if (jlist == NULL)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < jlist->count
|
||||
&& g_jobnew_notify_count < CBRP_MAX_JOBNEW_NOTIFY; i++) {
|
||||
job_t *j = (job_t *)xlist_get(jlist, i);
|
||||
if (j != NULL)
|
||||
g_jobnew_notify_jobids[g_jobnew_notify_count++] = j->job.jobid;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool cbrp_jobnew_notified(uint64_t jobid)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < g_jobnew_notify_count; i++) {
|
||||
if (g_jobnew_notify_jobids[i] == jobid)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Array-partial-construction-failure remediation (round-2 fix, task 3.1):
|
||||
* when ONE array element's post-commit create_job() fails (a jobid
|
||||
* collision that slipped past jobid_range_exists()'s pre-check, or any
|
||||
* other construct-time failure), the OTHER array elements that constructed
|
||||
* successfully — and are already durably committed — must still fire the
|
||||
* AFTER_EVENT/JOB_NEW notification hook. Both scheduler and apiserver are
|
||||
* purely event-driven off this hook with no periodic reconciliation, so a
|
||||
* withheld notification strands an otherwise-healthy, already-durable job
|
||||
* until the next controller restart replays the WAL.
|
||||
*
|
||||
* The colliding element's failure is reproduced deterministically (no real
|
||||
* race) by exploiting the actual TOCTOU window between
|
||||
* jobid_range_exists()'s pre-check (a READ-locked snapshot taken BEFORE any
|
||||
* WAL work) and makeJob_'s own key_find (a WRITE-locked check made fresh at
|
||||
* construction time, AFTER the commit barrier): a stand-in job for the
|
||||
* second array element's jobid is inserted directly into c->jobmgr from the
|
||||
* test thread strictly after the pre-check has already run (its own WAL
|
||||
* entry is visible, which cannot happen until after the pre-check passes)
|
||||
* and strictly before the construction loop can run (its commit is still
|
||||
* pending on a withheld ACK). */
|
||||
static void test_jobsubmit_array_partial_construct_failure_notifies_survivors(void)
|
||||
{
|
||||
cbrp_reply_obs_t obs = { 0 };
|
||||
uint64_t lsn_before, wm0, wm1;
|
||||
job_submit_data_t collide_submit;
|
||||
bool elem0_wal_appeared, elem1_wal_appeared;
|
||||
bool elem0_exists, elem0_notified;
|
||||
|
||||
/* Every TEST_ASSERT_* below is deferred until after cbrp_teardown().
|
||||
* A failing Unity assertion longjmps out of this function (TEST_ABORT),
|
||||
* which would otherwise skip cbrp_teardown() and leak live
|
||||
* persist/commit-engine background threads into the NEXT test's setup,
|
||||
* deadlocking the whole group (see bug-1408's round-2 correction: this
|
||||
* exact failure mode was originally reproduced as a hang, not a clean
|
||||
* FAIL, under the standard --run test_commit_barrier_reply invocation).
|
||||
* Capture every observable into a local first, teardown unconditionally,
|
||||
* then assert on the captured locals. */
|
||||
|
||||
cbrp_setup(2000);
|
||||
cbrp_jobnew_capture_reset();
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_add_hook(&g_hc.hooksys, XCE_CONTROLLER_HOOK_JOB,
|
||||
cbrp_jobnew_capture_fn, NULL));
|
||||
cbrp_arrange_jobsubmit(9200, 2); /* 9200 survives, 9201 collides */
|
||||
|
||||
pthread_mutex_lock(&g_hc.persist.lock);
|
||||
lsn_before = g_hc.persist.current_lsn;
|
||||
|
|
@ -1028,70 +941,146 @@ static void test_jobsubmit_array_partial_construct_failure_notifies_survivors(vo
|
|||
TEST_ASSERT_EQUAL_INT(0, pthread_create(&g_op_tid, NULL,
|
||||
cbrp_op_thread_fn, &g_op_arg));
|
||||
|
||||
/* Element 0's (jobid 9200) WAL entry appears first; ACK it so the
|
||||
* sequential payload loop advances to element 1. The settling sleep
|
||||
* before each ACK mirrors cbrp_drive_acked's proven pattern — it gives
|
||||
* the handler thread time to actually register its commit wait for the
|
||||
* just-appended LSN before the ACK is delivered. */
|
||||
wm0 = cbrp_poll_append(&g_hc.persist, lsn_before, 2000);
|
||||
elem0_wal_appeared = (wm0 > 0);
|
||||
cbrp_sleep_ms(150);
|
||||
if (elem0_wal_appeared)
|
||||
repl_on_ack(&g_hc.persist.repl, &g_hep_a, wm0);
|
||||
/* No ACK is delivered before this join. If handle_jobsubmit still
|
||||
* blocked on the majority-commit barrier (the pre-3.2 behavior), this
|
||||
* join could not return before the 2000ms sync_timeout elapses; a fast
|
||||
* return proves registration replaced blocking (Req 1.1, 1.4). */
|
||||
t0 = cbrp_mono_ms();
|
||||
TEST_ASSERT_EQUAL_INT(0, pthread_join(g_op_tid, NULL));
|
||||
join_elapsed_ms = cbrp_mono_ms() - t0;
|
||||
|
||||
/* Element 1's (jobid 9201) WAL entry appears once element 0 commits.
|
||||
* Its own ACK is deliberately withheld until the collision stand-in
|
||||
* below is in place. cbrp_poll_append's lsn_before is a current_lsn
|
||||
* SNAPSHOT (see its first use above, seeded from persist.current_lsn),
|
||||
* not an entry's assigned LSN — element 0 already advanced current_lsn
|
||||
* to wm0 + 1, so that (not wm0 itself) is the correct baseline to wait
|
||||
* past for element 1's append. */
|
||||
wm1 = cbrp_poll_append(&g_hc.persist, wm0 + 1, 2000);
|
||||
elem1_wal_appeared = (wm1 > wm0);
|
||||
cbrp_sleep_ms(150);
|
||||
watermark = cbrp_poll_append(&g_hc.persist, lsn_before, 2000);
|
||||
|
||||
/* Deterministic TOCTOU-outcome injection (see block comment above). */
|
||||
memset(&collide_submit, 0, sizeof(collide_submit));
|
||||
cbrp_make_job(9201, &collide_submit);
|
||||
/* Deliver the covering ACK, then poll for the hand-off artifact
|
||||
* (raft-apply-decoupling task 3.2's producer-side registration) —
|
||||
* NOT for a reply or a constructed job, neither of which this task
|
||||
* produces on the committed-outcome path. */
|
||||
repl_on_ack(&g_hc.persist.repl, &g_hep_a, watermark);
|
||||
|
||||
if (elem1_wal_appeared)
|
||||
repl_on_ack(&g_hc.persist.repl, &g_hep_a, wm1);
|
||||
has_pending = false;
|
||||
t1 = cbrp_mono_ms();
|
||||
while (cbrp_mono_ms() - t1 < 2000) {
|
||||
if (persist_has_pending_apply(&g_hc.persist, watermark)) {
|
||||
has_pending = true;
|
||||
break;
|
||||
}
|
||||
cbrp_sleep_ms(5);
|
||||
}
|
||||
|
||||
cbrp_poll_reply(&g_req_ep, 2000, &obs);
|
||||
pthread_join(g_op_tid, NULL);
|
||||
|
||||
elem0_exists = cbrp_job_exists(9200);
|
||||
elem0_notified = cbrp_jobnew_notified(9200);
|
||||
/* Give the (non-existent) rest of the pipeline a beat, then confirm
|
||||
* nothing leaked through — the task's own explicit "no independently
|
||||
* observable behavior" contract for the committed-outcome path. */
|
||||
cbrp_sleep_ms(200);
|
||||
cbrp_scan_replies(&g_req_ep, &obs);
|
||||
job_exists = cbrp_job_exists(9101);
|
||||
|
||||
cbrp_teardown();
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(elem0_wal_appeared,
|
||||
"array element 0's WAL entry must appear");
|
||||
TEST_ASSERT_TRUE_MESSAGE(elem1_wal_appeared,
|
||||
"array element 1's WAL entry must appear");
|
||||
TEST_ASSERT_TRUE_MESSAGE(join_elapsed_ms < 500,
|
||||
"handle_jobsubmit must return before any commit outcome is known "
|
||||
"(raft-apply-decoupling task 3.2, Req 1.1/1.4) — no ACK was ever "
|
||||
"delivered before this join");
|
||||
TEST_ASSERT_TRUE_MESSAGE(watermark > 0,
|
||||
"job submission must still append its WAL entry");
|
||||
TEST_ASSERT_TRUE_MESSAGE(has_pending,
|
||||
"a committed entry must be handed off to the ordered apply pump "
|
||||
"via persist_register_pending_apply, keyed by its lsn "
|
||||
"(raft-apply-decoupling task 3.2)");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, obs.n_replies,
|
||||
"no reply may be sent on the committed-outcome path until task 6's "
|
||||
"apply pump exists (raft-apply-decoupling task 3.2's own contract)");
|
||||
TEST_ASSERT_FALSE_MESSAGE(job_exists,
|
||||
"no job may be constructed on the committed-outcome path until "
|
||||
"task 6's apply pump exists (raft-apply-decoupling task 3.2)");
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(obs.n_replies > 0,
|
||||
"the requester must still receive a reply when one array element "
|
||||
"fails post-commit construction");
|
||||
TEST_ASSERT_TRUE_MESSAGE(obs.last_status < 0,
|
||||
"a partially-failed array submission must report the construction "
|
||||
"failure to the client");
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(elem0_exists,
|
||||
"the non-colliding array element must still be constructed and "
|
||||
"present in c->jobmgr despite its sibling failing to construct");
|
||||
TEST_ASSERT_TRUE_MESSAGE(elem0_notified,
|
||||
"the successfully constructed sibling must fire the AFTER_EVENT/"
|
||||
"JOB_NEW notification hook even though array element 9201 failed "
|
||||
"to construct post-commit (raft-apply-decoupling task 3.1 "
|
||||
"remediation) — otherwise it is durably committed and constructed "
|
||||
"but invisible to the purely event-driven scheduler/apiserver "
|
||||
"until the next controller restart replays the WAL");
|
||||
|
||||
printf("test_jobsubmit_array_partial_construct_failure_notifies_survivors:"
|
||||
printf("test_jobsubmit_returns_before_commit_and_registers_pending_apply:"
|
||||
" PASS\n");
|
||||
}
|
||||
|
||||
/* raft-apply-decoupling task 3.2: the failure/timeout path must ALSO prove
|
||||
* the handler returns immediately — cbrp_drive_withheld (used by
|
||||
* test_jobsubmit_withheld_acks_no_phantom_job below) only proves the
|
||||
* EVENTUAL reply/no-phantom-job observable, not that the calling thread
|
||||
* stopped blocking well before the commit timeout elapses. */
|
||||
static void test_jobsubmit_returns_before_commit_on_failure_path_too(void)
|
||||
{
|
||||
uint64_t lsn_before, watermark, t0, join_elapsed_ms;
|
||||
cbrp_reply_obs_t obs = { 0 };
|
||||
bool job_exists;
|
||||
|
||||
cbrp_setup(1000);
|
||||
cbrp_arrange_jobsubmit(9104, 1);
|
||||
|
||||
pthread_mutex_lock(&g_hc.persist.lock);
|
||||
lsn_before = g_hc.persist.current_lsn;
|
||||
pthread_mutex_unlock(&g_hc.persist.lock);
|
||||
|
||||
g_op_arg.fn = handle_jobsubmit;
|
||||
g_op_arg.req = &g_jobsub_req;
|
||||
g_op_arg.rc = -9999;
|
||||
TEST_ASSERT_EQUAL_INT(0, pthread_create(&g_op_tid, NULL,
|
||||
cbrp_op_thread_fn, &g_op_arg));
|
||||
|
||||
/* No ACK is EVER delivered in this test. The 1000ms sync_timeout_ms is
|
||||
* how long the ENTRY takes to resolve as FAILED — the HANDLER itself
|
||||
* must return immediately, well before that timeout. */
|
||||
t0 = cbrp_mono_ms();
|
||||
TEST_ASSERT_EQUAL_INT(0, pthread_join(g_op_tid, NULL));
|
||||
join_elapsed_ms = cbrp_mono_ms() - t0;
|
||||
|
||||
watermark = cbrp_poll_append(&g_hc.persist, lsn_before, 500);
|
||||
|
||||
/* Let the timeout sweep resolve the entry FAILED and confirm the
|
||||
* (eventual) failure reply + no-phantom-job observable — same
|
||||
* contract test_jobsubmit_withheld_acks_no_phantom_job already pins,
|
||||
* verified here alongside the non-blocking-return proof. */
|
||||
cbrp_poll_reply(&g_req_ep, 1000 + 1500, &obs);
|
||||
job_exists = cbrp_job_exists(9104);
|
||||
|
||||
cbrp_teardown();
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(join_elapsed_ms < 500,
|
||||
"handle_jobsubmit must return immediately after registering its "
|
||||
"continuation, without waiting for the commit timeout to elapse "
|
||||
"(raft-apply-decoupling task 3.2, Req 1.1/1.4)");
|
||||
TEST_ASSERT_TRUE_MESSAGE(watermark > 0,
|
||||
"job submission must still append its WAL entry before the "
|
||||
"handler returns");
|
||||
TEST_ASSERT_TRUE_MESSAGE(obs.n_replies > 0,
|
||||
"a forced timeout must still eventually reply to the client");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(-ETIMEDOUT, obs.last_status,
|
||||
"a timed-out commit must reply with -ETIMEDOUT, the same "
|
||||
"vocabulary the synchronous path already used (Req 2.3)");
|
||||
TEST_ASSERT_FALSE_MESSAGE(job_exists,
|
||||
"no job may ever be constructed for an entry whose commit timed "
|
||||
"out (raft-apply-decoupling task 3.2, Req 2.3)");
|
||||
|
||||
printf("test_jobsubmit_returns_before_commit_on_failure_path_too: PASS\n");
|
||||
}
|
||||
|
||||
/* REMOVED (raft-apply-decoupling task 3.2): the array-partial-construction-
|
||||
* failure remediation test (task 3.1 round-2 fix, bug-1408) that lived here
|
||||
* drove handle_jobsubmit's OLD sequential, per-element BLOCKING append loop
|
||||
* — it ACKed array element 0 and only then observed element 1's WAL entry
|
||||
* appear, exploiting that strict ordering to inject a deterministic jobid
|
||||
* collision between the two elements' commits. Task 3.2 appends every array
|
||||
* element back-to-back, non-blocking, with no per-element ACK gate between
|
||||
* them, so that ordering assumption no longer holds — the test's whole
|
||||
* premise is gone, not just its expected outcome.
|
||||
*
|
||||
* More importantly, the CODE this test exercised (handle_jobsubmit's own
|
||||
* post-commit create_job()-loop + jlist-build + xce_run_hooks(AFTER_EVENT)
|
||||
* block) no longer exists in job.c at all (see the "REMOVED" comment above
|
||||
* handle_jobsubmit's async registration block) — it is superseded, not
|
||||
* relocated, by apply_committed_entry()'s existing WAL_JOB_SUBMIT case
|
||||
* (persist.c, task 2.2), which already does restore_job() + fires
|
||||
* HOOK_DETAIL_JOB_NEW per entry, independently per array element, and is
|
||||
* therefore structurally immune to the bug-1408 class already. Coverage for
|
||||
* that invariant now belongs to persist.c's own apply-seam test suite
|
||||
* (test_apply_seam.c), not here. See this task's status report for the
|
||||
* full reasoning; git history (commit 6f1fa97) preserves the removed test
|
||||
* verbatim. */
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* Receipt-ACK verdict pins (ha-commit-barrier-rollout task 3.3, Req 2.3)
|
||||
*
|
||||
|
|
@ -2133,8 +2122,8 @@ void test_commit_barrier_reply(void)
|
|||
RUN_TEST(test_worker_join_no_hostconfig_error_reply_immediate);
|
||||
/* handle_jobsubmit (raft-apply-decoupling task 3.1, Req 2.1/2.2/2.3) */
|
||||
RUN_TEST(test_jobsubmit_withheld_acks_no_phantom_job);
|
||||
RUN_TEST(test_jobsubmit_success_reply_and_job_visible_after_commit);
|
||||
RUN_TEST(test_jobsubmit_array_partial_construct_failure_notifies_survivors);
|
||||
RUN_TEST(test_jobsubmit_returns_before_commit_and_registers_pending_apply);
|
||||
RUN_TEST(test_jobsubmit_returns_before_commit_on_failure_path_too);
|
||||
/* Receipt-ACK verdict pins (task 3.3, Req 2.3) — green on landing */
|
||||
RUN_TEST(test_jobdispatch_receipt_ack_no_wal_append);
|
||||
RUN_TEST(test_set_preason_receipt_reply_fact_retained);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue