v0.228.0 — the off-site check reads the data; the debug page stops lying (R-399 + R-400)
gates / gates (push) Successful in 12s

R-399: monitoring.integrity.read_data_subset defaults to 100%. A pack damaged
without changing its size made plain `restic check` report "no errors were found"
on demo-hp 2026-08-30; every read-data form caught it. Cost on that 134 MB store:
35.0s structure vs 39.2s at 100%. "off" (any case) is the off token; empty means
not-configured, therefore the default; a malformed value falls back to the DEFAULT,
never to structure. A completed check over 5 minutes logs a WARN naming the
duration, the depth and R-401 — operator log only, no hub event, no depth change.
The depth is now recorded with the verdict (LastIntegrityDepth; empty = NOT
RECORDED, never "structure").

R-400: 24 debug-page references, 17 dispatched, 7 dead — three of which fetched on
page LOAD, so those panels were permanently blank. backup/crossdrive implemented;
backup/infra, hub/infra-push, dr/infra-status, storage/watchdog-status and both
storage/simulate-* deleted with their panels and JavaScript.
scripts/debug_route_gate.py fails in both directions and is registered after the
seven were resolved. 18 referenced, 18 dispatched, none orphaned.

Corrections: the dead-field warning in report/types.go said the controller runs no
integrity check and the notifiers are called from nowhere — both false since
v0.227.0. controller.yaml.example gains its missing integrity: block.
integrityCheckTimeout's "ships OFF" comment rewritten.
This commit is contained in:
2026-08-31 10:24:29 +02:00
parent 300d7e87d7
commit 3c49dc8ea4
23 changed files with 1144 additions and 175 deletions
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Debug-route gate (R-400) — every control on the debug page must resolve to a handler, and every
handler must be reachable from the page.
WHY IT EXISTS. On 2026-08-31 the shipped debug page referenced 24 `/api/debug/...` addresses and the
dispatcher answered 17. Seven controls did nothing, and three of those seven were not buttons at all:
they fetch on page LOAD, so whole panels had been permanently empty and nobody had to click anything
to be misled. This is the page an operator opens when something is already wrong.
BOTH DIRECTIONS FAIL. A reference with no case is a dead control. A case with no reference is a
handler nothing reaches — the same defect mirrored, and the shape this project has now shipped eight
times. Neither is a warning here.
DELIBERATELY TEN LINES OF LOGIC. Two lists and a difference. Its value is that it cannot rot: a
cleverer gate that understood routing would need maintaining, and an unmaintained gate is how the
class hides in the first place. The dispatcher's EXACT-match switch with a NotFound default is what
made the original defect visible, and this gate assumes exactly that shape — do not make either clever.
Run from controller/: python3 scripts/debug_route_gate.py
"""
import io
import os
import re
import sys
TEMPLATE = os.path.join("internal", "web", "templates", "debug.html")
DISPATCH = os.path.join("internal", "web", "handler_debug.go")
REF_RE = re.compile(r"/api/debug/([A-Za-z0-9/_-]+)")
CASE_RE = re.compile(r'subpath\s*==\s*"([A-Za-z0-9/_-]+)"')
def read(path):
if not os.path.exists(path):
print("DEBUG ROUTE GATE INCONCLUSIVE: %s not found (run from controller/)" % path)
sys.exit(2)
return io.open(path, encoding="utf-8").read()
def main():
# Sets, not lists: the same address referenced by two controls is satisfied by one case (§8).
refs = set(REF_RE.findall(read(TEMPLATE)))
cases = set(CASE_RE.findall(read(DISPATCH)))
dead = sorted(refs - cases)
unreached = sorted(cases - refs)
for name in dead:
print("DEAD CONTROL %s references /api/debug/%s and %s has no case for it"
% (TEMPLATE, name, DISPATCH))
for name in unreached:
print("UNREACHED %s dispatches %r and %s never references it"
% (DISPATCH, name, TEMPLATE))
if dead or unreached:
print("DEBUG ROUTE GATE FAILED: %d dead control(s), %d unreached handler(s)"
% (len(dead), len(unreached)))
sys.exit(1)
print("debug route gate OK - %d referenced address(es), all dispatched, none orphaned"
% len(refs))
if __name__ == "__main__":
main()