#!/usr/bin/env python3 """Build the verdict table, the three lines and the promotion list from the verdict records. Nothing here judges anything — it reads what each edge wrote and lays it out. `inconclusive` is carried through as itself and is never folded into `failed`. """ import glob, json, os, sys HERE = os.path.dirname(os.path.abspath(__file__)) def short(refs): if not refs: return "—" out = [] for k, v in refs.items(): v = str(v) out.append(v.rsplit("/", 1)[-1]) return ", ".join(sorted(set(out))) def load(): recs = [] for p in sorted(glob.glob(os.path.join(HERE, "apps", "*", "verdict.json"))): try: d = json.load(open(p)) d["_dir"] = os.path.basename(os.path.dirname(p)) recs.append(d) except Exception as e: print(f"skipped {p}: {e}", file=sys.stderr) return recs def main(): recs = load() proven = [r for r in recs if r["verdict"] == "proven"] failed = [r for r in recs if r["verdict"] == "failed"] inconc = [r for r in recs if r["verdict"] == "inconclusive"] print("## The verdict table\n") print("One row per edge attempted tonight. `inconclusive` means *we could not measure it*, which") print("is a different fact from *it does not work* — and only one of them is about the app.\n") print("| app | from → to | class | box verdict | seed before → after | secs | migration line seen | evidence |") print("|---|---|---|---|---|---|---|---|") for r in sorted(recs, key=lambda x: (x["verdict"] != "proven", x["app"])): mig = r.get("migration_observed") mig = "yes" if mig else ("—" if r["verdict"] != "proven" else "none printed") arrow = f"{short(r.get('from'))} → {short(r.get('to'))}" print(f"| `{r['app']}` | {arrow} | {r.get('class') or r.get('leg') or '—'} " f"| **{r['verdict']}** | {r.get('seed_read_before')} → {r.get('seed_read_after')} " f"| {r.get('duration_s')} | {mig} | `apps/{r['_dir']}/` |") print(f"\n**{len(proven)} proven · {len(failed)} failed · {len(inconc)} inconclusive " f"— out of {len(recs)} attempted.**\n") if inconc: print("### Why each inconclusive edge could not be judged\n") for r in inconc: why = "; ".join(r.get("notes") or []) or "—" print(f"- **`{r['app']}`** — {why}") print() if failed: print("### The edges that failed — the most valuable results of the night\n") for r in failed: why = "; ".join(r.get("notes") or []) or "—" print(f"- **`{r['app']}`** — final phase `{r.get('final_phase')}`, " f"hold `{r.get('hold_reason')}`, error `{r.get('update_error')}`. {why}") print() print("## The promotion list for the operator\n") print("**CC promotes nothing.** These are the real, within-a-major edges that ended `proven` on") print("the box tonight, with the data read back through the app's own front door both before and") print("after. Moving each of them on the LIVE catalog is the operator's call.\n") print("| app | the move | what it would mean for a box in the field |") print("|---|---|---|") for r in sorted(proven, key=lambda x: x["app"]): frm, to = short(r.get("from")), short(r.get("to")) mig = r.get("migration_observed") note = ("the app runs its own schema migration on the way — proven here, and the update " "takes a backup first" if mig else "no migration line printed; the app came up on the new version with its data intact") print(f"| `{r['app']}` | {frm} → {to} | {note} |") print() if __name__ == "__main__": main()