feat(config-upgrade-snapshot): add configcompare tool, extend snapverify (task 8.4)

Build configcompare_paths(), an offline sibling to snapdiff/walcompare that
recomputes each member's active-config sha256 fresh from its own on-disk
workdir/name/conf bytes and reports identical vs. divergent (Req 4.2).
Wire it as a cmd_configcompare CLI subcommand in test.c, matching its
siblings, after review caught it shipped as an unreachable library function
in round 1.

Extend snapverify to match the current on-disk checkpoint format: bump
SNAP_FORMAT_VERSION to 2, replace the stale raw queue-frame decode with the
capnp length-prefixed walk, add host-frame and config-bytes-section
structural walks, and decode (not just presence-check) the header's
cfgref tag/sha. Without this fix snapverify rejected every checkpoint
written by the current codebase.
This commit is contained in:
Rongsong Shen 2026-07-25 09:06:24 +00:00
parent c5d8ecbd7d
commit 1d09631df0
10 changed files with 997 additions and 47 deletions

View file

@ -1,14 +1,20 @@
#pragma once
/*
* snapverify.h — read-only checkpoint-file structural inspector/validator
* (bug-1389 triage: OPCode_snapshotTransfer chunk-content corruption).
* (bug-1389 triage: OPCode_snapshotTransfer chunk-content corruption;
* extended by config-upgrade-snapshot task 8.4, Requirement 4.2, for the
* SNAPSHOT_FORMAT_VERSION=2 host + config-bytes sections and to decode --
* not merely report the presence of -- the header's cfgref tag/hash).
*
* Parses the on-disk checkpoint layout persist.c's load_snapshot() /
* load_snapshot_entities() use (design.md "Checkpoint File Layout") WITHOUT
* restoring any job/queue into a live cluster_t — a pure structural walk, so
* it needs no jobmgr/qmgr context and is safe to run standalone against a
* snap-*.capnp (locally generated) or snap-*.bin (received via
* ha_handle_snapshot_chunk, ha.c) file on disk.
* restoring any job/queue/host into a live cluster_t — a pure structural
* walk, so it needs no jobmgr/qmgr/workermgr context and is safe to run
* standalone against a snap-*.capnp (locally generated) or snap-*.bin
* (received via ha_handle_snapshot_chunk, ha.c) file on disk. This tool
* validates STRUCTURE only, never content: job/queue/host frame payloads
* and the config-bytes section are walked past via their length prefix,
* never capnp-decoded or interpreted.
*
* Strictly read-only: opens O_RDONLY, mutates nothing.
*/
@ -18,7 +24,17 @@
typedef struct {
uint64_t checkpoint_lsn;
int has_cfgref; /* config-ref tail present in the header */
int has_cfgref; /* config-ref tail present in the header
* (header[16] != 0) -- semantic presence,
* distinct from cfgref_tag/cfgref_sha below
* being decoded */
uint64_t cfgref_tag; /* header cfgref tag, decoded whenever the
* header extends to SNAP_HEADER_MAX bytes,
* REGARDLESS of has_cfgref -- meaningful
* only when has_cfgref is also 1 */
uint8_t cfgref_sha[32]; /* header cfgref sha256, same decode-
* unconditionally-but-gate-meaning-on-
* has_cfgref discipline as cfgref_tag */
int has_entity_section; /* fmt + job_count present past the header;
* 0 = legacy position-only checkpoint */
uint32_t format_version;
@ -27,6 +43,22 @@ typedef struct {
uint32_t queue_count; /* as declared in the file (0 if legacy, or if the
* job-frame walk failed before reaching it) */
uint32_t queues_ok; /* queue frames structurally walked before any failure */
uint32_t host_count; /* as declared in the file (0 if legacy, or if an
* earlier section's walk failed before reaching
* it) */
uint32_t hosts_ok; /* host frames structurally walked before any failure */
int has_config_bytes_section; /* 1 iff the trailing config-bytes
* section's 4-byte length field
* itself was present and readable
* (i.e. the file extends past the
* host section at all) -- TRUE even
* when that length decodes to 0, and
* FALSE (not a failure) when the
* file simply ends right after the
* host section */
uint32_t config_bytes_len; /* decoded length of the config-bytes
* section; valid only when
* has_config_bytes_section is 1 */
off_t file_size;
off_t consumed; /* bytes structurally accounted for */
int ok; /* 1 = well-formed, consumed == file_size */