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
+32 -2
View File
@@ -59,6 +59,28 @@ type ConfigBundle struct {
Drift []string `json:"drift"`
}
// DiskTrim is the agent's weekly guest disk trim stanza (R-444, `09` §3 decision 139): the host report's TOP-LEVEL
// `guest_disk_trim` object (agent internal/hub GuestDiskTrimStatus). A field-by-field mirror of the agent's type,
// checked BOTH ways by the wire-contract gate (SUBTREE_MIRRORS in scripts/wire_contract_gate.py), so a rename or a new
// field on either side convicts.
type DiskTrim struct {
Schedule string `json:"schedule"`
Guests []GuestTrim `json:"guests"`
}
// GuestTrim is one guest's LAST trim attempt. `ok` with `last_attempt_at` is that attempt's verdict; `last_ok_at` is
// the last attempt that succeeded ("" = never) — staleness is judged on it, never on the attempt time alone.
type GuestTrim struct {
VMID int `json:"vmid"`
LastAttemptAt string `json:"last_attempt_at"` // RFC3339
OK bool `json:"ok"`
BytesTrimmed int64 `json:"bytes_trimmed"`
Mounts int `json:"mounts"`
DurationSeconds float64 `json:"duration_seconds"`
LastOKAt string `json:"last_ok_at"` // RFC3339, "" = never
Error string `json:"error"`
}
// System is the whole stanza. Present is false for a report from an agent older than v0.142.0.
type System struct {
Present bool
@@ -70,10 +92,14 @@ type System struct {
Host Host
Guest Guest
Bundle ConfigBundle
// DiskTrim is the top-level `guest_disk_trim` stanza (R-444). Independent of the `system` stanza: it is read even
// when Present is false. nil = the agent sent none (an agent with no trimmer).
DiskTrim *DiskTrim
}
type wire struct {
System *struct {
DiskTrim *DiskTrim `json:"guest_disk_trim"`
System *struct {
PVEVersion string `json:"pve_version"`
KernelVersion string `json:"kernel_version"`
VMID int `json:"vmid"`
@@ -97,7 +123,11 @@ func Parse(reportJSON string) System {
Host: Host{Debian: Unknown, KernelRunning: Unknown, KernelNextBoot: Unknown},
Guest: Guest{Debian: Unknown, DockerEngine: Unknown, Containerd: Unknown, LiveRestore: Unknown}}
var w wire
if json.Unmarshal([]byte(reportJSON), &w) != nil || w.System == nil {
if json.Unmarshal([]byte(reportJSON), &w) != nil {
return out
}
out.DiskTrim = w.DiskTrim
if w.System == nil {
return out
}
out.Present = true
+29
View File
@@ -41,3 +41,32 @@ func TestParse_AbsentAndPartialAreUnknown(t *testing.T) {
t.Fatal("unknown kernels must not read as 'differs'")
}
}
// R-444: the top-level `guest_disk_trim` stanza is read on its own — with or without a `system` stanza — and an absent
// stanza stays nil (the page says "—", never a made-up trim). The fixture is the agent's TestReportJSONShape output shape.
func TestParse_DiskTrim(t *testing.T) {
s := Parse(`{"host":{},"guest_disk_trim":{"schedule":"weekly, Wednesday from 10:00","guests":[
{"vmid":9201,"last_attempt_at":"2026-10-07T08:30:00Z","ok":true,"bytes_trimmed":90143313920,"mounts":2,"duration_seconds":24.4,"last_ok_at":"2026-10-07T08:30:00Z"},
{"vmid":9202,"last_attempt_at":"2026-10-07T08:35:00Z","ok":false,"bytes_trimmed":0,"mounts":0,"duration_seconds":1.2,"error":"pct fstrim: exit 255"}]}}`)
if s.Present {
t.Error("no system stanza: Present must stay false")
}
d := s.DiskTrim
if d == nil || d.Schedule != "weekly, Wednesday from 10:00" || len(d.Guests) != 2 {
t.Fatalf("guest_disk_trim misread: %+v", d)
}
g, f := d.Guests[0], d.Guests[1]
if g.VMID != 9201 || !g.OK || g.BytesTrimmed != 90143313920 || g.Mounts != 2 || g.DurationSeconds != 24.4 ||
g.LastAttemptAt != "2026-10-07T08:30:00Z" || g.LastOKAt != "2026-10-07T08:30:00Z" {
t.Errorf("guest 9201 misread: %+v", g)
}
if f.OK || f.Error != "pct fstrim: exit 255" || f.LastOKAt != "" {
t.Errorf("guest 9202 misread: %+v", f)
}
if s = Parse(full); s.DiskTrim != nil {
t.Errorf("a report with no guest_disk_trim must read nil, got %+v", s.DiskTrim)
}
if s = Parse(`{"system":{"pve_version":"x"},"guest_disk_trim":{"schedule":"w","guests":[]}}`); !s.Present || s.DiskTrim == nil || len(s.DiskTrim.Guests) != 0 {
t.Errorf("with a system stanza the trim must still be read: %+v", s)
}
}
+80 -7
View File
@@ -40,6 +40,7 @@ type systemRow struct {
Agent cell // R-530: the box's agent against the vouched one
// guest
GuestDebian, GuestRelease, GuestPending, GuestRestart cell
Trim cell // R-444: the last `pct fstrim` of the guest
// docker
Engine, Containerd, LiveRestore, DockerRelease cell
// last leg
@@ -112,6 +113,77 @@ func buildFloorRows(ovs []store.CustomerFloorOverride, global string, now time.T
func plain(s string) cell { return cell{Text: s} }
// trimStaleAfter is when a guest's last SUCCESSFUL disk trim reads amber: the boxes trim weekly (`09` §3 decision
// 139), so two missed weeks are worth a look.
const trimStaleAfter = 14 * 24 * time.Hour
// trimCell is the "Last disk trim" cell (R-444): per guest, the last successful `pct fstrim` (from `last_ok_at`) and what
// the newest attempt freed. Amber when the newest attempt failed (its error is shown) or the last success is older than
// 14 days — judged on `last_ok_at`, never on the attempt time (an attempt is not a result). "—" when the agent sends no
// stanza (no trimmer); an unreadable time is "unknown", never a guess.
func trimCell(dt *sysfacts.DiskTrim, now time.Time) cell {
if dt == nil {
return cell{Text: "—", Title: "the agent reports no disk trim (an agent without the weekly trim)"}
}
if len(dt.Guests) == 0 {
return cell{Text: "none yet", Title: "the weekly trim runs (" + dt.Schedule + ") and has not trimmed a guest yet"}
}
out := cell{Title: "schedule: " + dt.Schedule}
var texts []string
for _, g := range dt.Guests {
t, warn, why := guestTrimText(g, now)
if len(dt.Guests) > 1 {
t = fmt.Sprintf("%d: %s", g.VMID, t)
}
texts = append(texts, t)
if warn {
out.Class = "warn"
out.Title = fmt.Sprintf("guest %d: %s — %s", g.VMID, why, out.Title)
}
}
out.Text = strings.Join(texts, " / ")
return out
}
func guestTrimText(g sysfacts.GuestTrim, now time.Time) (text string, warn bool, why string) {
var okAt time.Time
if g.LastOKAt != "" {
t, err := time.Parse(time.RFC3339, g.LastOKAt)
if err != nil {
return "unknown", true, fmt.Sprintf("unreadable last_ok_at %q", g.LastOKAt)
}
okAt = t
}
attAt, aerr := time.Parse(time.RFC3339, g.LastAttemptAt)
if aerr != nil {
return "unknown", true, fmt.Sprintf("unreadable last_attempt_at %q", g.LastAttemptAt)
}
if g.OK {
text = fmt.Sprintf("%s · %.1f GiB", ago(attAt, now), float64(g.BytesTrimmed)/(1<<30))
if !okAt.IsZero() {
text = fmt.Sprintf("%s · %.1f GiB", ago(okAt, now), float64(g.BytesTrimmed)/(1<<30))
} else {
okAt = attAt
}
} else {
last := "never ok"
if !okAt.IsZero() {
last = "last ok " + ago(okAt, now)
}
errText := g.Error
if errText == "" {
errText = "no error text"
}
text = fmt.Sprintf("FAILED %s: %s (%s)", ago(attAt, now), errText, last)
warn, why = true, "the newest trim failed"
}
if !okAt.IsZero() && now.Sub(okAt) > trimStaleAfter {
warn = true
why = strings.TrimPrefix(why+"; ", "; ") + "the last successful trim is older than 14 days (the boxes trim weekly)"
}
return text, warn, why
}
// bundleCell is the "Root files" cell (R-840): the box's config bundle against the vouched agent's. Amber while behind,
// red from the alarm's wait on, amber when a file was changed by hand (drift); "unknown" is never coloured as a fact.
func bundleCell(f sysfacts.System, vouchedAgent, vouchedSHA string, since time.Time, after time.Duration, now time.Time) cell {
@@ -239,6 +311,7 @@ func buildSystemRows(lines []osupdates.FleetLine, facts map[string]sysfacts.Syst
r.GuestRelease = plain(orDash(l.Guest.ReleaseID))
r.GuestPending = plain(fmt.Sprint(l.Guest.Pending))
r.GuestRestart = plain(fmt.Sprint(l.Guest.RestartNeeded))
r.Trim = trimCell(f.DiskTrim, now)
r.Engine, r.Containerd = unknownCell(f.Guest.DockerEngine), unknownCell(f.Guest.Containerd)
r.LiveRestore = unknownCell(f.Guest.LiveRestore)
if f.Guest.LiveRestore == "off" {
@@ -312,16 +385,16 @@ func (s *Server) handleSystem(w http.ResponseWriter, r *http.Request) {
s.logger.Printf("[ERROR] system page: floor overrides: %v", oerr)
}
data := map[string]interface{}{
"Rows": rows,
"Rows": rows,
"GlobalFloor": global,
"VouchedAgent": man.AgentVersion,
"Floors": buildFloorRows(ovs, global, time.Now()),
"Releases": view.Releases(),
"Cancelled": view.CancelledReleases(),
"Candidates": view.Candidates(),
"Flash": r.URL.Query().Get("flash"),
"FlashErr": r.URL.Query().Get("err"),
"CSRFToken": s.getCSRFToken(r),
"Releases": view.Releases(),
"Cancelled": view.CancelledReleases(),
"Candidates": view.Candidates(),
"Flash": r.URL.Query().Get("flash"),
"FlashErr": r.URL.Query().Get("err"),
"CSRFToken": s.getCSRFToken(r),
}
if err := s.templates.ExecuteTemplate(w, "system.html", data); err != nil {
s.logger.Printf("[ERROR] system.html template: %v", err)
+81
View File
@@ -0,0 +1,81 @@
package web
import (
"fmt"
"strings"
"testing"
"time"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
"gitea.dooplex.hu/admin/felhom-hub/internal/sysfacts"
)
// R-444 (`09` §3 decision 139): the System page's "Last disk trim" cell — one case per branch. Staleness is judged on
// last_ok_at, never on the attempt time ("presence is not success").
func TestTrimCell(t *testing.T) {
now := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
at := func(d time.Duration) string { return now.Add(-d).Format(time.RFC3339) }
day := 24 * time.Hour
dt := func(gs ...sysfacts.GuestTrim) *sysfacts.DiskTrim {
return &sysfacts.DiskTrim{Schedule: "weekly", Guests: gs}
}
cases := []struct {
name string
dt *sysfacts.DiskTrim
text string
class string
titleHas string
}{
{"agent sends nothing", nil, "—", "", "reports no disk trim"},
{"stanza, no guest yet", dt(), "none yet", "", "has not trimmed a guest yet"},
{"recent ok", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(2 * day), LastOKAt: at(2 * day), OK: true, BytesTrimmed: 32427003904}),
"2 days ago · 30.2 GiB", "", "schedule: weekly"},
{"ok but older than 14 days", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(15 * day), LastOKAt: at(15 * day), OK: true, BytesTrimmed: 1 << 30}),
"15 days ago · 1.0 GiB", "warn", "older than 14 days"},
{"exactly 14 days is not stale", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(14 * day), LastOKAt: at(14 * day), OK: true}),
"14 days ago · 0.0 GiB", "", ""},
{"newest failed, recent success before it", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(1 * day), LastOKAt: at(8 * day), OK: false, Error: "pct fstrim: exit 255"}),
"FAILED 24 h ago: pct fstrim: exit 255 (last ok 8 days ago)", "warn", "the newest trim failed"},
{"failed and never ok", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(1 * day), OK: false}),
"FAILED 24 h ago: no error text (never ok)", "warn", "the newest trim failed"},
{"a recent ATTEMPT does not hide a 20-day-old success", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(1 * day), LastOKAt: at(20 * day), OK: false, Error: "busy"}),
"FAILED 24 h ago: busy (last ok 20 days ago)", "warn", "older than 14 days"},
{"two guests, one amber", dt(
sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: at(2 * day), LastOKAt: at(2 * day), OK: true, BytesTrimmed: 2 << 30},
sysfacts.GuestTrim{VMID: 9202, LastAttemptAt: at(1 * day), OK: false, Error: "x"}),
"9201: 2 days ago · 2.0 GiB / 9202: FAILED 24 h ago: x (never ok)", "warn", "guest 9202"},
{"unreadable time", dt(sysfacts.GuestTrim{VMID: 9201, LastAttemptAt: "yesterday", OK: true}), "unknown", "warn", "unreadable last_attempt_at"},
}
for _, c := range cases {
got := trimCell(c.dt, now)
if got.Text != c.text || got.Class != c.class || !strings.Contains(got.Title, c.titleHas) {
t.Errorf("%s: got %+v, want text %q class %q title containing %q", c.name, got, c.text, c.class, c.titleHas)
}
}
}
// The cell reaches the rendered page, read from the stored host report (the endpoint the operator's browser calls).
func TestSystemPage_LastDiskTrim(t *testing.T) {
s, st, _ := systemServer(t)
when := time.Now().Add(-20 * 24 * time.Hour).UTC().Format(time.RFC3339)
body := fmt.Sprintf(`{"host":{"cpu_percent":1},"guest_disk_trim":{"schedule":"weekly","guests":[{"vmid":9201,"last_attempt_at":%q,"ok":true,"bytes_trimmed":32427003904,"mounts":2,"duration_seconds":24.4,"last_ok_at":%q}]}}`, when, when)
if err := st.UpsertHost(&store.Host{HostID: "trim-1", CustomerID: "c-trim", APIKey: "k-trim"}); err != nil {
t.Fatal(err)
}
if err := st.SaveHostReport("trim-1", "c-trim", []byte(body), store.HostReportDenorm{AgentVersion: "0.150.0", CloudflaredStatus: "running"}); err != nil {
t.Fatal(err)
}
b := getSystem(t, s)
if !strings.Contains(b, ">Last disk trim</th>") {
t.Error("the System page lacks the Last disk trim column")
}
if !strings.Contains(b, "20 days ago · 30.2 GiB") {
t.Errorf("the trim-1 box's trim is not on the page")
}
if !strings.Contains(b, `class="c-warn" title="guest 9201: the last successful trim is older than 14 days`) {
t.Error("a 20-day-old trim must be amber, with its reason")
}
if !strings.Contains(b, `title="the agent reports no disk trim`) {
t.Error("a box whose agent sends no trim must read — with its reason")
}
}
+3 -3
View File
@@ -104,11 +104,11 @@
<tr>
<th>Box</th><th>Ring / updates</th><th>Tunnel</th>
<th class="grp">Proxmox</th><th>Kernel (running)</th><th>Kernel (next boot)</th><th>Debian</th><th>Felhom release</th><th>Pending</th><th>Not covered</th><th>Held</th><th>Reboot needed</th><th>kernel.panic</th><th>Oops</th><th>Crash restarts 24 h</th><th>Crash guard</th><th title="The root-owned config bundle: sudoers, wrappers, units (R-840)">Root files</th><th title="The box's agent against the vouched one (R-530). Agents update only by a per-box signed job.">Agent</th>
<th class="grp">Guest Debian</th><th>Felhom release</th><th>Pending</th><th>Restart needed</th>
<th class="grp">Guest Debian</th><th>Felhom release</th><th>Pending</th><th>Restart needed</th><th title="The newest pct fstrim of the guest's disks, and what it freed (R-444). The boxes trim weekly.">Last disk trim</th>
<th class="grp">Docker</th><th>containerd</th><th>live-restore</th><th>Docker release</th>
<th class="grp">Last OS leg</th>
</tr>
<tr class="text-muted"><th></th><th></th><th></th><th class="grp" colspan="15">host</th><th class="grp" colspan="4">guest</th><th class="grp" colspan="4">Docker engine</th><th class="grp"></th></tr>
<tr class="text-muted"><th></th><th></th><th></th><th class="grp" colspan="15">host</th><th class="grp" colspan="5">guest</th><th class="grp" colspan="4">Docker engine</th><th class="grp"></th></tr>
</thead>
<tbody>
{{range .Rows}}
@@ -136,7 +136,7 @@
{{template "sys_cell" .Held}}{{template "sys_cell" .RebootSince}}{{template "sys_cell" .KernelPanic}}{{template "sys_cell" .Oops}}
{{template "sys_cell" .CrashRestarts24h}}{{template "sys_cell" .Guard}}{{template "sys_cell" .Bundle}}{{template "sys_cell" .Agent}}
<td class="grp {{if .GuestDebian.Class}}c-{{.GuestDebian.Class}}{{end}}">{{.GuestDebian.Text}}</td>
{{template "sys_cell" .GuestRelease}}{{template "sys_cell" .GuestPending}}{{template "sys_cell" .GuestRestart}}
{{template "sys_cell" .GuestRelease}}{{template "sys_cell" .GuestPending}}{{template "sys_cell" .GuestRestart}}{{template "sys_cell" .Trim}}
<td class="grp {{if .Engine.Class}}c-{{.Engine.Class}}{{end}}">{{.Engine.Text}}</td>
{{template "sys_cell" .Containerd}}{{template "sys_cell" .LiveRestore}}{{template "sys_cell" .DockerRelease}}
<td class="grp {{if .LastLeg.Class}}c-{{.LastLeg.Class}}{{end}}" title="{{.LastLeg.Title}}">{{.LastLeg.Text}}</td>
+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