feat(config-upgrade-snapshot): add materialization-and-hash-verification activation fence

Adds a new, independent fence (ha_state_t.upgrade_materialization_fence)
distinct from the existing config-byte serve-fence: that fence tracks
whether the locally-held config bytes hash-match the committed marker;
this one tracks whether THIS MEMBER'S OWN upgrade-snapshot
materialization (task 5's marker-triggered checkpoint) has actually
completed for the current committed marker, with the live on-disk
config bytes still hash-verifying against it right now.

upgrade_materialization_verify(c) mirrors config_activate_committed's
shape: reads the current committed marker, checks whether
persist.cfgref_present/cfgref_tag/cfgref_sha already reflect it (stamped
by every checkpoint via scan_latest_committed_config_ref -- proof this
member's own materialization ran for this exact marker), and separately
re-reads the LIVE on-disk config bytes fresh on every call (never a
cached/embedded copy) to hash-verify them too. Both must hold for the
fence to clear. Logs loud only on the 0->1 transition, never
exits/aborts, always fail-loud + fence + stay alive + retriable.
upgrade_materialization_fenced(c) is the trivial atomic-read accessor.

Not yet wired into the dispatch gate, promotion gate, or an automatic
retry trigger -- that's task 6.2. Task 6.1 of 19.

Requirements: 1.8, 3.2, 3.4
This commit is contained in:
Rongsong Shen 2026-07-25 02:51:49 +00:00
parent 1da26b043e
commit 7b507156a0
4 changed files with 509 additions and 1 deletions

View file

@ -320,6 +320,44 @@ typedef struct {
* cleared and the config is activated.
* NEVER self-terminates (loud non-serving
* fence only). 0 at init (ha_init memset). */
/* ── New field added by config-upgrade-snapshot spec (Task 6.1) ────── */
_Atomic int upgrade_materialization_fence; /* Upgrade-snapshot
* materialization-and-hash-verify
* fence (Req 1.8, 3.2, 3.4) — a NEW,
* INDEPENDENT fence, deliberately NOT
* merged into config_serve_fence
* above: that fence tracks whether the
* out-of-band config-PUSH bytes match
* the committed marker; THIS fence
* tracks whether THIS MEMBER'S OWN
* upgrade-snapshot materialization
* (the marker-triggered checkpoint,
* config-upgrade-snapshot task 5) has
* actually completed for the CURRENT
* committed marker, with the live
* on-disk config bytes still
* hash-verifying against it right
* now. These are logically distinct
* and can disagree in either
* direction (e.g. bytes can already
* hash-match — config_serve_fence
* clear — while this member's own
* checkpoint for that exact marker
* has not run yet, immediately after
* a marker commits but before its
* forced checkpoint fires). Set by
* upgrade_materialization_verify()
* on materialization-incomplete or a
* hash mismatch against a FRESH
* re-read of the live config bytes
* (never a cached/embedded copy);
* cleared on success. While set, the
* member MUST NOT serve under the new
* configuration or become primary; it
* STAYS ALIVE and keeps retrying.
* NEVER self-terminates (loud
* non-serving fence only). 0 at init
* (ha_init memset). */
/* ── New field added by raft-drive-async spec (Task 2.1) ──────── */
_Atomic int reconnect_inflight[MAX_HA_CANDIDATES]; /* Per-peer
* single-flight guard for the offloaded
@ -755,6 +793,41 @@ int ha_dispatch_gate(ha_state_t *ha, int opcode);
*/
int ha_config_promotion_fenced(const ha_state_t *ha);
/*
* upgrade_materialization_verify (config-upgrade-snapshot task 6.1, Req 1.8,
* 3.2, 3.4): the Activation Fence's own verify function. Mirrors
* config_activate_committed's exact shape (config_repl.c:304-377) for a
* DIFFERENT, independently-tracked condition — see ha_state_t's
* upgrade_materialization_fence field doc comment above for the full
* rationale on why this must be a separate fence from config_serve_fence.
*
* On every call: reads the current committed WAL_CONFIG_MARKER (the
* authority for the target upgrade), checks whether THIS MEMBER'S OWN
* checkpoint machinery has already captured a materialization reference for
* that EXACT marker (c->persist.cfgref_present/cfgref_tag/cfgref_sha,
* stamped by every checkpoint via persist.c's scan_latest_committed_config_
* ref), and separately re-reads the LIVE, currently-active config bytes off
* disk FRESH (never a cached/embedded copy) to hash-verify them against the
* marker too. Both must hold for the fence to clear.
*
* Returns 0 (fence cleared or absent-marker benign case) on success;
* -EAGAIN when materialization is incomplete or the hash mismatches (fence
* SET, logged loud only on the 0->1 transition); -EINVAL/-EIO on a genuine
* error reading the committed marker. NEVER exits/aborts on this condition
* (controller/CLAUDE.md never-self-terminate invariant) — always fail loud +
* fence + stay alive + retriable.
*/
int upgrade_materialization_verify(cluster_t *c);
/*
* upgrade_materialization_fenced: 1 when this member's own upgrade-snapshot
* materialization for the current committed config marker is incomplete or
* hash-mismatched (upgrade_materialization_fence set); 0 otherwise. Trivial
* atomic-read accessor — mirrors config_serve_fenced (config_repl.c:262-268)
* exactly, for the new independent fence. Pure atomic read (no I/O).
*/
int upgrade_materialization_fenced(const cluster_t *c);
/*
* ha_apply_wal_catchup_entry: apply a single WAL catch-up entry to local
* persistent storage and advance ha->applied_lsn on success.