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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user