hub v0.105.0: the third name, a machine told to be quiet, and a guard for the hub's own words
gates / gates (push) Successful in 17s
gates / gates (push) Successful in 17s
Hub only. No controller change, no agent change, no wire change — nothing to bake. demo-hp untouched: the operator is re-deploying it this evening. R-323 — the five-word phrase is „Tulajdonosi jelmondat". It was „Visszaállító jelszó": one word from the name retired last week, and false besides — it restores nothing, it proves the account owns the box being bound. Five sites, all in the hub; felhom-controller and felhom-agent carry the name nowhere, so no halt and no bake. Both suggested names were rejected with reasons: „Fiókjelszó" would collide with the dashboard login (a DIFFERENT real secret), and „Összekötési jelszó" would leave the two factors on this page separated only by kód-versus-jelszó — the exact shape being removed, since the other factor is the „Párosító kód". The chosen name differs on both axes, stem and noun. Naming only; the acceptance pin drives the real handler. R-324 — the hub's customer copy is under a guard for the first time. Retired names banned across all 95 hub files; retrieval stems registered in four declared customer surfaces. The selftest found a defect in its own instrument on the first run. One shared vocabulary in scripts/, drift-checked into the controller gate rather than copied (R-325 removes the scaffold). R-321 — a machine we told to be quiet is no longer reported as dead, and it was two doors, not one: because the state is RECORDED rather than deleted, the morning deadline check can skip it too. A deleted state returns "", which is not "down" — R-195's shape returning through a second door. The clock runs from the report the hub can see, so re-enabling starts it there and emits no recovery for an outage that never happened. Three red-proofs; the one that matters showed a genuinely dead machine sitting at "disabled" when the suppression was made unconditional. R-326 — "which claims are unproven" is answerable by a command now. The nine I have been repeating was the count of claims the 9 August pass DOWNGRADED, not the count of unproven ones. The real figures: 55 claims, 23 walked, 32 not — and only 6 of those 32 cite evidence. Its first run found a stale claim (R-327).
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""customer_copy_vocab — THE ONE list of customer-copy vocabulary, shared by both repos.
|
||||
|
||||
WHY THIS FILE EXISTS AT ALL. Two guards need the same words: the controller's
|
||||
`retrieval_promise_gate.py` (which scans the box's own screens) and this repo's `hub_copy_gate.py`
|
||||
(which scans the hub's e-mails and binding pages). Two copies of a word list is not a theoretical
|
||||
risk here — it is the R-299 defect exactly: a guard asserted one inflection of a Hungarian verb and
|
||||
the plural, one paragraph above, walked straight past it. A second list drifts the same way.
|
||||
|
||||
So the list lives HERE, in `felhom.eu/scripts/`, which is already the established home for gates both
|
||||
repos consume without copying (`reuse_refs_check.py`, `instructions_gate.py` — the controller's own
|
||||
`controller_gates.py` runs them from this directory and FAILS when the sibling clone is absent, which
|
||||
is the fail-CLOSED shape a shared gate needs).
|
||||
|
||||
⚠ ADOPTION IS HALF DONE, AND SAYING SO IS THE POINT. `hub_copy_gate.py` imports this module.
|
||||
`felhom-controller/controller/scripts/retrieval_promise_gate.py` still carries its own `STEMS`
|
||||
literal, because the session that wrote this module was under a hard end-state requirement to leave
|
||||
felhom-controller untouched (its target box was being re-deployed the same evening). Until that gate
|
||||
imports this file, `hub_copy_gate.py` DRIFT-CHECKS it: it reads the controller gate's `STEMS` and
|
||||
fails if the two lists disagree. That gives single-source semantics tonight without a cross-repo
|
||||
edit — but it is a scaffold, not the destination. Register row: R-325.
|
||||
"""
|
||||
|
||||
# ── The retired names ────────────────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# A name here is BANNED from customer-facing copy outright. Unlike the stems below, these are not
|
||||
# claims that might be true in context — they are names for a secret that a different secret now
|
||||
# owns, and a customer meeting one is being told two things have the same name.
|
||||
#
|
||||
# Each entry is a SUBSTRING, deliberately shorn of its leading capital and its ending, so a
|
||||
# possessive or accusative form cannot slip past a check written against the nominative:
|
||||
# „visszaállító jelszavadat" must convict as surely as „Visszaállító jelszó". That is the R-299
|
||||
# lesson applied to nouns instead of verbs.
|
||||
RETIRED_NAMES = {
|
||||
"isszaállító kód": (
|
||||
"R-295 (2026-08-10 box side, 2026-08-13 hub side). The THREE-word code that takes control of "
|
||||
"the dashboard is „Beállító kód”. „Visszaállító kód” was a near-homograph of the escrow "
|
||||
"„Helyreállítási kód” and the collision cost a real code."),
|
||||
"isszaállító jelsz": (
|
||||
"R-323 (2026-08-13). The FIVE-word phrase that proves the account owns the box being bound is "
|
||||
"„Tulajdonosi jelmondat”. „Visszaállító jelszó” was one word from the name retired above, and "
|
||||
"it was false besides — the phrase restores nothing."),
|
||||
}
|
||||
|
||||
# ── The retrieval-promise stems ──────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# These are NOT banned. Each carries the claim "your old backups can be got back", which is sometimes
|
||||
# TRUE and must then be sayable. An occurrence must be REGISTERED with a reason in the consuming
|
||||
# gate's allowlist; an unregistered one fails.
|
||||
#
|
||||
# The docstring of the controller gate is the long version and is worth reading before adding a stem:
|
||||
# five instances of this claim surfaced ONE AT A TIME, and the obvious guard (ban the sentence) was
|
||||
# tried twice and failed twice.
|
||||
RETRIEVAL_STEMS = ["visszaállíthat", "visszaszerezhet", "visszahozhat", "visszanyit"]
|
||||
|
||||
# ── The three secrets, so a reviewer can check a name against the THING ───────────────────────────
|
||||
#
|
||||
# Not consumed by any check — it is here because every argument about these names has had to be
|
||||
# reconstructed from register rows, and the table is the argument.
|
||||
THE_THREE_SECRETS = [
|
||||
("three words", "takes control of the dashboard", "Beállító kód"),
|
||||
("ten words", "opens the sealed off-site backups", "Helyreállítási kód"),
|
||||
("five words", "proves the account owns the box being bound", "Tulajdonosi jelmondat"),
|
||||
]
|
||||
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""hub_copy_gate — the hub's customer-facing copy comes under a guard (R-324).
|
||||
|
||||
THE SCOPE GAP THIS CLOSES. `retrieval_promise_gate.py` lives in `felhom-controller` and scans that
|
||||
repo only. It was extended to Go string literals on 2026-08-12 (R-311) on the express ground that the
|
||||
recovery screen's messages are Go strings in a handler and "none of it had ever been scanned" — and
|
||||
the identical sentence was true one repo over the whole time. **The hub composes every customer
|
||||
e-mail and renders the binding pages: the first sentences a customer ever reads, before they have
|
||||
seen any box screen.** Nothing looked at them.
|
||||
|
||||
A hand scan on 2026-08-13 returned zero retrieval claims, so this was a SCOPE gap rather than a live
|
||||
defect — which is the moment to close it, before someone writes the first hub-side promise assuming
|
||||
the guard has them covered.
|
||||
|
||||
TWO CHECKS, and the difference is deliberate:
|
||||
|
||||
1. RETIRED NAMES — banned outright, scanned across the WHOLE hub, not just declared surfaces. A
|
||||
name for a secret that a different secret now owns is never correct anywhere, so there is no
|
||||
allowlist and no "unless". Comments are stripped: prose explaining a rename is not the rename
|
||||
coming back, and the register rows that record these decisions quote the retired names by
|
||||
necessity.
|
||||
|
||||
2. RETRIEVAL STEMS — registered, not banned, in the DECLARED customer-facing surfaces. The claim
|
||||
they carry is sometimes true and must stay sayable; what must not happen is a new one appearing
|
||||
where nobody was looking. Same contract as the controller gate: an occurrence is allowlisted
|
||||
WITH A REASON, and a stale allowlist entry is also a failure.
|
||||
|
||||
BOTH lists come from `customer_copy_vocab.py` — see that file for why they are not literals here.
|
||||
|
||||
DRIFT CHECK, and it is load-bearing. The controller gate still owns its own `STEMS` literal (the
|
||||
session that wrote this could not touch felhom-controller). This gate reads that literal and FAILS if
|
||||
it disagrees with the shared list, so the two cannot silently diverge in the meantime. An ABSENT
|
||||
sibling clone is INCONCLUSIVE (exit 2), never a pass — the G-1 lesson: a gate that skips when its
|
||||
sibling is missing runs in neither home.
|
||||
|
||||
Run: python3 scripts/hub_copy_gate.py (from the felhom.eu repo root)
|
||||
python3 scripts/hub_copy_gate.py --selftest (plant → convict → remove → pass)
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from customer_copy_vocab import RETIRED_NAMES, RETRIEVAL_STEMS # noqa: E402
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_REPO = os.path.dirname(_HERE)
|
||||
_HUB = os.path.join(_REPO, "hub")
|
||||
|
||||
# The hub files that carry HUNGARIAN CUSTOMER-FACING text — the surfaces where a retrieval claim
|
||||
# could be made to a customer. Enumerated by scanning every non-test .go/.html under hub/internal for
|
||||
# accented Hungarian outside comments, then keeping the ones a CUSTOMER (not the operator) reads.
|
||||
#
|
||||
# A declared file that is missing is a FAILURE, never a skip: the controller gate learned that when a
|
||||
# renamed handler would have silently emptied its own scope.
|
||||
CUSTOMER_SURFACES = [
|
||||
os.path.join("hub", "internal", "notify", "templates.go"), # every customer e-mail + event copy
|
||||
os.path.join("hub", "internal", "web", "selfbind.go"), # the binding pages
|
||||
os.path.join("hub", "internal", "api", "handler.go"), # customer-visible event messages
|
||||
os.path.join("hub", "internal", "notify", "dispatcher.go"), # the customer channel's own wording
|
||||
]
|
||||
|
||||
# (file basename, substring that identifies the occurrence) -> why it is allowed.
|
||||
# Empty today, and that is a measurement rather than an oversight: the hub makes no retrieval promise.
|
||||
ALLOWLIST = {}
|
||||
|
||||
GO_COMMENT = re.compile(r"//[^\n]*|/\*.*?\*/", re.S)
|
||||
TPL_COMMENT = re.compile(r"\{\{/\*.*?\*/\}\}", re.S)
|
||||
HTML_COMMENT = re.compile(r"<!--.*?-->", re.S)
|
||||
|
||||
|
||||
def strip_comments(text, path):
|
||||
"""Remove what never reaches a customer.
|
||||
|
||||
Go `//` and `/* */`, Go-template `{{/* */}}`, and — unlike the controller gate — HTML `<!-- -->`
|
||||
too. The controller gate deliberately SCANS HTML comments because its templates ship to a browser
|
||||
where View Source is one keystroke. The hub's Hungarian lives in Go string literals that happen
|
||||
to contain HTML, and its operator templates are not customer copy, so an HTML comment here is a
|
||||
note to the next maintainer. Stated because it is a real difference between the two gates.
|
||||
"""
|
||||
text = TPL_COMMENT.sub("", text)
|
||||
text = HTML_COMMENT.sub("", text)
|
||||
if path.endswith(".go"):
|
||||
text = GO_COMMENT.sub("", text)
|
||||
return text
|
||||
|
||||
|
||||
def _iter_hub_sources():
|
||||
for root, _dirs, files in os.walk(os.path.join(_REPO, "hub", "internal")):
|
||||
for f in sorted(files):
|
||||
if f.endswith("_test.go") or not (f.endswith(".go") or f.endswith(".html")):
|
||||
continue
|
||||
yield os.path.join(root, f)
|
||||
|
||||
|
||||
def scan_retired(extra_text=None):
|
||||
"""Every retired name, everywhere in the hub. Returns a list of convictions."""
|
||||
convictions = []
|
||||
sources = [(p, open(p, encoding="utf-8").read()) for p in _iter_hub_sources()]
|
||||
if extra_text is not None:
|
||||
# The synthetic path MUST end in .go: strip_comments keys comment-stripping off the
|
||||
# extension, so a selftest source named anything else would be scanned WITH its comments —
|
||||
# which is how the first run of this selftest convicted its own step-3 control. The bug was
|
||||
# in the instrument, and the control is what found it.
|
||||
sources.append((os.path.join(_REPO, "hub", "internal", "selftest_planted.go"), extra_text))
|
||||
for path, raw in sources:
|
||||
text = strip_comments(raw, path)
|
||||
for name in RETIRED_NAMES:
|
||||
for m in re.finditer(re.escape(name), text):
|
||||
line = text[: m.start()].count("\n") + 1
|
||||
ctx = " ".join(text[max(0, m.start() - 70): m.end() + 70].split())[:150]
|
||||
convictions.append((os.path.relpath(path, _REPO), line, name, ctx))
|
||||
return convictions
|
||||
|
||||
|
||||
def scan_stems():
|
||||
"""Retrieval stems in the declared customer surfaces. Returns (convictions, seen_keys)."""
|
||||
convictions, seen = [], set()
|
||||
for rel in CUSTOMER_SURFACES:
|
||||
path = os.path.join(_REPO, rel)
|
||||
if not os.path.exists(path):
|
||||
raise SystemExit("hub-copy gate: declared customer surface is MISSING: %s" % rel)
|
||||
name = os.path.basename(path)
|
||||
text = strip_comments(open(path, encoding="utf-8").read(), path)
|
||||
for stem in RETRIEVAL_STEMS:
|
||||
for m in re.finditer(re.escape(stem) + r"[a-záéíóöőúüű]*", text):
|
||||
hit = None
|
||||
for k in (k for k in ALLOWLIST if k[0] == name):
|
||||
for om in re.finditer(re.escape(k[1]), text):
|
||||
if om.start() <= m.start() and m.end() <= om.end():
|
||||
hit = k
|
||||
break
|
||||
if hit:
|
||||
break
|
||||
if hit:
|
||||
seen.add(hit)
|
||||
else:
|
||||
line = text[: m.start()].count("\n") + 1
|
||||
ctx = " ".join(text[max(0, m.start() - 90): m.end() + 90].split())[:170]
|
||||
convictions.append((name, line, m.group(0), ctx))
|
||||
return convictions, seen
|
||||
|
||||
|
||||
def check_drift():
|
||||
"""The controller gate's STEMS must equal the shared list. Returns (status, message).
|
||||
|
||||
status: "ok" | "drift" | "inconclusive"
|
||||
"""
|
||||
sibling = os.path.join(os.path.dirname(_REPO), "felhom-controller",
|
||||
"controller", "scripts", "retrieval_promise_gate.py")
|
||||
if not os.path.exists(sibling):
|
||||
return "inconclusive", ("the felhom-controller clone is absent, so the shared stem list "
|
||||
"could not be compared against the gate that also uses it (%s)" % sibling)
|
||||
text = open(sibling, encoding="utf-8").read()
|
||||
m = re.search(r"^STEMS\s*=\s*\[(.*?)\]", text, re.M | re.S)
|
||||
if not m:
|
||||
return "drift", "could not find a STEMS list in the controller gate — its shape changed"
|
||||
theirs = re.findall(r"[\"']([^\"']+)[\"']", m.group(1))
|
||||
if theirs != RETRIEVAL_STEMS:
|
||||
return "drift", ("the controller gate's STEMS have diverged from customer_copy_vocab.py\n"
|
||||
" controller : %r\n shared : %r" % (theirs, RETRIEVAL_STEMS))
|
||||
return "ok", "controller gate's STEMS match the shared list (%d stem(s))" % len(theirs)
|
||||
|
||||
|
||||
def selftest():
|
||||
"""Plant → convict → remove → pass. A guard never seen catching anything proves nothing."""
|
||||
print("hub-copy gate SELFTEST")
|
||||
baseline = scan_retired()
|
||||
if baseline:
|
||||
print(" FAIL: the tree is not clean before planting — %d conviction(s)" % len(baseline))
|
||||
for c in baseline:
|
||||
print(" %s:%d [%s]" % (c[0], c[1], c[2]))
|
||||
return 1
|
||||
print(" 1. clean tree : 0 conviction(s) OK")
|
||||
|
||||
planted = 'body := "Add meg a visszaállító jelszavadat a folytatáshoz."\n'
|
||||
convicted = scan_retired(extra_text=planted)
|
||||
if len(convicted) != 1 or convicted[0][2] != "isszaállító jelsz":
|
||||
print(" 2. planted retired name : NOT CONVICTED — the guard is inert")
|
||||
print(" got: %r" % (convicted,))
|
||||
return 1
|
||||
print(" 2. planted „visszaállító jelszavadat”: CONVICTED (%s) OK" % convicted[0][2])
|
||||
|
||||
commented = '// the old name was „visszaállító jelszó" and is retired\n'
|
||||
if scan_retired(extra_text=commented):
|
||||
print(" 3. same phrase inside a COMMENT : convicted — comments must not be scanned")
|
||||
return 1
|
||||
print(" 3. same phrase inside a comment : not convicted OK")
|
||||
|
||||
if scan_retired():
|
||||
print(" 4. planting removed : still convicting — the scan is not deterministic")
|
||||
return 1
|
||||
print(" 4. planting removed : 0 conviction(s) OK")
|
||||
print("hub-copy gate selftest OK — the guard has been watched catching, ignoring and releasing")
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
if "--selftest" in sys.argv:
|
||||
return selftest()
|
||||
|
||||
retired = scan_retired()
|
||||
stems, seen = scan_stems()
|
||||
stale = [k for k in ALLOWLIST if k not in seen]
|
||||
drift_status, drift_msg = check_drift()
|
||||
|
||||
for path, line, name, ctx in retired:
|
||||
print(" %s:%d RETIRED NAME in customer copy (%s):\n …%s…" % (path, line, name, ctx))
|
||||
print(" reason it is retired: %s" % RETIRED_NAMES[name])
|
||||
for name, line, word, ctx in stems:
|
||||
print(" %s:%d unregistered retrieval claim (%s):\n …%s…" % (name, line, word, ctx))
|
||||
for k in stale:
|
||||
print(" STALE ALLOWLIST ENTRY (no longer present): %s :: %r" % (k[0], k[1]))
|
||||
|
||||
n_files = len(list(_iter_hub_sources()))
|
||||
if retired or stems or stale:
|
||||
print("\nHUB-COPY GATE FAILED: %d retired name(s), %d unregistered claim(s), %d stale."
|
||||
% (len(retired), len(stems), len(stale)))
|
||||
print("A retired name is never correct in customer copy — use the current name. If a new")
|
||||
print("retrieval claim is genuinely TRUE where it renders, make it conditional on what the")
|
||||
print("hub can actually see, then register it in ALLOWLIST with the reason.")
|
||||
return 1
|
||||
|
||||
if drift_status == "drift":
|
||||
print(" DRIFT: %s" % drift_msg)
|
||||
print("\nHUB-COPY GATE FAILED: the shared vocabulary is no longer shared.")
|
||||
return 1
|
||||
|
||||
print("hub-copy gate OK — %d hub file(s) scanned for %d retired name(s); %d customer surface(s) "
|
||||
"scanned for %d retrieval stem(s), %d registered claim(s), none unregistered"
|
||||
% (n_files, len(RETIRED_NAMES), len(CUSTOMER_SURFACES), len(RETRIEVAL_STEMS), len(ALLOWLIST)))
|
||||
if drift_status == "inconclusive":
|
||||
print(" ⚠ INCONCLUSIVE (exit 2): %s" % drift_msg)
|
||||
return 2
|
||||
print(" drift: %s" % drift_msg)
|
||||
print(" (BLIND SPOT: this checks the WORDS in the four declared customer surfaces. It cannot")
|
||||
print(" tell whether a true-looking sentence is wired to a predicate that is actually true —")
|
||||
print(" that is what render tests are for. And it does not read the operator's screens.)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -69,6 +69,9 @@ GATES = [
|
||||
("instructions", os.path.join(SCRIPTS, "instructions_gate.py"), [ROOT], True),
|
||||
("golden-currency", os.path.join(SCRIPTS, "golden_currency_gate.py"), [], True),
|
||||
("wire-contract", os.path.join(SCRIPTS, "wire_contract_gate.py"), [], True),
|
||||
# R-324 — the hub composes every customer e-mail and renders the binding pages, and until
|
||||
# 2026-08-13 no guard in either repo had ever looked at them. Fast: pure file reads.
|
||||
("hub-copy", os.path.join(SCRIPTS, "hub_copy_gate.py"), [], True),
|
||||
]
|
||||
|
||||
VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""unproven.py — "which claims are not proven?", as a question a machine can answer (R-326).
|
||||
|
||||
WHY THIS EXISTS. The operator asked on 2026-08-04 to see what is built and what is still unproven.
|
||||
The picture was built (`where-felhom-stands.yaml` → `.html`) and it is good — but the QUESTION could
|
||||
only be answered by a person reading it. On 2026-08-13 a session was asked for "the nine grey claims",
|
||||
could not determine which nine, and declined to guess. It was right to decline, and the refusal is
|
||||
the finding this closes.
|
||||
|
||||
WHERE "NINE" CAME FROM, since a wrong number that matches nothing is worse than no number. It is
|
||||
real, and it answers a DIFFERENT question: nine claims carry `verdict: downgraded` — the count the
|
||||
2026-08-09 verification pass LOWERED. The count of claims that are not walked is 32. Both are true;
|
||||
only one of them is "what is unproven".
|
||||
|
||||
This reads the dataset only. It makes no judgement, opens no evidence and contacts no machine — a
|
||||
claim's status is the capability map's business (the map changes first; the dataset follows it), and
|
||||
`check_stands.py` is the gate that keeps the citations honest. This just answers the question.
|
||||
|
||||
Run: python3 scripts/unproven.py # every claim that is not walked
|
||||
python3 scripts/unproven.py --summary # the counts only
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
DATA = os.path.join(ROOT, "documentation", "architecture", "where-felhom-stands.yaml")
|
||||
|
||||
# The status that means "done end to end on real hardware, with evidence on file". Everything else is
|
||||
# a degree of not-that, which is the whole point of the question.
|
||||
PROVEN = "walked"
|
||||
|
||||
# Widest first, so the output reads as a ladder down from nearly-there to not-started.
|
||||
ORDER = ["partial", "built", "missing"]
|
||||
|
||||
|
||||
def field(entry, name):
|
||||
m = re.search(r"^\s*%s:\s*\"?(.*?)\"?\s*$" % name, entry, re.M)
|
||||
return m.group(1) if m else ""
|
||||
|
||||
|
||||
def load():
|
||||
if not os.path.exists(DATA):
|
||||
sys.exit("unproven: the dataset is missing: %s" % DATA)
|
||||
text = io.open(DATA, encoding="utf-8").read()
|
||||
parts = re.split(r"\n - id: ", text)
|
||||
header, entries = parts[0], parts[1:]
|
||||
if not entries:
|
||||
sys.exit("unproven: no claims parsed from %s — the file's shape changed" % DATA)
|
||||
claims = []
|
||||
for e in entries:
|
||||
claims.append({
|
||||
"id": e.split("\n", 1)[0].strip(),
|
||||
"status": field(e, "status") or "(none)",
|
||||
"title": field(e, "title"),
|
||||
"band": field(e, "band"),
|
||||
"verdict": field(e, "verdict"),
|
||||
"evidence": "evidence:" in e,
|
||||
})
|
||||
return field(header, "verified_on"), claims
|
||||
|
||||
|
||||
def main():
|
||||
verified_on, claims = load()
|
||||
not_walked = [c for c in claims if c["status"] != PROVEN]
|
||||
counts = {}
|
||||
for c in claims:
|
||||
counts[c["status"]] = counts.get(c["status"], 0) + 1
|
||||
|
||||
print("where felhom stands — %d claims, verified_on %s" % (len(claims), verified_on))
|
||||
print(" %-8s %d" % (PROVEN, counts.get(PROVEN, 0)))
|
||||
for s in ORDER:
|
||||
n = counts.get(s, 0)
|
||||
with_ev = sum(1 for c in claims if c["status"] == s and c["evidence"])
|
||||
print(" %-8s %d (%d cite evidence, %d prose only)" % (s, n, with_ev, n - with_ev))
|
||||
for s in sorted(k for k in counts if k not in ORDER + [PROVEN]):
|
||||
print(" %-8s %d ⚠ status not known to this script" % (s, counts[s]))
|
||||
print(" NOT WALKED: %d of %d" % (len(not_walked), len(claims)))
|
||||
|
||||
if "--summary" in sys.argv:
|
||||
return 0
|
||||
|
||||
print()
|
||||
for s in ORDER + sorted(k for k in counts if k not in ORDER + [PROVEN]):
|
||||
rows = [c for c in not_walked if c["status"] == s]
|
||||
if not rows:
|
||||
continue
|
||||
print("%s (%d)" % (s.upper(), len(rows)))
|
||||
for c in sorted(rows, key=lambda c: c["id"]):
|
||||
ev = "evidence" if c["evidence"] else "PROSE ONLY"
|
||||
vd = (" [%s]" % c["verdict"]) if c["verdict"] else ""
|
||||
print(" %-40s %-10s %s%s" % (c["id"], c["band"], ev, vd))
|
||||
print(" %s" % c["title"][:110])
|
||||
print()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user