Verify the standing picture against source: 12 downgrades, and the decay ran both ways
gates / gates (push) Successful in 21s
gates / gates (push) Successful in 21s
55 claims verified. Twelve moved, all downwards: walked 32 -> 20, built 5 -> 17. Register ceiling R-284 -> R-290. THE RULE DID NOT FIRE THE WAY IT WAS EXPECTED TO. Not one downgrade came from code moving under an old proof. All twelve came from step 1 of the same rule -- the cited evidence does not exist. Measured: of the 28 capability-map rows behind the page's claims, 8 carry a tests/ or audits/ path and 20 carry prose only. The green dots were drawn from rows that cite an argument, not a walk (R-290). The map, not the dataset, is what needs fixing -- it still says PROVEN-LIVE for all twelve. And once it ran backwards: fault.operator-email looked contradicted by R-182, but live source shows the backup_run_failures digest allowlisted, operator-only and templated, with recovery_unit_capture_failed now record-only. The claim is right and the REGISTER ROW is stale (R-289). The session went looking for stale proofs and found a stale defect. R-281 WITHDRAWN -- wrong in both directions, settled by the operator's mailbox. The tripwire DID fire (escrow_blob_served 10:19:41Z = 12:19 CEST) and false error-severity alarms fired too, for deliberate attended work (R-285). The measurement's cause is ESTABLISHED: the P7 query copied hub.db without hub.db-wal, and the signature is exact -- it reported "2 events all day, newest 00:30:07", and the rows at or before 00:30:07 number exactly 2. Timezone and wrong-key were tested and refuted. The control had been drawn from the same stale snapshot as the measurement, which is why it agreed (R-286). Part 4: NO WORKFLOW CHANGED, deliberately. The gate is not ref-sensitive -- it enumerates from the Gitea tags API, and both previous tag pushes passed. The red is TRUE: run 267 saw v0.120.0 downloadable, run 284 on the same commit saw 404. Who deleted the package is NOT established and is not guessed (R-287). The page is now generated from where-felhom-stands.yaml by scripts/render_stands.py: static, zero script tags, every moved status carrying a visible "changed, was X" chip. The React bundle -- whose content was gzip+base64 inside a JS module map -- is kept as a dated snapshot. scripts/check_stands.py gates the data and convicted 51 problems in my own first draft before the staged positive control ever ran.
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""render_stands.py — generate where-felhom-stands.html from where-felhom-stands.yaml.
|
||||
|
||||
Run by hand, deliberately not wired into CI:
|
||||
|
||||
python3 scripts/check_stands.py && python3 scripts/render_stands.py
|
||||
|
||||
WHY THIS EXISTS. The first version of this page was a React bundle: one 154 KB artefact whose
|
||||
content sat gzip+base64 inside a JS module map. It rendered, and that was all it could do — it
|
||||
could not be edited, a diff of it showed nothing a person could read, and it could not be
|
||||
regenerated when a status moved. So it began going stale the moment it was committed, which is the
|
||||
one thing a picture of "where we stand" must not do.
|
||||
|
||||
This emits plain static HTML with inline CSS and NO JavaScript. It is bigger on disk than it needs
|
||||
to be and that is the trade: the page is a build product of a file a person can read and diff.
|
||||
|
||||
The palette and layout deliberately match the 2026-08-09 snapshot — the operator should recognise
|
||||
the page, not learn a new one.
|
||||
"""
|
||||
import html
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SRC = os.path.join(ROOT, "documentation", "architecture", "where-felhom-stands.yaml")
|
||||
OUT = os.path.join(ROOT, "documentation", "architecture", "where-felhom-stands.html")
|
||||
|
||||
DOT = {"walked": "#34d399", "built": "#60a5fa", "partial": "#fbbf24", "missing": "#64748b"}
|
||||
LEGEND = [
|
||||
("walked", "Walked", "done end to end on real hardware, evidence on file"),
|
||||
("built", "Built", "shipped and tested, the real path never walked"),
|
||||
("partial", "Partial", "some walked, some not — the note says which"),
|
||||
("missing", "Missing", "does not exist"),
|
||||
]
|
||||
STAGES = {1: "Getting a box", 2: "Making it theirs", 3: "Using it", 4: "Drives",
|
||||
5: "Backing up", 6: "When something goes wrong", 7: "Getting everything back"}
|
||||
|
||||
|
||||
def load(path):
|
||||
claims, cur, sect = [], None, None
|
||||
meta = {}
|
||||
for raw in open(path, encoding="utf-8"):
|
||||
line = raw.rstrip("\n")
|
||||
if line.startswith("verified_on:"):
|
||||
meta["date"] = line.split(":", 1)[1].strip()
|
||||
if line.startswith(" - id:"):
|
||||
cur = {"id": line.split(":", 1)[1].strip(), "sources": [], "changed": None}
|
||||
claims.append(cur)
|
||||
sect = None
|
||||
continue
|
||||
if cur is None:
|
||||
continue
|
||||
if line.strip() == "sources:":
|
||||
sect = "sources"
|
||||
continue
|
||||
if line.strip() == "changed:":
|
||||
sect = "changed"
|
||||
cur["changed"] = {}
|
||||
continue
|
||||
if line.strip() == "verified:":
|
||||
sect = "verified"
|
||||
continue
|
||||
m = re.match(r'\s+- (capability-map|evidence|register): (.*)$', line)
|
||||
if sect == "sources" and m:
|
||||
cur["sources"].append((m.group(1), m.group(2).strip().strip('"')))
|
||||
continue
|
||||
m = re.match(r'\s+(\w+): (.*)$', line)
|
||||
if not m:
|
||||
continue
|
||||
k, v = m.group(1), m.group(2).strip().strip('"')
|
||||
if sect == "changed":
|
||||
cur["changed"][k] = v
|
||||
elif sect == "verified":
|
||||
cur.setdefault("v_" + k, v)
|
||||
else:
|
||||
cur.setdefault(k, v)
|
||||
return meta, claims
|
||||
|
||||
|
||||
def esc(t):
|
||||
return html.escape(t, quote=False)
|
||||
|
||||
|
||||
def dot(status):
|
||||
return ('<span style="width:8px;height:8px;border-radius:50%%;background:%s;'
|
||||
'flex:0 0 8px;margin-top:5px"></span>' % DOT.get(status, "#64748b"))
|
||||
|
||||
|
||||
def claim_html(c):
|
||||
bits = ['<div style="display:flex;gap:8px;align-items:flex-start;margin-bottom:9px">', dot(c.get("status"))]
|
||||
body = ['<span style="font-size:12px;line-height:1.45;color:#b8c4d6;text-wrap:pretty">',
|
||||
esc(c.get("title", ""))]
|
||||
if c.get("changed"):
|
||||
body.append('<span style="display:inline-block;margin-left:6px;padding:1px 5px;border-radius:3px;'
|
||||
'background:#3a2a12;color:#fbbf24;font-size:10px;white-space:nowrap">changed 2026-08-09, was %s</span>'
|
||||
% esc(c["changed"].get("from", "?")))
|
||||
if c.get("v_verdict") in ("needs-hardware", "contested"):
|
||||
col = "#a78bfa" if c["v_verdict"] == "contested" else "#7dd3fc"
|
||||
body.append('<span style="display:inline-block;margin-left:6px;padding:1px 5px;border-radius:3px;'
|
||||
'background:#1b2740;color:%s;font-size:10px;white-space:nowrap">%s</span>'
|
||||
% (col, esc(c["v_verdict"])))
|
||||
if c.get("note"):
|
||||
body.append('<span style="display:block;color:#7c8aa3;font-size:11px;margin-top:3px">%s</span>'
|
||||
% esc(c["note"]))
|
||||
if c.get("changed", {}) and c["changed"].get("reason"):
|
||||
body.append('<span style="display:block;color:#9a7b3a;font-size:11px;margin-top:2px">why it moved: %s</span>'
|
||||
% esc(c["changed"]["reason"]))
|
||||
srcs = " · ".join("%s %s" % (k, esc(v if len(v) < 62 else v[:59] + "…")) for k, v in c["sources"])
|
||||
body.append('<span style="display:block;color:#4b5b76;font-size:10px;margin-top:3px;'
|
||||
'font-family:ui-monospace,SFMono-Regular,Menlo,monospace">%s</span>' % srcs)
|
||||
body.append("</span>")
|
||||
bits.append("".join(body))
|
||||
bits.append("</div>")
|
||||
return "".join(bits)
|
||||
|
||||
|
||||
def panel(title, inner, sub=None):
|
||||
h = ['<div style="background:#121b2c;border:1px solid #1f2c44;border-radius:6px;padding:14px 13px">']
|
||||
h.append('<div style="font-size:14px;font-weight:500;padding-bottom:9px;margin-bottom:10px;'
|
||||
'border-bottom:1px solid #1f2c44">%s</div>' % title)
|
||||
if sub:
|
||||
h.append('<div style="color:#6b7a91;font-size:11px;margin:-6px 0 10px">%s</div>' % esc(sub))
|
||||
h.append(inner)
|
||||
h.append("</div>")
|
||||
return "".join(h)
|
||||
|
||||
|
||||
def main():
|
||||
meta, claims = load(SRC)
|
||||
counts = {}
|
||||
for c in claims:
|
||||
counts[c.get("status")] = counts.get(c.get("status"), 0) + 1
|
||||
changed = [c for c in claims if c.get("changed")]
|
||||
|
||||
o = []
|
||||
o.append("<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\">")
|
||||
o.append('<meta name="viewport" content="width=device-width, initial-scale=1">')
|
||||
o.append("<title>Where Felhom stands</title><style>")
|
||||
o.append("@page{size:A3 landscape;margin:8mm}"
|
||||
"*{margin:0;padding:0;box-sizing:border-box}"
|
||||
"body{background:#0b1220;color:#e6edf7;font-family:-apple-system,BlinkMacSystemFont,"
|
||||
"'Segoe UI',Roboto,sans-serif;-webkit-font-smoothing:antialiased;padding:26px 20px}"
|
||||
".wrap{max-width:1600px;margin:0 auto}"
|
||||
".grid7{display:grid;grid-template-columns:repeat(7,1fr);gap:12px;align-items:start}"
|
||||
".grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;align-items:start}"
|
||||
".grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;align-items:start}"
|
||||
"h1{font-size:22px;font-weight:600;letter-spacing:-.2px}"
|
||||
"a{color:#60a5fa}"
|
||||
"@media print{body{padding:0}.grid7{grid-template-columns:repeat(7,1fr)}}")
|
||||
o.append("</style></head><body><div class=\"wrap\">")
|
||||
|
||||
o.append("<h1>Where Felhom stands</h1>")
|
||||
o.append('<div style="color:#8fa0b8;font-size:12.5px;margin:7px 0 4px;max-width:1100px;line-height:1.5">'
|
||||
'What we built, what happens when things go wrong, and what is still missing. '
|
||||
'<b style="color:#cbd7e8">Generated from '
|
||||
'<code>where-felhom-stands.yaml</code></b>, which cites the capability map, register row or '
|
||||
'evidence document behind every claim — and which is checked by '
|
||||
'<code>scripts/check_stands.py</code>.</div>')
|
||||
o.append('<div style="color:#6b7a91;font-size:11.5px;margin-bottom:16px">Verified %s against '
|
||||
'felhom-agent <code>28ba8593b8</code>, felhom-controller <code>c732fe1283</code>, hub '
|
||||
'<code>56f8aa611c</code>. <b style="color:#fbbf24">%d status(es) moved in that pass</b> — '
|
||||
'each is marked on the page.</div>' % (meta.get("date", "?"), len(changed)))
|
||||
|
||||
leg = ['<div style="display:flex;gap:22px;flex-wrap:wrap;background:#121b2c;border:1px solid #1f2c44;'
|
||||
'border-radius:6px;padding:11px 14px;margin-bottom:18px">']
|
||||
for k, name, desc in LEGEND:
|
||||
leg.append('<div style="display:flex;gap:8px;align-items:flex-start">%s'
|
||||
'<span style="font-size:11.5px"><b>%s</b> <span style="color:#7c8aa3">(%d)</span>'
|
||||
'<span style="display:block;color:#6b7a91;font-size:10.5px">%s</span></span></div>'
|
||||
% (dot(k), name, counts.get(k, 0), esc(desc)))
|
||||
leg.append('<div style="display:flex;gap:8px;align-items:flex-start"><span style="font-size:11.5px">'
|
||||
'<b style="color:#7dd3fc">needs-hardware</b><span style="display:block;color:#6b7a91;'
|
||||
'font-size:10.5px">only a running box could settle it</span></span></div>')
|
||||
leg.append("</div>")
|
||||
o.append("".join(leg))
|
||||
|
||||
o.append('<div style="font-size:15px;font-weight:600;margin:0 0 9px">The journey</div>')
|
||||
o.append('<div style="color:#6b7a91;font-size:11.5px;margin-bottom:10px">Seven stages, left to '
|
||||
'right, as they happen to a person.</div>')
|
||||
o.append('<div class="grid7">')
|
||||
for n in range(1, 8):
|
||||
inner = "".join(claim_html(c) for c in claims
|
||||
if c.get("band") == "journey" and c.get("stage") == str(n))
|
||||
o.append(panel('<span style="color:#4b5b76;font-family:ui-monospace,monospace">%d</span> %s'
|
||||
% (n, esc(STAGES[n])), inner))
|
||||
o.append("</div>")
|
||||
|
||||
fails = [c for c in claims if c.get("band") == "failures"]
|
||||
o.append('<div style="font-size:15px;font-weight:600;margin:22px 0 9px">When things go wrong</div>')
|
||||
o.append('<div style="color:#6b7a91;font-size:11.5px;margin-bottom:10px">%d situations, and what '
|
||||
'actually happens in each.</div>' % len(fails))
|
||||
o.append('<div class="grid3">')
|
||||
for c in fails:
|
||||
o.append(panel(esc(c.get("title", "").split(" — ")[0])[:74], claim_html(c)))
|
||||
o.append("</div>")
|
||||
|
||||
o.append('<div style="margin-top:26px;padding-top:12px;border-top:1px solid #1f2c44;color:#4b5b76;'
|
||||
'font-size:10.5px;line-height:1.6">'
|
||||
'Generated by <code>scripts/render_stands.py</code> from <code>where-felhom-stands.yaml</code>. '
|
||||
'Do not hand-edit this file — edit the data and regenerate. '
|
||||
'The 2026-08-09 React bundle is kept as '
|
||||
'<code>where-felhom-stands-2026-08-09-snapshot.html</code> and is not maintained.'
|
||||
'</div>')
|
||||
o.append("</div></body></html>")
|
||||
|
||||
open(OUT, "w", encoding="utf-8").write("\n".join(o))
|
||||
print("wrote %s (%d bytes) — %d claims, %d changed"
|
||||
% (os.path.relpath(OUT, ROOT), os.path.getsize(OUT), len(claims), len(changed)))
|
||||
print(" statuses: " + ", ".join("%s=%d" % kv for kv in sorted(counts.items())))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user