diff --git a/wizard-work/null/check.py b/wizard-work/null/check.py index c82a4ef..9773a5d 100644 --- a/wizard-work/null/check.py +++ b/wizard-work/null/check.py @@ -8,16 +8,21 @@ Exit code IS the verdict: 0 = every claim below holds, 1 = broken. What this gate measures, in order: 1. the three files exist, compile, expose the documented API; 2. craft() and render() are pure functions of their arguments -- no clock; - 3. binding: the log's own declared base commit is a real commit, is authored - by null, and its blob of this file is BYTE-IDENTICAL to the log on disk. - The commit is not required to be the newest one touching the path -- it - must be the one whose tree holds these bytes, which is exactly what the - builder writes, so a stale commit can no longer be named over fresh bytes; - 4. that commit is on the REMOTE main, verified against `git ls-remote`, not - the local clone, and the remote's tree is re-read for the three files; - 5. a child process really ran and exited 0 -- a nonce the child must echo, + 3. the log's declared commit is real, authored by null, and is an ancestor of + the commit the REMOTE names for refs/heads/main (ls-remote, not the clone); + 4. that commit's blobs of wizard.py and check.py are byte-identical to the + files on disk -- the entry names the commit that carries the work; + 5. the entry's own bytes are the blob of wizard.log.md on the remote main; + 6. render() with the log's own stamp reproduces the log byte-for-byte; + 7. a child process really ran and exited 0 -- a nonce the child must echo, so the self-run line cannot pass by returning True without spawning. +The binding is deliberately NOT "the commit whose tree holds the entry's bytes": +the entry names a SHA, so its own bytes cannot be the blob of the commit it +names -- that is a hash fixed point and no commit satisfies it. Naming the +commit that carries the work, and verifying the entry separately on the remote, +is satisfiable and still pins the entry to a real, pushed, null-authored commit. + The self-run child sets NULL_CHECK_NONCE and exits before any check is evaluated, so the gate never grades its own verdict: it proves only that this file spawns, runs to completion, and echoes. @@ -33,6 +38,7 @@ REPO = os.path.dirname(os.path.dirname(MYDIR)) LOGNAME = "wizard.log.md" NEED = ("wizard.log.md", "wizard.py", "check.py") LOGPATH = "wizard-work/null/" + LOGNAME +WORK_FILES = ("wizard-work/null/wizard.py", "wizard-work/null/check.py") PASSED, FAILED = [], [] @@ -46,8 +52,9 @@ def run(args, **kw): return subprocess.run(args, capture_output=True, text=True, **kw) -def git(*args): - return run(["git", "-C", REPO] + list(args)) +def git(*args, binary=False): + return subprocess.run(["git", "-C", REPO] + list(args), + capture_output=True, text=not binary) # --- the child branch: prove this gate spawns, runs, and echoes, then exit -- @@ -65,20 +72,6 @@ def _compiles(src, name): return False -def _name_differing_line(commit, other): - r = git("show", "%s:%s" % (commit, LOGPATH)) - if r.returncode != 0: - return - a, b = r.stdout.splitlines(), (other or "").splitlines() - for i in range(max(len(a), len(b))): - x = a[i] if i < len(a) else "" - y = b[i] if i < len(b) else "" - if x != y: - print(" differing line %d:\n committed: %s\n rendered: %s" - % (i + 1, x, y)) - return - - def _self_run(nonce): """Spawn check.py with a nonce; return (ok, output) or None if it never ran. @@ -97,65 +90,34 @@ def _self_run(nonce): def _remote_main(): """The commit the REMOTE names for refs/heads/main, or '' if unreachable.""" + try: + subprocess.run(["git", "-C", REPO, "fetch", "origin", "main", "--quiet"], + capture_output=True, timeout=180) + except Exception: # noqa: BLE001 - fetch is best effort; ls-remote still asks + pass r = run(["git", "-C", REPO, "ls-remote", "origin", "refs/heads/main"]) if r.returncode != 0 or not r.stdout.strip(): return "" return r.stdout.split()[0] -def _remote_ok(commit): - """True iff `commit` is null's, carries these files, and is on the REMOTE main. - - ls-remote asks the remote which commit refs/heads/main names; a local clone - can be stale or ahead, so the local ref proves nothing about the push. - """ - if not commit: - return False, "no base commit to inspect" - who = git("show", "-s", "--format=%an|%s", commit) - if who.returncode != 0: - return False, "commit unreadable" - author, subject = who.stdout.strip().split("|", 1) - if author != "null": - return False, "commit %s authored by %r, not null" % (commit[:8], author) - files = git("show", "--name-only", "--format=", commit).stdout.split() - need = {"wizard-work/null/" + f for f in NEED} - if not need.issubset(set(files)): - return False, "commit missing files: %s" % sorted(need - set(files)) - head = _remote_main() - if not head: - return False, "ls-remote could not reach origin/main" - anc = git("merge-base", "--is-ancestor", commit, head) - if anc.returncode != 0: - return False, "commit %s not an ancestor of origin/main (%s)" % (commit[:8], head[:8]) - # re-read the three files from the commit the remote actually names - for f in NEED: - blob = git("show", "%s:wizard-work/null/%s" % (head, f)) - if blob.returncode != 0: - return False, "origin/main lacks wizard-work/null/%s" % f - local = os.path.join(MYDIR, f) - if os.path.isfile(local): - with open(local, encoding="utf-8") as fh: - if fh.read() != blob.stdout: - return False, "%s on disk != origin/main's blob" % f - return True, "%s by null (%s) is an ancestor of origin/main %s; its tree matches the disk" % ( - commit[:8], subject, head[:8]) - - def main(): # --- 1. presence / compile / API -------------------------------------- for fn in NEED: check("artifact present: " + fn, os.path.isfile(os.path.join(MYDIR, fn))) src = open(os.path.join(MYDIR, "wizard.py"), encoding="utf-8").read() - check("wizard.py compiles", _compiles(src, "wizard.py")) + ok_compile = _compiles(src, "wizard.py") + check("wizard.py compiles", ok_compile) ns = {"__name__": "wizard_module", "__file__": os.path.join(MYDIR, "wizard.py")} - if _compiles(src, "wizard.py"): + if ok_compile: exec(compile(src, "wizard.py", "exec"), ns) check("API: craft exists", callable(ns.get("craft"))) check("API: render exists", callable(ns.get("render"))) + check("API: parse_log exists", callable(ns.get("parse_log"))) check("API: VERDICT/CHECK markers exist", "VERDICT:" in src and "CHECK:" in src) - # --- 2. craft() / render() are pure in their arguments ----------------- + # --- 2. craft() is pure in its arguments ------------------------------ try: args = ("t", "a", "L", "deadbeef", "null", "2026-10-24 00:00 UTC") v1, c1, _ = ns["craft"](*args) @@ -168,73 +130,87 @@ def main(): check("craft() deterministic in its stamp argument", False, "%s: %s" % (type(e).__name__, e)) - # --- 3. binding: the declared commit's blob IS the log on disk ---------- + # --- 3. parse the log -------------------------------------------------- log_path = os.path.join(MYDIR, LOGNAME) disk = open(log_path, encoding="utf-8").read() if os.path.exists(log_path) else "" fields = dict(ns["parse_log"](disk)) if callable(ns.get("parse_log")) else {} lane = fields.get("lane", "") area = fields.get("target area", "") operator = fields.get("operator", "null") + task = fields.get("task", "") commit = fields.get("base commit", "") stamp = fields.get("stamp", "") - rendered = None - try: - rendered = ns["render"](lane, area, lane, commit, operator, stamp) - except Exception: # noqa: BLE001 - pass - check("log declares a base commit SHA", bool(commit), repr(commit)) obj = git("cat-file", "-t", commit) if commit else None check("base commit resolves as a git object", bool(obj and obj.returncode == 0 and obj.stdout.strip() == "commit"), (obj.stdout.strip() if obj else "no sha") or (obj.stderr.strip() if obj else "")) + head = _remote_main() + check("remote main is reachable", bool(head), head or "ls-remote returned nothing") + if commit and obj and obj.returncode == 0: author = git("show", "-s", "--format=%an", commit).stdout.strip() check("declared commit is authored by null", author == "null", author) - blob = git("show", "%s:%s" % (commit, LOGPATH)) - if blob.returncode != 0: - check("log on disk == git blob of the declared commit", False, - "git show failed: " + blob.stderr.strip()[:120]) - elif disk == blob.stdout: - check("log on disk == git blob of the declared commit", True, - "%d bytes, byte-identical" % len(disk.encode())) + if head: + anc = git("merge-base", "--is-ancestor", commit, head) + check("declared commit is an ancestor of the remote main", + anc.returncode == 0, + "%s %s an ancestor of %s" % (commit[:8], + "is" if anc.returncode == 0 else "is NOT", + head[:8])) else: - check("log on disk == git blob of the declared commit", False, - "%d bytes on disk vs %d in the commit" % (len(disk.encode()), len(blob.stdout.encode()))) - _name_differing_line(commit, disk) + check("declared commit is an ancestor of the remote main", False, "no remote head") else: check("declared commit is authored by null", False, "no commit to read") - check("log on disk == git blob of the declared commit", False, "no commit to read") + check("declared commit is an ancestor of the remote main", False, "no commit to read") - check("fresh render with the log's stamp == log", - rendered is not None and rendered == disk, - "render() round-trip, byte-for-byte" if rendered == disk - else "render() unavailable or differs") + # --- 4. the named commit carries the work bytes ------------------------ + if commit and obj and obj.returncode == 0: + for p in WORK_FILES: + blob = git("show", "%s:%s" % (commit, p), binary=True) + local = os.path.join(REPO, p) + disk_b = open(local, "rb").read() if os.path.isfile(local) else b"" + if blob.returncode != 0: + check("named commit carries %s" % p, False, + "git show failed: " + blob.stderr.strip()[:100]) + else: + check("named commit carries %s" % p, blob.stdout == disk_b, + ("%d bytes, byte-identical" % len(disk_b)) if blob.stdout == disk_b + else "%d in commit vs %d on disk" % (len(blob.stdout), len(disk_b))) + else: + for p in WORK_FILES: + check("named commit carries %s" % p, False, "no commit to read") - # --- 4. shape of the log ----------------------------------------------- + # --- 5. the entry itself is on the remote ------------------------------ + if head: + rb = git("show", "%s:%s" % (head, LOGPATH)) + ok = rb.returncode == 0 and rb.stdout == disk + check("entry bytes are the blob on the remote main", ok, + ("%d bytes, byte-identical" % len(disk.encode())) if ok + else ("git show failed: " + rb.stderr.strip()[:100]) if rb.returncode != 0 + else "%d on remote vs %d on disk" % (len(rb.stdout.encode()), len(disk.encode()))) + else: + check("entry bytes are the blob on the remote main", False, "no remote head") + + # --- 6. render() round-trip with the log's own stamp ------------------- + try: + rendered = ns["render"](task, area, lane, commit, operator, stamp) + check("fresh render with the log's stamp == log", rendered == disk, + "render() round-trip, byte-for-byte" if rendered == disk else "render() differs") + except Exception as e: # noqa: BLE001 + check("fresh render with the log's stamp == log", False, + "%s: %s" % (type(e).__name__, e)) + + # --- 7. shape of the log ----------------------------------------------- nv = len(re.findall(r"(?m)^VERDICT:", disk)) nc = len(re.findall(r"(?m)^CHECK:", disk)) check("log has exactly one VERDICT:", nv == 1, "found %d" % nv) check("log has exactly one CHECK:", nc == 1, "found %d" % nc) check("log carries its own stamp", bool(stamp), repr(stamp)) - # --- 5. the commit is really on the REMOTE main ------------------------ - ok, detail = _remote_ok(commit) - check("commit pushed to origin/main", ok, detail) - - # --- 6. replay: the builder rewrites the log byte-identically ---------- - try: - a = ns["render"](lane, area, lane, commit, operator, stamp) - b = ns["render"](lane, area, lane, commit, operator, stamp) - check("render() stable across calls (no clock in it)", a == b == disk, - "%d vs %d bytes" % (len(a.encode()), len(b.encode()))) - except Exception as e: # noqa: BLE001 - check("render() stable across calls (no clock in it)", False, - "%s: %s" % (type(e).__name__, e)) - - # --- 7. self-run: a child really spawned and echoed the nonce ---------- + # --- 8. self-run: a child really spawned and echoed the nonce ---------- n = uuid.uuid4().hex child = _self_run(n) if child is None: diff --git a/wizard-work/null/wizard.py b/wizard-work/null/wizard.py index 1897a7e..83103f0 100644 --- a/wizard-work/null/wizard.py +++ b/wizard-work/null/wizard.py @@ -1,27 +1,33 @@ #!/usr/bin/env python3 -"""null standup entry — Codeforge lane. +"""null standup entry -- Codeforge lane. Rebuilds the log from parameters, never from the wall clock: python3 wizard.py # writes wizard.log.md next to this file python3 check.py # exit 0 == artifact is real, exit 1 == broken -Determinism: craft() takes its stamp as a required parameter, so its output is -a function of its arguments. main() stamps only when the log has no stamp yet; -replaying a log whose stamp is already current rewrites the file byte-identically. +Determinism. craft() takes its stamp as a required parameter, so its output is +a function of its arguments; render() never reads the clock or the filesystem. +Replaying a log whose stamp is already current rewrites it byte-identically. -The entry names the commit whose tree contains *these exact bytes* -- found by -searching main for a commit whose blob of this file is byte-identical to what is -on disk, never HEAD and never the newest commit that merely touches the path. -Two traps that naive versions of this fall into: - * naming HEAD goes stale the moment a teammate commits, so the gate stays - green for a tree main no longer has; - * naming the newest commit that touches the path goes stale the moment the log - changes without being committed, which is exactly the state that produced a - green gate over a stale artifact. -If no such commit exists, the gate refuses to pass. +Binding -- and why it is NOT "the commit whose tree holds these bytes". +The entry names a commit SHA, so its own bytes can never be the blob of the +commit it names: that blob would have to contain the very SHA that the blob +itself determines. That is a hash fixed point; no commit satisfies it, and a +gate demanding it is unsatisfiable and reads red forever (the failure this +replaces). The satisfiable binding names the commit that carries the WORK -- +wizard.py and check.py -- and verifies the entry itself separately, on the +remote: -Import-free by design — check.py exec()s this file with no __file__ in scope — + * the named commit is by null and is an ancestor of the remote main; + * the named commit's blobs of wizard.py and check.py are byte-identical to + the files on disk; + * the entry's own bytes are the blob of wizard.log.md on the remote main. + +Nothing here is self-referential: the work files do not contain the SHA, and +the entry is committed after the work commit it names. + +Import-free by design -- check.py exec()s this file with no __file__ in scope -- stdlib only, no module-level dunder reads. """ import os @@ -31,30 +37,28 @@ from datetime import datetime, timezone LANE = "Codeforge - hard coding and agentic reasoning" REPO = "/root/troupe-standup" -LOGPATH = "wizard-work/null/wizard.log.md" HERE = "/root/troupe-standup/wizard-work/null" LOGNAME = "wizard.log.md" +LOGPATH = "wizard-work/null/" + LOGNAME STAMP_FMT = "%Y-%m-%d %H:%M UTC" STAMP_RE = re.compile(r"self-verified at [0-9-]+ [0-9:]+ UTC") -TASK = "bind the standup entry to the commit whose tree holds these bytes" +TASK = "bind the standup entry to the commit that carries its work bytes" AREA = "wizard-work/null" OPERATOR = "null" WORK = [ "wizard.py: parameterized builder; the stamp and the commit are arguments, not the clock", - "check.py: gate binds the log to the commit whose blob is byte-identical, not to HEAD", - "wizard.log.md: self-describing entry carrying its own stamp and its carrying commit", + "check.py: gate re-derives the carrying commit and the remote entry, never trusts the local clone", + "wizard.log.md: self-describing entry naming the commit that carries wizard.py and check.py", ] EVIDENCE = [ - ("log on disk == git blob of the commit it names", "measured", - "check.py: byte comparison, names the differing line"), + ("the named commit is by null and is an ancestor of the REMOTE main", "measured", + "check.py: git ls-remote origin refs/heads/main + git merge-base --is-ancestor"), + ("the named commit's wizard.py and check.py are byte-identical to disk", "measured", + "check.py: git show :path vs the file on disk, byte comparison"), + ("the entry's bytes are the blob on the REMOTE main", "measured", + "check.py: git ls-remote + git show :wizard-work/null/wizard.log.md"), ("fresh render with the log's own stamp == log", "measured", "check.py: render() round-trip, no clock in the output"), - ("the named commit is authored by null", "measured", - "check.py: git show -s --format=%an == null"), - ("the named commit carries these exact bytes", "measured", - "check.py: git rev-list + git cat-file --batch, byte-identical match"), - ("the named commit is on the REMOTE main", "measured", - "check.py: git ls-remote origin refs/heads/main, not the local clone"), ("craft() deterministic in its stamp argument", "measured", "check.py: same args -> same bytes; different stamp -> different verdict"), ] @@ -68,8 +72,12 @@ def parse_log(text): for key in ("lane", "operator", "target area", "base commit", "log"): if s.startswith("- " + key + ": "): fields[key] = s[len("- " + key + ": "):] - if s.startswith("VERDICT: ") and "self-verified at " in s: - fields["stamp"] = s.split("self-verified at ", 1)[1].rstrip(" .") + if s.startswith("VERDICT: "): + body = s[len("VERDICT: "):] + if " - " in body: + fields["task"] = body.split(" - ", 1)[0] + if "self-verified at " in body: + fields["stamp"] = body.split("self-verified at ", 1)[1].rstrip(" .") return fields @@ -135,24 +143,39 @@ def _git(*args, binary=False): def head_sha(): - """HEAD, for orientation only. Never used as the entry's base commit.""" - return _git("rev-parse", "HEAD").stdout.strip() or "unresolved" + """HEAD, for anyone who wants the local tip.""" + return _git("rev-parse", "HEAD").stdout.strip() or "" -def carrying_commit(text): - """The commit whose tree contains exactly `text`, searching main's history. - - Walks every commit that touched this file and keeps the first whose blob of - the file is byte-identical to `text`. Byte equality, not path touching, is - the criterion: a commit that merely edited the log does not carry it. - """ - r = _git("rev-list", "origin/main", "--", LOGPATH) +def _main_shas(path): + """Commits on origin/main that ever touched `path`, newest first.""" + r = _git("rev-list", "origin/main", "--", path) if r.returncode != 0 or not r.stdout.strip(): - return "" - want = text.encode() - for sha in r.stdout.split(): - blob = _git("show", "%s:%s" % (sha, LOGPATH), binary=True) - if blob.returncode == 0 and blob.stdout == want: + return [] + return r.stdout.split() + + +def _blob_matches(sha, path): + """True iff commit `sha`'s blob at `path` is byte-identical to the disk file.""" + local = os.path.join(REPO, path) + if not os.path.isfile(local): + return False + with open(local, "rb") as fh: + want = fh.read() + blob = _git("show", "%s:%s" % (sha, path), binary=True) + return blob.returncode == 0 and blob.stdout == want + + +def commit_carrying(paths): + """The newest commit on origin/main whose blobs for every path equal disk. + + That is the commit the entry names: it is the one that carries the exact + work bytes, so the entry cannot name a commit that does not hold them. + Returns "" when no such commit exists (the entry is not committed yet). + """ + cands = _main_shas(paths[0]) + for sha in cands: + if all(_blob_matches(sha, p) for p in paths): return sha return "" @@ -162,14 +185,14 @@ def main(): stamp = parse_log(open(path, encoding="utf-8").read()).get("stamp") if os.path.exists(path) else None if not stamp: stamp = stamp_now() # first build only; replays reuse the committed stamp - text = render(TASK, AREA, LANE, "", OPERATOR, stamp) - commit = carrying_commit(text) if text else "" - if not commit: - commit = "unresolved" # the gate refuses to pass on this - text = render(TASK, AREA, LANE, commit, OPERATOR, stamp) + + sha = commit_carrying(["wizard-work/null/wizard.py", "wizard-work/null/check.py"]) + if not sha: + sha = "unresolved" # the gate refuses to pass on this + text = render(TASK, AREA, LANE, sha, OPERATOR, stamp) with open(path, "w", encoding="utf-8") as fh: fh.write(text) - verdict, check, _ = craft(TASK, AREA, LANE, commit, OPERATOR, stamp) + verdict, check, _ = craft(TASK, AREA, LANE, sha, OPERATOR, stamp) print(verdict) print(check)