Skip to content

Commit aba0928

Browse files
NickFlachflaukowskiclaude
authored
test(ci): gate the disk upgrade path — frozen audit home survives field growth (#183)
ci-smoke-disk always writes AND reads with the SAME kernel, so it never exercises the cross-version case that epic #177's frozen audit-ledger home exists to protect: an OLDER kernel's on-disk FIELD descriptor meeting a NEWER field geometry. A future change that un-freezes audit_home_lba() would silently discard the durable authority ledger on every real-hardware upgrade with ZERO ci signal — the compile-time _Static_assert only pins the CONSTANT, not the function's on-disk result. New ci-smoke-disk-upgrade gate (test-only; no kernel change) proves both halves non-vacuously: 1. FROZEN-HOME GUARD: after a normal write, the superblock's audit descriptor must name EXACTLY count-27 (LBA 4069 on the 2 MiB image). Any movement of audit_home_lba() fails this — verified: mutating the function body to count-27-1 makes it report "superblock names 4068" and the gate FAILS (revert-and-confirm-fail). 2. GRACEFUL UPGRADE: with the field descriptor rewritten to the pre-#177 geometry (old home count-6, old 4-region size 2456; audit untouched), the ledger still restores ("AUDIT: restored ledger from disk") and the field cold-starts cleanly ("implausible field section"), with no off-the-end blob read from the stale LBA. Wired into the persistence CI job after ci-smoke-disk + .PHONY. Implementation note: the superblock byte-patch uses python3, not `printf '\xNN' | dd` — make runs recipes under /bin/sh (dash), whose printf lacks \xHH and would splat the literal escape string across the audit descriptor (caught during bring-up: the gate spuriously "failed" because the bad patch corrupted the very ledger it was checking). Co-authored-by: The Hand <[email protected]> Co-authored-by: Claude Opus 4.8 <[email protected]>
1 parent cdd0181 commit aba0928

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,8 @@ jobs:
585585
sudo apt-get install -y build-essential qemu-system-x86 nasm
586586
- name: Prove persistence across reboots
587587
run: make ci-smoke-disk
588+
- name: Prove the disk upgrade path (frozen audit home, epic #177)
589+
run: make ci-smoke-disk-upgrade
588590

589591
quantum-gateway:
590592
name: Quantum Gateway Cross-Oracle (exact engine vs PennyLane — epic #149)

Makefile

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ OBJECTS = $(KERNEL_SOURCES:$(KERNEL_DIR)/src/%.c=$(BUILD_DIR)/%.o) \
111111
-include $(OBJECTS:.o=.d)
112112

113113
# Targets
114-
.PHONY: all clean kernel run debug dump test test-list test-coverage ci-smoke ci-smoke-disk ci-smoke-net ci-smoke-http ci-smoke-httpd ci-smoke-quiet ci-smoke-resonant ci-smoke-qseed ci-smoke-swarm ci-smoke-mcp ci-smoke-mcp-gate ci-smoke-qsubmit ci-smoke-society ci-smoke-society-gate ci-smoke-society-agents ci-smoke-society-agents-gate ci-smoke-society3 ci-smoke-society3-gate ci-smoke-iso ci-smoke-kbd ci-smoke-noserial ci-smoke-screen swarm-pingpong
114+
.PHONY: all clean kernel run debug dump test test-list test-coverage ci-smoke ci-smoke-disk ci-smoke-disk-upgrade ci-smoke-net ci-smoke-http ci-smoke-httpd ci-smoke-quiet ci-smoke-resonant ci-smoke-qseed ci-smoke-swarm ci-smoke-mcp ci-smoke-mcp-gate ci-smoke-qsubmit ci-smoke-society ci-smoke-society-gate ci-smoke-society-agents ci-smoke-society-agents-gate ci-smoke-society3 ci-smoke-society3-gate ci-smoke-iso ci-smoke-kbd ci-smoke-noserial ci-smoke-screen swarm-pingpong
115115

116116
all: kernel
117117

@@ -1355,6 +1355,78 @@ ci-smoke-disk: kernel
13551355
@echo ""
13561356
@echo "=== Persistence Test PASSED ==="
13571357

1358+
# CI Smoke Test (disk UPGRADE path, epic #177): ci-smoke-disk above always
1359+
# writes AND reads with the SAME kernel, so it never exercises the cross-version
1360+
# case where an OLDER kernel's on-disk FIELD descriptor meets a NEWER field
1361+
# geometry. #177 froze the audit-ledger home (AUDIT_HOME_TOP_OFFSET) precisely so
1362+
# a field-geometry bump can never relocate — and thus never discard — the
1363+
# format-unchanged authority ledger on an existing disk. This gate proves BOTH
1364+
# halves, non-vacuously:
1365+
# 1. the on-disk audit descriptor names EXACTLY the frozen home (count-27) —
1366+
# any change to audit_home_lba() moves it and fails this assert. The
1367+
# compile-time _Static_assert pins the CONSTANT; this pins the FUNCTION's
1368+
# actual on-disk result.
1369+
# 2. a superblock carrying a PRE-#177 field descriptor (old home count-6, old
1370+
# 4-region size 2456) restores the ledger anyway and cold-starts the field
1371+
# GRACEFULLY (the lba/bytes mismatch short-circuits before any off-the-end
1372+
# blob read from the stale LBA).
1373+
DISK_UPG_IMG = $(BUILD_DIR)/disk-upgrade.img
1374+
ci-smoke-disk-upgrade: kernel
1375+
@echo "=== QuantumOS Disk Upgrade-Path Test (epic #177 frozen audit home) ==="
1376+
@rm -f $(DISK_UPG_IMG)
1377+
@dd if=/dev/zero of=$(DISK_UPG_IMG) bs=1M count=2 2>/dev/null
1378+
@echo "[boot 1] write a valid QDSK disk (fs + field + audit) with the current kernel..."
1379+
@( printf 'write /data/note upgrade-probe\nimprint the field abides\nsync\n'; sleep 8 ) | \
1380+
timeout 10s qemu-system-x86_64 -kernel $(BUILD_DIR)/kernel.elf32 \
1381+
-drive file=$(DISK_UPG_IMG),format=raw,if=ide -serial stdio -m 128M \
1382+
-display none -no-reboot 2>&1 | tee /tmp/qemu-upg-boot1.log || true
1383+
@if ! grep -q "qsh: sync ok" /tmp/qemu-upg-boot1.log 2>/dev/null; then \
1384+
echo "ERROR: boot 1 sync failed — no valid disk to upgrade"; \
1385+
cat /tmp/qemu-upg-boot1.log 2>/dev/null || true; \
1386+
echo ""; echo "=== Upgrade Test FAILED ==="; exit 1; \
1387+
fi
1388+
@# (1) FROZEN-HOME GUARD: the superblock's audit descriptor (QDSK_AUDIT_LBA_OFF
1389+
@# = byte 44) must name LBA count-27 = 4096-27 = 4069 on this 2 MiB image.
1390+
@# od -tu4 reads the little-endian u32 the kernel wrote.
1391+
@AUDIT_LBA=$$(dd if=$(DISK_UPG_IMG) bs=1 skip=44 count=4 2>/dev/null | od -An -tu4 | tr -d ' '); \
1392+
if [ "$$AUDIT_LBA" != "4069" ]; then \
1393+
echo "ERROR: audit home is NOT frozen at count-27 (4069) — superblock names $$AUDIT_LBA."; \
1394+
echo " audit_home_lba() moved; a field-geometry upgrade would now discard the ledger."; \
1395+
echo ""; echo "=== Upgrade Test FAILED ==="; exit 1; \
1396+
fi
1397+
@echo "SUCCESS: audit ledger home frozen at LBA 4069 (count-27) — survives field growth"
1398+
@# (2) Simulate a PRE-#177 disk: rewrite ONLY the FIELD descriptor to the old
1399+
@# geometry — field_lba (byte 32) = count-6 = 4090, field_bytes (byte 36) =
1400+
@# 2456 (24 + 4*8*76, the 4-region blob). Audit descriptor (byte 44+) is
1401+
@# untouched. python3 writes the two LE u32s unambiguously — a `printf '\xNN'`
1402+
@# is NOT portable (make runs recipes under /bin/sh, whose printf lacks \xHH
1403+
@# and would splat the literal string across the audit descriptor).
1404+
@python3 -c "f=open('$(DISK_UPG_IMG)','r+b'); f.seek(32); f.write((4090).to_bytes(4,'little')+(2456).to_bytes(4,'little')); f.close()"
1405+
@echo "[boot 2] boot the current kernel on the stale-field-descriptor disk..."
1406+
@( printf 'help\n'; sleep 8 ) | \
1407+
timeout 10s qemu-system-x86_64 -kernel $(BUILD_DIR)/kernel.elf32 \
1408+
-drive file=$(DISK_UPG_IMG),format=raw,if=ide -serial stdio -m 128M \
1409+
-display none -no-reboot 2>&1 | tee /tmp/qemu-upg-boot2.log || true
1410+
@# The ledger must RESTORE — its frozen home is untouched by the field change.
1411+
@if ! grep -q "AUDIT: restored ledger from disk" /tmp/qemu-upg-boot2.log 2>/dev/null; then \
1412+
echo "ERROR: the authority ledger was LOST across a field-descriptor upgrade"; \
1413+
cat /tmp/qemu-upg-boot2.log 2>/dev/null || true; \
1414+
echo ""; echo "=== Upgrade Test FAILED ==="; exit 1; \
1415+
fi
1416+
@# ...and the FIELD must cold-start gracefully from the implausible descriptor.
1417+
@if ! grep -q "FIELD: superblock names an implausible field section" /tmp/qemu-upg-boot2.log 2>/dev/null; then \
1418+
echo "ERROR: stale field descriptor did not trigger a graceful cold start"; \
1419+
cat /tmp/qemu-upg-boot2.log 2>/dev/null || true; \
1420+
echo ""; echo "=== Upgrade Test FAILED ==="; exit 1; \
1421+
fi
1422+
@if grep -q "FIELD: restored slots from disk" /tmp/qemu-upg-boot2.log 2>/dev/null; then \
1423+
echo "ERROR: field FALSELY restored from a pre-#177 descriptor"; \
1424+
echo ""; echo "=== Upgrade Test FAILED ==="; exit 1; \
1425+
fi
1426+
@echo "SUCCESS: ledger restored + field cold-started across a field-geometry upgrade"
1427+
@echo ""
1428+
@echo "=== Disk Upgrade-Path Test PASSED ==="
1429+
13581430
# CI Smoke Test (networking): boot WITH an rtl8139 NIC on QEMU's user-mode
13591431
# network (SLIRP), and prove the link layer works end to end. SLIRP's gateway
13601432
# (10.0.2.2) always answers ARP, so an ARP request that gets a reply exercises

0 commit comments

Comments
 (0)