6446197925
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
83 lines
3.3 KiB
Python
83 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""check-probe-measured.py — a `setup_done_probe:` must say what it MEASURED, before and after (R-715).
|
|
|
|
WHY. The setup gate (`09` §3 decision 46) opens an app when its own status says the first admin exists. A probe
|
|
that never flips keeps the app closed to everyone but the household — and it also makes the household's "Done"
|
|
press refuse (the press asks the probe first). Measured 2026-09-29 on gramps-web: its status answered 405 after the
|
|
setup, the box read only 200s, and the press was refused until the catalog was fixed. And zipline's upstream status
|
|
route answered 403 after the setup. A probe copied from upstream docs is a guess; only a before/after measurement on
|
|
a real install is evidence.
|
|
|
|
THE RULE. The comment block directly above `setup_done_probe:` (the contiguous `#` lines) must carry:
|
|
* the word "measured",
|
|
* a date (YYYY-MM-DD), and
|
|
* both answers: "before … after …", or "<value> -> <value>".
|
|
A comment elsewhere in the file (the tagline, another block) does not count.
|
|
|
|
Run from the repo root: python3 scripts/check-probe-measured.py [--root=DIR] [app ...]
|
|
Exit 0 all probes carry a measurement · 1 a probe without one.
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
DATE = re.compile(r"\b20\d\d-\d\d-\d\d\b")
|
|
|
|
|
|
def comment_above(lines, i):
|
|
out = []
|
|
j = i - 1
|
|
while j >= 0 and lines[j].lstrip().startswith("#"):
|
|
out.append(lines[j].lstrip()[1:].strip())
|
|
j -= 1
|
|
return " ".join(reversed(out))
|
|
|
|
|
|
def check(path):
|
|
lines = open(path, encoding="utf-8").read().split("\n")
|
|
for i, l in enumerate(lines):
|
|
if l.startswith("setup_done_probe:"):
|
|
c = comment_above(lines, i)
|
|
low = c.lower()
|
|
has_pair = "->" in c or ("before" in low and "after" in low)
|
|
if "measured" not in low or not DATE.search(c) or not has_pair:
|
|
return "the comment above setup_done_probe: lacks %s (found: %r)" % (
|
|
", ".join(x for x, ok in (("the word 'measured'", "measured" in low), ("a date", bool(DATE.search(c))),
|
|
("the before/after answers", has_pair)) if not ok), c[:160])
|
|
return None
|
|
|
|
|
|
def main():
|
|
root = ROOT
|
|
apps = []
|
|
for a in sys.argv[1:]:
|
|
if a.startswith("--root="):
|
|
root = a.split("=", 1)[1]
|
|
else:
|
|
apps.append(a)
|
|
tdir = os.path.join(root, "templates")
|
|
names = apps or sorted(d for d in os.listdir(tdir) if os.path.isdir(os.path.join(tdir, d)))
|
|
bad, n = [], 0
|
|
for app in names:
|
|
for dp, _, files in os.walk(os.path.join(tdir, app)):
|
|
if ".felhom.yml" in files and os.path.basename(dp) == app:
|
|
p = os.path.join(dp, ".felhom.yml")
|
|
if "\nsetup_done_probe:" in open(p, encoding="utf-8").read():
|
|
n += 1
|
|
why = check(p)
|
|
if why:
|
|
bad.append("%s: %s" % (app, why))
|
|
if bad:
|
|
print("probe-measured: FAIL — %d of %d probes carry no measurement:" % (len(bad), n))
|
|
for b in bad:
|
|
print(" - " + b)
|
|
return 1
|
|
print("probe-measured: OK — %d probes, each with a measured before/after" % n)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|