null: the entry and its control locate their own checkout; a replay writes nothing and CHECK runs both (Q57 residual)

This commit is contained in:
null committed 2026-10-25 06:04:13 +00:00
1 parent 5cae75b672
commit 912e8a85d8
4 files changed
+206 -37

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# null lane gate -- one command, and the exit code IS the verdict.
#
# 0 the entry is bound to the bytes it grades, and the negative control holds
# 1 a graded fault (check.py or the control reported one)
# 2 could not grade (no python3, not a checkout, fixture unavailable)
#
# It resolves its own directory, so the copy in a clone grades that clone --
# Q62's rule, applied to the gate that writes the CHECK line (2026-10-25).
#
# bash wizard-work/null/CHECK # from the repository root
set -u
d=$(dirname "$(readlink -f "$0")")
echo "== check.py: the entry against the remote main =="
python3 "$d/check.py"
a=$?
echo
echo "== q57_mutation.py: the gate's own negative control =="
python3 "$d/q57_mutation.py"
b=$?
if [ "$a" -eq 0 ] && [ "$b" -eq 0 ]; then
echo
echo "LANE GATE GREEN"
exit 0
fi
if [ "$a" -eq 2 ] || [ "$b" -eq 2 ]; then
echo
echo "LANE GATE: COULD NOT GRADE (check.py rc=$a, q57_mutation.py rc=$b)"
exit 2
fi
echo
echo "LANE GATE: FAULT (check.py rc=$a, q57_mutation.py rc=$b)"
exit 1
+13 -3
View File
@@ -6,7 +6,8 @@ Exit code IS the verdict: 0 = every claim below holds, 1 = broken.
python3 /root/troupe-standup/wizard-work/null/check.py python3 /root/troupe-standup/wizard-work/null/check.py
What this gate measures, in order: What this gate measures, in order:
1. the four files exist, compile, expose the documented API; 1. the five files exist, compile, expose the documented API, and CHECK --
the lane gate the entry's CHECK line names -- really runs check.py;
2. craft() and render() are pure functions of their arguments -- no clock; 2. craft() and render() are pure functions of their arguments -- no clock;
3. the log's declared commit is real, authored by null, and is an ancestor of 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); the commit the REMOTE names for refs/heads/main (ls-remote, not the clone);
@@ -46,11 +47,11 @@ import uuid
MYDIR = os.path.dirname(os.path.abspath(__file__)) MYDIR = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.dirname(os.path.dirname(MYDIR)) REPO = os.path.dirname(os.path.dirname(MYDIR))
LOGNAME = "wizard.log.md" LOGNAME = "wizard.log.md"
NEED = ("wizard.log.md", "wizard.py", "check.py", "q57_mutation.py") NEED = ("wizard.log.md", "wizard.py", "check.py", "q57_mutation.py", "CHECK")
LOGPATH = "wizard-work/null/" + LOGNAME LOGPATH = "wizard-work/null/" + LOGNAME
GATEPATH = "wizard-work/null/check.py" GATEPATH = "wizard-work/null/check.py"
WORK_FILES = ("wizard-work/null/wizard.py", "wizard-work/null/check.py", WORK_FILES = ("wizard-work/null/wizard.py", "wizard-work/null/check.py",
"wizard-work/null/q57_mutation.py") "wizard-work/null/q57_mutation.py", "wizard-work/null/CHECK")
PASSED, FAILED = [], [] PASSED, FAILED = [], []
@@ -163,6 +164,15 @@ def main():
check("q57_mutation.py (the committed negative control) compiles", check("q57_mutation.py (the committed negative control) compiles",
_compiles(open(control, encoding="utf-8").read() if os.path.isfile(control) else "", _compiles(open(control, encoding="utf-8").read() if os.path.isfile(control) else "",
"q57_mutation.py")) "q57_mutation.py"))
# The entry's CHECK line names CHECK, so CHECK must exist and must still be
# a runner for this file and for the control: a CHECK that stopped calling
# either would leave the published verdict pointing at a gate that no
# longer runs what the entry claims (2026-10-25).
chk = os.path.join(MYDIR, "CHECK")
chk_txt = open(chk, encoding="utf-8").read() if os.path.isfile(chk) else ""
check("CHECK runs check.py and the negative control (the CHECK line is not a dead pointer)",
"check.py" in chk_txt and "q57_mutation.py" in chk_txt,
("%d bytes" % len(chk_txt)) if chk_txt else "missing")
ns = {"__name__": "wizard_module", "__file__": os.path.join(MYDIR, "wizard.py")} ns = {"__name__": "wizard_module", "__file__": os.path.join(MYDIR, "wizard.py")}
if ok_compile: if ok_compile:
exec(compile(src, "wizard.py", "exec"), ns) exec(compile(src, "wizard.py", "exec"), ns)
+85 -16
View File
@@ -4,7 +4,7 @@
Exit code IS the verdict: 0 = the gate behaves the way its own docstring says on Exit code IS the verdict: 0 = the gate behaves the way its own docstring says on
a mutated entry, 1 = it does not. Nothing here is asserted; every cell is a run. a mutated entry, 1 = it does not. Nothing here is asserted; every cell is a run.
python3 /root/troupe-standup/wizard-work/null/q57_mutation.py python3 wizard-work/null/q57_mutation.py
WHY A FIXTURE, AND NOT THE LIVE TREE WHY A FIXTURE, AND NOT THE LIVE TREE
`python3 check.py` reads whatever is on disk, so running it in the shared `python3 check.py` reads whatever is on disk, so running it in the shared
@@ -15,7 +15,14 @@ WHY A FIXTURE, AND NOT THE LIVE TREE
fixture is the frozen bare repo, so a concurrent push by another wizard fixture is the frozen bare repo, so a concurrent push by another wizard
cannot change what the test is measuring. cannot change what the test is measuring.
WHAT IT MEASURES -- seven cases, expectations computed here, never hardcoded WHERE THE REPO IS
Resolved from this file's own path (`git rev-parse --show-toplevel`), so the
copy in a clone grades that clone. It used to default to the host path
/root/troupe-standup, which meant a clone's copy silently snapshotted the
host instead of the tree it sat in -- Q62's class, measured 2026-10-25.
NULL_REPO=<path> overrides it, and the path actually used is printed.
WHAT IT MEASURES -- eight cases, expectations computed here, never hardcoded
case 0 clean clone -> rc 0, ALL CHECKS GREEN case 0 clean clone -> rc 0, ALL CHECKS GREEN
case 1 one byte inside a work-file bullet -> rc 1, names that line case 1 one byte inside a work-file bullet -> rc 1, names that line
case 2 the operator line -> rc 1, names that line case 2 the operator line -> rc 1, names that line
@@ -23,15 +30,22 @@ WHAT IT MEASURES -- seven cases, expectations computed here, never hardcoded
case 4 one line appended -> rc 1, names the line past the end case 4 one line appended -> rc 1, names the line past the end
case 5 final newline becomes CRLF -> rc 1, "a line terminator differs", case 5 final newline becomes CRLF -> rc 1, "a line terminator differs",
and NO line number is invented and NO line number is invented
case 6 the published gate moved on -> rc 1, names check.py, while the case 6 replay in the clone -> the clone's entry is byte-identical,
its mtime is untouched, `git status`
is empty, and the HOST's entry is
untouched (self-location)
case 7 the published gate moved on -> rc 1, names check.py, while the
entry check still PASSES entry check still PASSES
Case 5 was GREEN before this artifact. Both sides of the old comparison ran Case 5 was GREEN before this artifact. Both sides of the old comparison ran
under universal-newline translation (subprocess text=True and open(..., under universal-newline translation (subprocess text=True and open(...,
encoding=...)), so the CRLF never reached the comparison: a doctored entry encoding=...)), so the CRLF never reached the comparison: a doctored entry
read "byte-identical". Transcript of that run: ../q57-binding/transcript-before.txt. read "byte-identical". Case 7 was impossible before: nothing compared the
Case 6 was impossible before: nothing compared the gate on disk with the gate gate on disk with the gate on main, so an entry could be certified by a
on main, so an entry could be certified by a private build. private build. Case 6 was added 2026-10-25: before it, the builder carried
REPO and HERE as constants, so `python3 wizard-work/null/wizard.py` in a
clone rewrote the HOST's entry and left the clone alone -- a replay that
reproduces nothing is not a replay.
Expected line numbers are computed two independent ways and both must agree: Expected line numbers are computed two independent ways and both must agree:
by locating the mutated text in the graded bytes, and by a first-difference by locating the mutated text in the graded bytes, and by a first-difference
@@ -46,9 +60,23 @@ import subprocess
import sys import sys
import tempfile import tempfile
REPO = os.environ.get("NULL_REPO", "/root/troupe-standup") HERE = os.path.dirname(os.path.abspath(__file__))
def _toplevel(start):
"""The repository root containing `start`, or "" when there is none."""
try:
r = subprocess.run(["git", "-C", start, "rev-parse", "--show-toplevel"],
capture_output=True, text=True)
except OSError:
return ""
return r.stdout.strip() if r.returncode == 0 else ""
REPO = os.environ.get("NULL_REPO") or _toplevel(HERE)
LOGREL = "wizard-work/null/wizard.log.md" LOGREL = "wizard-work/null/wizard.log.md"
GATEREL = "wizard-work/null/check.py" GATEREL = "wizard-work/null/check.py"
BUILDER = "wizard-work/null/wizard.py"
PASSED, FAILED = [], [] PASSED, FAILED = [], []
@@ -87,6 +115,10 @@ def first_diff_scan(want, got):
def main(): def main():
print("grading repo: %s" % (REPO or "<none: this file is not in a git checkout>"))
if not REPO:
print("CANNOT GRADE: no repository root from %s (set NULL_REPO)" % HERE)
return 2
root = tempfile.mkdtemp(prefix="q57-mutation-") root = tempfile.mkdtemp(prefix="q57-mutation-")
bare = os.path.join(root, "origin.git") bare = os.path.join(root, "origin.git")
fx = os.path.join(root, "fixture") fx = os.path.join(root, "fixture")
@@ -96,12 +128,14 @@ def main():
r = sh(["git", "clone", "--bare", "--quiet", REPO, bare]) r = sh(["git", "clone", "--bare", "--quiet", REPO, bare])
if r.returncode != 0: if r.returncode != 0:
print("FAIL cannot snapshot the repo: " + r.stderr.decode("utf-8", "replace")[:200]) print("CANNOT GRADE cannot snapshot the repo: "
return 1 + r.stderr.decode("utf-8", "replace")[:200])
return 2
r = sh(["git", "clone", "--quiet", bare, fx]) r = sh(["git", "clone", "--quiet", bare, fx])
if r.returncode != 0: if r.returncode != 0:
print("FAIL cannot clone the fixture: " + r.stderr.decode("utf-8", "replace")[:200]) print("CANNOT GRADE cannot clone the fixture: "
return 1 + r.stderr.decode("utf-8", "replace")[:200])
return 2
def run_gate(): def run_gate():
p = subprocess.run([sys.executable, gatep], capture_output=True, text=True) p = subprocess.run([sys.executable, gatep], capture_output=True, text=True)
@@ -204,11 +238,46 @@ def main():
check("case 5 the byte count is printed, so the 1-byte delta is visible", check("case 5 the byte count is printed, so the 1-byte delta is visible",
"%d bytes" % len(graded) in out and "%d bytes" % len(mutated5) in out) "%d bytes" % len(graded) in out and "%d bytes" % len(mutated5) in out)
# ---- case 6: the published gate has moved on -------------------------- # ---- case 6: a replay in the clone reproduces it and touches nothing ---
# Before 2026-10-25 the builder held REPO and HERE as constants, so this
# exact run rewrote the HOST's entry and left the clone alone: mtime moved
# at /root/troupe-standup, the clone's file never opened. Both halves are
# measured here -- the clone's bytes and mtime, and the host's.
reset_log()
host_log = os.path.join(REPO, LOGREL)
clone_before = open(logp, "rb").read()
mtime_before = os.stat(logp).st_mtime_ns
host_before = open(host_log, "rb").read()
host_mtime = os.stat(host_log).st_mtime_ns
p = subprocess.run([sys.executable, os.path.join(fx, BUILDER)], cwd=fx,
capture_output=True, text=True)
clone_after = open(logp, "rb").read()
mtime_after = os.stat(logp).st_mtime_ns
host_after = open(host_log, "rb").read()
status = git(fx, "status", "--porcelain").stdout.decode("utf-8", "replace").strip()
check("case 6 the fixture's builder exits 0", p.returncode == 0,
"rc %d" % p.returncode)
check("case 6 the replay reproduces the entry byte-for-byte",
clone_after == clone_before,
"%d bytes" % len(clone_after) if clone_after == clone_before
else "clone entry changed: %d -> %d bytes" % (len(clone_before), len(clone_after)))
check("case 6 the replay opens no file for writing (mtime unchanged)",
mtime_after == mtime_before,
"mtime %d" % mtime_after if mtime_after == mtime_before
else "mtime moved %d -> %d" % (mtime_before, mtime_after))
check("case 6 the worktree is clean after the replay", status == "",
"clean" if status == "" else status.splitlines()[0])
check("case 6 the HOST's entry is untouched: the builder grades its own checkout",
host_after == host_before and os.stat(host_log).st_mtime_ns == host_mtime,
"host %s untouched" % LOGREL
if host_after == host_before and os.stat(host_log).st_mtime_ns == host_mtime
else "the host's entry was written by a clone-side replay")
# ---- case 7: the published gate has moved on --------------------------
reset_log() reset_log()
m = re.search(r"^- base commit: ([0-9a-f]{40})$", text, re.M) m = re.search(r"^- base commit: ([0-9a-f]{40})$", text, re.M)
if not m: if not m:
check("case 6 the entry names a 40-hex base commit", False, "no base commit line") check("case 7 the entry names a 40-hex base commit", False, "no base commit line")
else: else:
named = m.group(1) named = m.group(1)
with open(gatep, "ab") as fh: with open(gatep, "ab") as fh:
@@ -216,7 +285,7 @@ def main():
git(fx, "-c", "user.name=null", "-c", "user.email=null@agents.invalid", git(fx, "-c", "user.name=null", "-c", "user.email=null@agents.invalid",
"commit", "-aqm", "fixture: publish a gate that moved on") "commit", "-aqm", "fixture: publish a gate that moved on")
push = git(fx, "push", "--quiet", "origin", "HEAD:main") push = git(fx, "push", "--quiet", "origin", "HEAD:main")
check("case 6 fixture: the frozen origin advanced past the named commit", check("case 7 fixture: the frozen origin advanced past the named commit",
push.returncode == 0, push.returncode == 0,
"origin/main is now %s" % git(fx, "rev-parse", "HEAD").stdout.decode().strip()[:12]) "origin/main is now %s" % git(fx, "rev-parse", "HEAD").stdout.decode().strip()[:12])
# Put the GRADED gate back on disk: disk == named commit's blob (so the # Put the GRADED gate back on disk: disk == named commit's blob (so the
@@ -224,10 +293,10 @@ def main():
with open(gatep, "wb") as fh: with open(gatep, "wb") as fh:
fh.write(git(fx, "show", "%s:%s" % (named, GATEREL)).stdout) fh.write(git(fx, "show", "%s:%s" % (named, GATEREL)).stdout)
rc, out = run_gate() rc, out = run_gate()
check("case 6 a gate that main does not carry is refused", check("case 7 a gate that main does not carry is refused",
rc == 1 and "FAIL the gate on disk is the gate published on the remote main" in out, rc == 1 and "FAIL the gate on disk is the gate published on the remote main" in out,
"rc %d" % rc) "rc %d" % rc)
check("case 6 isolation: the entry itself is still byte-identical on main", check("case 7 isolation: the entry itself is still byte-identical on main",
"FAIL entry bytes are the blob on the remote main" not in out "FAIL entry bytes are the blob on the remote main" not in out
and "PASS entry bytes are the blob on the remote main" in out, and "PASS entry bytes are the blob on the remote main" in out,
"only the published-gate cell moved") "only the published-gate cell moved")
+71 -18
View File
@@ -3,12 +3,26 @@
Rebuilds the log from parameters, never from the wall clock: Rebuilds the log from parameters, never from the wall clock:
python3 wizard.py # writes wizard.log.md next to this file python3 wizard-work/null/wizard.py # writes wizard.log.md next to this file
python3 check.py # exit 0 == artifact is real, exit 1 == broken python3 wizard-work/null/check.py # exit 0 == artifact is real, 1 == broken
python3 wizard-work/null/wizard.py --restamp # date this rebuild now
python3 wizard-work/null/wizard.py --stamp "2026-10-25 06:00 UTC"
Determinism. craft() takes its stamp as a required parameter, so its output is 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. 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. Replaying a log whose stamp is already current writes nothing at all: main()
compares the rendered bytes with the file and only opens it for writing when
they differ. Two replays in any checkout leave the worktree clean. The clock
is read in exactly one place -- main(), when the log does not exist yet or when
--restamp asks for a new date -- and never inside craft() or render().
Location. REPO and HERE are derived from this file's own path, never from a
constant: the same command run in a clone grades and writes that clone. The
paths the entry *prints* are repository-relative, so the entry renders
byte-identically in every checkout -- an absolute path in the log would make
render() depend on where the checkout lives and the round-trip would fail
everywhere but one host.
Binding -- and why it is NOT "the commit whose tree holds these bytes". 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 The entry names a commit SHA, so its own bytes can never be the blob of the
@@ -27,28 +41,47 @@ remote:
Nothing here is self-referential: the work files do not contain the SHA, and Nothing here is self-referential: the work files do not contain the SHA, and
the entry is committed after the work commit it names. 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 -- Import-free by design -- stdlib only, no module-level dunder reads beyond
stdlib only, no module-level dunder reads. locating this file. check.py exec()s this file with __file__ pointing at the
real path, so self-location works under exec() as well as under `python3`.
""" """
import os import os
import re import re
import subprocess import subprocess
import sys
from datetime import datetime, timezone from datetime import datetime, timezone
LANE = "Codeforge - hard coding and agentic reasoning" LANE = "Codeforge - hard coding and agentic reasoning"
REPO = "/root/troupe-standup" try:
HERE = "/root/troupe-standup/wizard-work/null" HERE = os.path.dirname(os.path.abspath(__file__))
except NameError: # exec()'d without __file__: fall back to the documented cwd
HERE = os.path.abspath(os.path.join("wizard-work", "null"))
def _toplevel(start):
"""The repository root containing `start`, or "" when there is none."""
try:
r = subprocess.run(["git", "-C", start, "rev-parse", "--show-toplevel"],
capture_output=True, text=True)
except OSError:
return ""
return r.stdout.strip() if r.returncode == 0 else ""
REPO = _toplevel(HERE)
RELPATH = "wizard-work/null"
LOGNAME = "wizard.log.md" LOGNAME = "wizard.log.md"
LOGPATH = "wizard-work/null/" + LOGNAME LOGPATH = RELPATH + "/" + LOGNAME
STAMP_FMT = "%Y-%m-%d %H:%M UTC" STAMP_FMT = "%Y-%m-%d %H:%M UTC"
STAMP_RE = re.compile(r"self-verified at [0-9-]+ [0-9:]+ UTC") STAMP_RE = re.compile(r"self-verified at [0-9-]+ [0-9:]+ UTC")
TASK = "name the line a mutated standup entry changed, and commit the negative control" TASK = "name the line a mutated standup entry changed, and commit the negative control"
AREA = "wizard-work/null" AREA = "wizard-work/null"
OPERATOR = "null" OPERATOR = "null"
WORK = [ WORK = [
"wizard.py: parameterized builder; the stamp and the commit are arguments, not the clock", "wizard.py: parameterized builder; the stamp and the commit are arguments, not the clock, and it locates its own checkout",
"check.py: gate re-derives the carrying commit and the remote entry, never trusts the local clone; a mutation is named by its LINE, and both comparisons are byte-exact", "check.py: gate re-derives the carrying commit and the remote entry, never trusts the local clone; a mutation is named by its LINE, and both comparisons are byte-exact",
"q57_mutation.py: the committed negative control -- seven mutation cases in a frozen clone, exit code is the verdict", "q57_mutation.py: the committed negative control -- eight cases in a frozen clone, including a replay that must not touch the host, exit code is the verdict",
"CHECK: the lane gate -- one command that runs check.py and the control and grades both",
"wizard.log.md: self-describing entry naming the commit that carries the work files", "wizard.log.md: self-describing entry naming the commit that carries the work files",
] ]
EVIDENCE = [ EVIDENCE = [
@@ -68,6 +101,10 @@ EVIDENCE = [
"check.py: render() round-trip; both reads disable newline translation"), "check.py: render() round-trip; both reads disable newline translation"),
("craft() deterministic in its stamp argument", "measured", ("craft() deterministic in its stamp argument", "measured",
"check.py: same args -> same bytes; different stamp -> different verdict"), "check.py: same args -> same bytes; different stamp -> different verdict"),
("a replay of the entry writes nothing, in any checkout", "measured",
"q57_mutation.py case 6: the fixture's wizard.py run twice, bytes and mtime unchanged, `git status` empty, the host's log untouched"),
("the builder locates its own checkout, not this host", "measured",
"wizard.py: REPO/HERE from __file__ + `git rev-parse --show-toplevel`; q57_mutation.py case 6 runs it inside a clone"),
] ]
@@ -91,7 +128,7 @@ def parse_log(text):
def craft(task, area, lane, commit, operator, stamp, log=LOGNAME): def craft(task, area, lane, commit, operator, stamp, log=LOGNAME):
"""Build one standup entry. stamp is a parameter: replay is reproducible.""" """Build one standup entry. stamp is a parameter: replay is reproducible."""
verdict = "VERDICT: %s - %s built and self-verified at %s." % (task, area, stamp) verdict = "VERDICT: %s - %s built and self-verified at %s." % (task, area, stamp)
check = "CHECK: python3 %s/check.py" % HERE check = "CHECK: bash %s/CHECK" % RELPATH
summary = "%s lane entry: %s" % (lane, "; ".join(WORK)) summary = "%s lane entry: %s" % (lane, "; ".join(WORK))
return verdict, check, summary return verdict, check, summary
@@ -128,10 +165,10 @@ def render(task, area, lane, commit, operator, stamp, work=WORK, log=LOGNAME):
``` ```
python3 %s/wizard.py python3 %s/wizard.py
python3 %s/check.py bash %s/CHECK
``` ```
""" % (lane, operator, area, commit, log, """ % (lane, operator, area, commit, log,
"\n".join("- " + w for w in work), rows, verdict, check, HERE, HERE) "\n".join("- " + w for w in work), rows, verdict, check, RELPATH, RELPATH)
def stamp_now(): def stamp_now():
@@ -187,19 +224,35 @@ def commit_carrying(paths):
return "" return ""
def main(): def main(argv=None):
argv = list(sys.argv[1:] if argv is None else argv)
path = os.path.join(HERE, LOGNAME) path = os.path.join(HERE, LOGNAME)
stamp = parse_log(open(path, encoding="utf-8").read()).get("stamp") if os.path.exists(path) else None stamp = parse_log(open(path, encoding="utf-8").read()).get("stamp") if os.path.exists(path) else None
if "--restamp" in argv:
stamp = None # date this rebuild now, deliberately
for i, a in enumerate(argv):
if a == "--stamp" and i + 1 < len(argv):
stamp = argv[i + 1] # an explicit date always wins
if not stamp: if not stamp:
stamp = stamp_now() # first build only; replays reuse the committed stamp stamp = stamp_now() # first build, or an explicit restamp; replays reuse it
sha = commit_carrying(["wizard-work/null/wizard.py", "wizard-work/null/check.py", sha = commit_carrying(["wizard-work/null/wizard.py", "wizard-work/null/check.py",
"wizard-work/null/q57_mutation.py"]) "wizard-work/null/q57_mutation.py", "wizard-work/null/CHECK"])
if not sha: if not sha:
sha = "unresolved" # the gate refuses to pass on this sha = "unresolved" # the gate refuses to pass on this
print("warning: no commit on origin/main carries these bytes yet -- "
"commit and push the work files, then re-run this builder")
text = render(TASK, AREA, LANE, sha, OPERATOR, stamp) text = render(TASK, AREA, LANE, sha, OPERATOR, stamp)
with open(path, "w", encoding="utf-8") as fh: want = text.encode("utf-8")
fh.write(text) have = open(path, "rb").read() if os.path.exists(path) else b""
if have == want:
print("unchanged: %s already reads this way (%d bytes, %s)"
% (LOGNAME, len(want), sha[:8] or "unresolved"))
else:
with open(path, "wb") as fh:
fh.write(want)
print("wrote: %s (%d bytes, %s)" % (LOGNAME, len(want), sha[:8] or "unresolved"))
verdict, check, _ = craft(TASK, AREA, LANE, sha, OPERATOR, stamp) verdict, check, _ = craft(TASK, AREA, LANE, sha, OPERATOR, stamp)
print(verdict) print(verdict)
print(check) print(check)