R-444: hub half — the System page shows each box's last disk trim

sysfacts reads the agent's top-level guest_disk_trim stanza (schedule + per-guest
last attempt: vmid, last_attempt_at, ok, bytes_trimmed, mounts, duration_seconds,
last_ok_at, error) into a field-by-field mirror. The System page's new 'Last disk
trim' column shows the last successful trim and the GiB it freed; amber when the
newest attempt failed (error shown) or last_ok_at is older than 14 days (judged on
the success time, never the attempt time); '—' when the agent sends no stanza.
wire_contract_gate: SUBTREE_MIRRORS checks guest_disk_trim field by field BOTH
ways against sysfacts.DiskTrim; decoys (ok renamed, last_ok_at dropped) in
test_gate_decoys.py. Decision 139.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-06 11:28:20 +02:00
parent 9d39faabf8
commit a411cde7c4
7 changed files with 303 additions and 13 deletions
+44 -1
View File
@@ -63,7 +63,9 @@ COVERS = {
"must convict (it passed for months as `language` did); the genuine article — the same "
"name in a struct tag beside a `//` inside a string literal — must pass; and R-315: the "
"agent RENAMING its mirror's `superseded_at` tag (the old name still occurs as a local-API "
"map key) must convict on the field-by-field mirror check"),
"map key) must convict on the field-by-field mirror check; and R-444: the agent renaming "
"`guest_disk_trim.guests.ok` to `succeeded` (a name the hub carries elsewhere) or DROPPING `last_ok_at` (a field the "
"hub reads and would show as never-ok for ever) must convict on the subtree mirror"),
"stands": ("R-819: a stand citing a register id that exists in NEITHER register, and a stand "
"marked 'walked' whose only source is a register row (a green dot from a label); "
"plus the genuine article — a stand citing only a CLOSED row, which must PASS"),
@@ -750,6 +752,47 @@ for _mode, _want in (("renamed", 10), ("genuine", 0)):
print(" ok %-20s %s" % ("wire-mirror/" + _mode,
"decoy rejected" if _want else "genuine accepted"))
# ── wire-contract subtree mirror (R-444) ─────────────────────────────────────────────────────────
#
# `guest_disk_trim` is checked field by field BOTH ways against hub sysfacts.DiskTrim. Two decoys on the agent's parsed
# GuestDiskTrim body (no tree copy): `ok` renamed to `succeeded` — the name check passes it, since `succeeded` occurs in the hub — and
# `last_ok_at` dropped, which only the reverse direction can see. The genuine article must pass.
_WCS = r"""
import os, sys
sys.path.insert(0, "scripts")
import wire_contract_gate as g
mode = sys.argv[1]
orig, agent = g.build_index, os.path.abspath(g.REPOS["agent"])
def patched(root):
by_dir, by_name = orig(root)
if mode != "genuine" and os.path.abspath(root) == agent:
k = ("internal/hub", "GuestDiskTrim")
b = by_dir[k]
if mode == "renamed":
assert 'json:"ok"' in b, "mutation target missing"
b = b.replace('json:"ok"', 'json:"succeeded"')
else:
assert 'json:"last_ok_at' in b, "mutation target missing"
b = "\n".join(l for l in b.split("\n") if 'json:"last_ok_at' not in l)
by_dir[k] = b
by_name["GuestDiskTrim"] = [(d, b if d == k[0] else x) for d, x in by_name["GuestDiskTrim"]]
return by_dir, by_name
g.build_index = patched
rc, conv = g.run(quiet=True)
want = {"renamed": "guest_disk_trim.guests.succeeded", "dropped": "guest_disk_trim.guests.last_ok_at"}.get(mode)
hit = any(d == want for _, _, m in conv for _, d in m)
print("rc=%d convicted=%s" % (rc, hit))
sys.exit(0 if rc == 0 else (10 if hit else 11))
"""
for _mode, _want in (("renamed", 10), ("dropped", 10), ("genuine", 0)):
ran += 1
_p = subprocess.run([sys.executable, "-c", _WCS, _mode], cwd=ROOT, capture_output=True, text=True)
if _p.returncode != _want:
fails.append("wire-trim/%s: rc=%d, want %d (10 = the mutated trim field convicted, 0 = passed)\n%s"
% (_mode, _p.returncode, _want, (_p.stdout + _p.stderr)[-500:]))
else:
print(" ok %-20s %s" % ("wire-trim/" + _mode, "decoy rejected" if _want else "genuine accepted"))
# ── stands (R-819) ───────────────────────────────────────────────────────────────────────────────
#
# check_stands.py was red and in no runner. Its decoys are stand files written as a session would write
+34
View File
@@ -105,6 +105,17 @@ MIRRORS = {
"hub -> controller (report ACK, `escrow` object)": ("internal/report", "EscrowStatus"),
}
# R-444 (2026-10-06): a SUBTREE of a name-checked root whose receiver decodes it into one named mirror type gets the
# field-by-field check in BOTH directions — every emitted path under it must be a json path of the mirror, AND every
# mirror path must be emitted (a field the hub reads and the agent never sends would leave the page on "—" for ever).
# Both directions, because some leaves here are short names (`ok`, `error`, `vmid`) the name check cannot judge:
# `ok` occurs everywhere, so a renamed `ok` would pass it. Keyed by (root label, dotted subtree path):
# (receiver package dir, receiver type). A subtree the emitter does not carry YET prints PENDING — the receiver was
# built first, against a provisional shape — and is neither a pass nor a conviction of the subtree.
SUBTREE_MIRRORS = {
("agent -> hub (POST /host-report)", "guest_disk_trim"): ("internal/sysfacts", "DiskTrim"),
}
# Tag names whose literal string carries no information in a repo-wide search. NOT CHECKED.
# Listed rather than silently skipped: each one is a hole.
GENERIC = {
@@ -630,6 +641,29 @@ def run(root_override=None, quiet=False):
convictions.append((label, receiver, missing))
continue
kinds.append((label, "name-reachability only (a tag found ANYWHERE in %s passes)" % receiver))
emitted_paths = {d for _, d in tags}
for (slabel, sub), (mdir, mtype) in sorted(SUBTREE_MIRRORS.items()):
if slabel != label:
continue
rby_dir, rby_name = indexes[receiver]
if (mdir, mtype) not in rby_dir:
die("wire-contract gate INCONCLUSIVE: declared subtree mirror %s.%s not found in %s/%s\n"
" A mirror that cannot be resolved is not a pass — fix SUBTREE_MIRRORS or the type."
% (receiver, mtype, receiver, mdir))
if sub not in emitted_paths:
kinds.append((" └ " + sub, "PENDING — the emitter does not carry `%s` yet; %s.%s is unchecked"
% (sub, mdir, mtype)))
continue
want = {sub + "." + d for _, d in walk(rby_dir, rby_name, mdir, mtype)}
got = {d for d in emitted_paths if d.startswith(sub + ".")}
for d in sorted(got - want):
checked += 1
missing.append((d.split(".")[-1], d))
for d in sorted(want - got):
checked += 1
missing.append((d.split(".")[-1] + " (read by the receiver, never emitted)", d))
kinds.append((" └ " + sub, "FIELD-BY-FIELD both ways against %s %s.%s (%d path(s))"
% (receiver, mdir, mtype, len(want | got))))
for tag, dotted in sorted(seen_tags.items()):
if tag in GENERIC:
skipped += 1