310 lines
15 KiB
Python
310 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
"""Trace collector for the cold run «door to file».
|
|
|
|
It reads the three places a run leaves a trace and prints them as facts an outsider can check:
|
|
the platform's Postgres, the engine's own disk (project DB + sidecars + the frame stream), and the
|
|
HTTP surface plus the downloaded bytes.
|
|
|
|
Two rules it follows everywhere, because both were paid for:
|
|
|
|
* every zero is printed next to a CONTROL number, so «nothing happened» and «nothing was read» are
|
|
told apart on the page rather than in the reader's head;
|
|
* the three traces are NOT independent — the platform's rows are materialised from the engine's
|
|
event stream and its text comes from the same `tmctl` verbs — so the report says so rather than
|
|
presenting agreement between them as corroboration.
|
|
|
|
Usage: collect.py --stand DIR --dsn DSN [--book ID] [--run ID] [--label NAME] [--json OUT]
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import sqlite3
|
|
import subprocess
|
|
import sys
|
|
from collections import Counter
|
|
|
|
MICRO = 1_000_000
|
|
|
|
|
|
def psql(dsn, sql):
|
|
"""One query, rows as lists of strings. Separator is a tab; psql -A -t gives raw fields."""
|
|
out = subprocess.run(
|
|
[os.path.expanduser("~/.local/pgsql/bin/psql"), dsn, "-At", "-F", "\t", "-c", sql],
|
|
capture_output=True, text=True)
|
|
if out.returncode != 0:
|
|
raise RuntimeError(f"psql failed: {out.stderr.strip()}\nSQL: {sql}")
|
|
return [line.split("\t") for line in out.stdout.strip().splitlines() if line != ""]
|
|
|
|
|
|
def one(dsn, sql, default="0"):
|
|
rows = psql(dsn, sql)
|
|
return rows[0][0] if rows and rows[0] and rows[0][0] != "" else default
|
|
|
|
|
|
def section(title):
|
|
print()
|
|
print("=" * 78)
|
|
print(title)
|
|
print("=" * 78)
|
|
|
|
|
|
def stamp_of(binary):
|
|
"""The VCS stamp of a Go binary, as three answers: revision, modified, and how many vcs.* lines
|
|
were there at all. The third is the one that matters: a binary built in a linked worktree
|
|
carries NONE, and a gate that only refuses `vcs.modified=true` passes it every time."""
|
|
out = subprocess.run(["go", "version", "-m", binary], capture_output=True, text=True).stdout
|
|
lines = [l.strip() for l in out.splitlines()]
|
|
vcs = [l for l in lines if "\tvcs." in l or l.startswith("build\tvcs.")]
|
|
rev = mod = ""
|
|
for l in vcs:
|
|
if "vcs.revision=" in l:
|
|
rev = l.split("vcs.revision=")[1].strip()
|
|
if "vcs.modified=" in l:
|
|
mod = l.split("vcs.modified=")[1].strip()
|
|
return {"binary": binary, "revision": rev, "modified": mod,
|
|
"vcs_lines": len(vcs), "build_lines": len(lines)}
|
|
|
|
|
|
def freeze_gate(stand, sha):
|
|
section("A. FREEZE GATE — every binary, three conditions, not one")
|
|
ok = True
|
|
for name in ("tmctl", "tmplatformd", "tmplatformctl"):
|
|
path = os.path.join(stand, "bin", name)
|
|
if not os.path.exists(path):
|
|
print(f" {name}: ABSENT at {path}")
|
|
ok = False
|
|
continue
|
|
s = stamp_of(path)
|
|
verdict = []
|
|
if s["vcs_lines"] == 0:
|
|
verdict.append("NO vcs.* LINES AT ALL — the gate would be vacuous")
|
|
if s["revision"] != sha:
|
|
verdict.append(f"revision is not the frozen one ({sha[:12]})")
|
|
if s["modified"] != "false":
|
|
verdict.append(f"vcs.modified={s['modified']!r}")
|
|
print(f" {name}: revision={s['revision'][:12] or '(none)'} modified={s['modified'] or '(none)'} "
|
|
f"vcs_lines={s['vcs_lines']} (control: build lines {s['build_lines']}) "
|
|
f"→ {'ok' if not verdict else '⛔ ' + '; '.join(verdict)}")
|
|
ok = ok and not verdict
|
|
print(f" VERDICT: {'all three binaries are from the freeze' if ok else '⛔ NOT ALL BINARIES ARE FROM THE FREEZE'}")
|
|
return ok
|
|
|
|
|
|
def platform_trace(dsn, book, run):
|
|
section("B. PLATFORM (Postgres) — the rows, each zero beside its control")
|
|
tables = ["users", "books", "chapters", "units", "runs", "run_attempts", "reservations",
|
|
"credit_ledger", "book_events", "exports", "bank_terms"]
|
|
for t in tables:
|
|
n = one(dsn, f"select count(*) from {t}")
|
|
print(f" {t}: {n}")
|
|
if book:
|
|
print(f"\n -- this book ({book}) --")
|
|
print(" books row:", psql(dsn, f"""
|
|
select status, chapter_count, source_chars, expected_micro_usd, book_once_micro_usd,
|
|
step_max_micro_usd, ordered_at is not null
|
|
from books where id = '{book}'"""))
|
|
units = psql(dsn, f"""
|
|
select u.state, count(*) from units u
|
|
join chapters c on c.id = u.chapter_id
|
|
where c.book_id = '{book}' group by 1 order by 1""")
|
|
total = one(dsn, f"select count(*) from units u join chapters c on c.id=u.chapter_id where c.book_id='{book}'")
|
|
print(f" units by state: {units} (control: units of this book = {total})")
|
|
ev = psql(dsn, f"select event, count(*) from book_events where book_id='{book}' group by 1 order by 2 desc")
|
|
evtotal = one(dsn, f"select count(*) from book_events where book_id='{book}'")
|
|
print(f" book_events by event: {ev} (control: events of this book = {evtotal})")
|
|
bank_all = one(dsn, f"select count(*) from bank_terms where book_id='{book}'")
|
|
bank_ok = one(dsn, f"select count(*) from bank_terms where book_id='{book}' and status='approved'")
|
|
allbooks = one(dsn, "select count(*) from bank_terms")
|
|
print(f" bank_terms: {bank_all} rows, {bank_ok} approved (control: bank_terms of ALL books = {allbooks})")
|
|
ex = psql(dsn, f"""select id, format, state, complete, size_bytes, path
|
|
from exports where book_id='{book}' order by requested_at""")
|
|
print(f" exports: {ex or 'none'} (control: exports of ALL books = {one(dsn, 'select count(*) from exports')})")
|
|
if run:
|
|
print(f"\n -- this run ({run}) --")
|
|
print(" runs row:", psql(dsn, f"""
|
|
select status, verify_bank, bond_funded, coalesce(paused_reason,''),
|
|
coalesce(failure_reason,''), ceiling_chapters, finished_at is not null
|
|
from runs where id='{run}'"""))
|
|
att = psql(dsn, f"""
|
|
select attempt_no, exit_code, exit_result, last_seq, spend_micro_usd,
|
|
ceiling_arg_micro_usd, engine_binary
|
|
from run_attempts where run_id='{run}' order by attempt_no""")
|
|
print(f" run_attempts ({len(att)}): ")
|
|
for a in att:
|
|
print(" ", a)
|
|
print(" ⚠ attempts, not runs: a resume after a bank stop is a SECOND attempt under the same run id")
|
|
|
|
|
|
def money(dsn, book, run, projectdb):
|
|
section("C. MONEY — micro-USD, as a CHECK between paths and never as an identity")
|
|
led = psql(dsn, "select kind, count(*), coalesce(sum(amount_micro_usd),0) from credit_ledger group by 1 order by 1")
|
|
print(f" credit_ledger by kind: {led}")
|
|
grants = int(one(dsn, "select coalesce(sum(amount_micro_usd),0) from credit_ledger where kind='grant'"))
|
|
settle = int(one(dsn, "select coalesce(sum(amount_micro_usd),0) from credit_ledger where kind='settlement'"))
|
|
balance = int(one(dsn, "select coalesce(sum(amount_micro_usd),0) from credit_ledger"))
|
|
openres = one(dsn, "select count(*) from reservations where state='open'")
|
|
allres = one(dsn, "select count(*) from reservations")
|
|
print(f" grants: {grants:>12} µUSD (${grants/MICRO:.6f})")
|
|
print(f" settlements: {settle:>12} µUSD (${settle/MICRO:.6f})")
|
|
print(f" balance: {balance:>12} µUSD (${balance/MICRO:.6f}) = sum of ALL ledger rows")
|
|
print(f" open reservations: {openres} (control: reservations of every state = {allres})")
|
|
spend_att = one(dsn, f"select coalesce(sum(spend_micro_usd),0) from run_attempts where run_id='{run}'") if run else "0"
|
|
print(f" Σ run_attempts.spend_micro_usd for this run: {spend_att}")
|
|
if projectdb and os.path.exists(projectdb):
|
|
rows, total, hits, models, bad = request_log(projectdb)
|
|
print(f" engine request_log: {rows} rows, Σ cost_usd = ${total:.6f} → {round(total*MICRO)} µUSD")
|
|
print(f" tm_hit=1 (answer taken from a checkpoint, bought nothing): {hits} (control: rows = {rows})")
|
|
print(f" distinct model_actual: {models}")
|
|
print(f" rows with err or finish_reason != stop: {len(bad)} (control: rows = {rows})")
|
|
for b in bad:
|
|
print(" ", b)
|
|
print(f" ⚠ request_log is TELEMETRY, not the source of money (the engine's money lives in spend/checkpoints);")
|
|
print(f" a redrive deletes checkpoints, so a difference here is a named class, not an error to hide.")
|
|
sp = engine_spend(projectdb)
|
|
print(f" engine spend table: {sp}")
|
|
else:
|
|
print(f" engine request_log: NOT READ — no project db at {projectdb}")
|
|
|
|
|
|
def request_log(db):
|
|
c = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
|
|
rows = c.execute("select count(*) from request_log").fetchone()[0]
|
|
total = c.execute("select coalesce(sum(cost_usd),0) from request_log").fetchone()[0]
|
|
hits = c.execute("select count(*) from request_log where tm_hit=1").fetchone()[0]
|
|
models = [r[0] for r in c.execute("select distinct model_actual from request_log order by 1")]
|
|
bad = c.execute("""select id, stage, role, model_actual, finish_reason, err
|
|
from request_log
|
|
where coalesce(err,'') != '' or coalesce(finish_reason,'') not in ('stop','')""").fetchall()
|
|
return rows, total, hits, models, bad
|
|
|
|
|
|
def engine_spend(db):
|
|
c = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
|
|
return c.execute("select book_id, date, committed_usd, reserved_usd from spend").fetchall()
|
|
|
|
|
|
def engine_disk(bookdir):
|
|
section("D. ENGINE DISK — frames, sidecars, checkpoints")
|
|
if not os.path.isdir(bookdir):
|
|
print(f" no book directory at {bookdir}")
|
|
return
|
|
files = sorted(os.listdir(bookdir))
|
|
print(f" book directory holds {len(files)} entries: {files}")
|
|
ev = os.path.join(bookdir, "events.jsonl")
|
|
if os.path.exists(ev):
|
|
kinds = Counter()
|
|
n = 0
|
|
with open(ev, encoding="utf-8") as f:
|
|
for line in f:
|
|
n += 1
|
|
try:
|
|
kinds[json.loads(line).get("type", "?")] += 1
|
|
except json.JSONDecodeError:
|
|
kinds["<unparsable>"] += 1
|
|
print(f" events.jsonl: {n} frames, by type: {dict(kinds)}")
|
|
else:
|
|
print(f" events.jsonl: ABSENT (control: {len(files)} files were listed in this directory)")
|
|
for name in sorted(f for f in files if ".bank" in f or "signature" in f or "mined" in f):
|
|
p = os.path.join(bookdir, name)
|
|
print(f" sidecar {name}: {os.path.getsize(p)} bytes sha256={sha256(p)}")
|
|
db = os.path.join(bookdir, "project.db")
|
|
if os.path.exists(db):
|
|
c = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
|
|
for t in ("checkpoints", "chunk_status", "glossary", "bank_stop_presented", "snapshots"):
|
|
try:
|
|
print(f" {t}: {c.execute(f'select count(*) from {t}').fetchone()[0]} rows")
|
|
except sqlite3.Error as e:
|
|
print(f" {t}: unreadable ({e})")
|
|
|
|
|
|
def sha256(path):
|
|
import hashlib
|
|
h = hashlib.sha256()
|
|
with open(path, "rb") as f:
|
|
for chunk in iter(lambda: f.read(1 << 20), b""):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
|
|
|
|
def build_report(stand, bookdir, out):
|
|
section("E. BUILD REPORT — the seven counters the API does NOT carry")
|
|
cfg = os.path.join(bookdir, "book.yaml")
|
|
if not os.path.exists(cfg):
|
|
print(f" no book.yaml at {cfg}")
|
|
return
|
|
# --out names the FILE, not a directory, and an explicit path is never replaced — so each call
|
|
# writes its own copy rather than asking the engine to overwrite one.
|
|
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
# The verb first, its flags after, and no --json: the report is JSON regardless (the flag is
|
|
# refused by name). Order and flags are the platform's own BuildArgs (internal/runner/build.go).
|
|
cmd = [os.path.join(stand, "bin", "tmctl"), "build", "--config", cfg,
|
|
"--format", "txt", "--out", out, "--partial"]
|
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
|
print(f" $ {' '.join(cmd)}")
|
|
print(f" exit {res.returncode}")
|
|
# The report is the WHOLE of stdout, pretty-printed over many lines: a per-line scan finds
|
|
# nothing and reports "no document", which looks exactly like a build that said nothing.
|
|
doc = None
|
|
try:
|
|
doc = json.loads(res.stdout)
|
|
except json.JSONDecodeError:
|
|
start = res.stdout.find("{")
|
|
if start >= 0:
|
|
try:
|
|
doc = json.loads(res.stdout[start:])
|
|
except json.JSONDecodeError:
|
|
doc = None
|
|
if doc is None:
|
|
print(" stdout carried no report document:")
|
|
print(" ", res.stdout.strip()[:600])
|
|
print(" stderr:", res.stderr.strip()[:600])
|
|
return
|
|
keys = ["build_version", "total_units", "pending_units", "withheld_units", "incomplete_units",
|
|
"stale_units", "stale_unknown", "ghost_rows", "config_drift", "complete"]
|
|
for k in keys:
|
|
print(f" {k}: {doc.get(k)}")
|
|
print(f" removed_files: {doc.get('removed_files')} stale_copies: {doc.get('stale_copies')}")
|
|
clean = (doc.get("complete") is True and doc.get("stale_unknown") is False
|
|
and all(doc.get(k, 0) == 0 for k in
|
|
("pending_units", "withheld_units", "incomplete_units", "stale_units", "ghost_rows")))
|
|
print(f" VERDICT: {'a WHOLE book — all five counters zero, stale_unknown false, complete true' if clean else '⛔ NOT whole by this report'}")
|
|
print(" ⚠ stale_units == 0 proves nothing on its own: under config drift it is mechanically zero,")
|
|
print(" which is why stale_unknown is printed beside it rather than folded into the same count.")
|
|
return doc
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--stand", required=True)
|
|
ap.add_argument("--dsn", required=True)
|
|
ap.add_argument("--book", default="")
|
|
ap.add_argument("--run", default="")
|
|
ap.add_argument("--sha", default=os.environ.get("FREEZE_SHA", ""))
|
|
ap.add_argument("--label", default="collect")
|
|
ap.add_argument("--skip-build", action="store_true")
|
|
a = ap.parse_args()
|
|
|
|
print(f"COLD RUN «DOOR TO FILE» — trace bundle «{a.label}»")
|
|
print(f"stand={a.stand} book={a.book or '(none)'} run={a.run or '(none)'}")
|
|
if a.sha:
|
|
freeze_gate(a.stand, a.sha)
|
|
bookdir = os.path.join(a.stand, "stand", "books", a.book) if a.book else ""
|
|
platform_trace(a.dsn, a.book, a.run)
|
|
money(a.dsn, a.book, a.run, os.path.join(bookdir, "project.db") if bookdir else "")
|
|
if bookdir:
|
|
engine_disk(bookdir)
|
|
if not a.skip_build:
|
|
import time as _t
|
|
build_report(a.stand, bookdir, os.path.join(
|
|
a.stand, "evidence", "builds", f"{a.label}-{_t.strftime('%Y%m%dT%H%M%S')}.txt"))
|
|
section("F. WHAT THIS BUNDLE DOES NOT PROVE")
|
|
print(" The platform's rows are MATERIALISED from the engine's event stream, and its text, its")
|
|
print(" manifest and its file come from the same `tmctl` verbs. So B, D and E agreeing is ONE")
|
|
print(" database agreeing with itself, not three witnesses. Independent of them are only the")
|
|
print(" bytes downloaded over HTTP and their sha256. There is no provider-side billing here.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|