gofmt gate in agent_gates.py with its decoys (R-1160); REPORT for the day
gates / gates (push) Successful in 49s
gates / gates (push) Successful in 49s
This commit is contained in:
@@ -67,6 +67,9 @@ GATES = [
|
||||
# runs the FULL set, so every push is still judged by it. It plants only in scratch clones and asks a fake
|
||||
# Gitea on 127.0.0.1 (never the network, never the real tree); it does not call this runner (no recursion).
|
||||
("gate-decoys", os.path.join(ROOT, "scripts", "test_gate_decoys.py"), [], False),
|
||||
# R-1160 (2026-10-19) — every Go file is gofmt-clean; the controller's gofmt_gate.py shape. Fast: one gofmt -l.
|
||||
# Where the runner has no Go it says NOT CHECKED out loud and passes; the pre-push hook (with Go) enforces it.
|
||||
("gofmt", os.path.join(ROOT, "scripts", "gofmt_gate.py"), [], True),
|
||||
]
|
||||
|
||||
VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gofmt_gate.py — every Go file in this module is gofmt-clean (R-1160; the controller's R-454 gate, same shape).
|
||||
|
||||
MEASURED 2026-10-19: 14 agent files were not gofmt-clean and nothing noticed (fixed by one formatting-only commit,
|
||||
1edfd8e). The controller's history below is why a gate and not a one-off clean-up.
|
||||
|
||||
MEASURED 2026-09-02: five internal/web test files had been gofmt-unclean for an unknown length of
|
||||
time and nothing noticed — `go vet` does not check formatting and no gate did. By 2026-10-05 the five
|
||||
were clean and TWELVE others were not: the count only grows while nothing looks, and every `gofmt -l`
|
||||
someone runs by hand is then noise that hides a real one.
|
||||
|
||||
Run from the repo root: python3 scripts/gofmt_gate.py
|
||||
Exit 0 clean · 1 convicted (lists the files) · 2 inconclusive (gofmt not found — never a pass).
|
||||
|
||||
gofmt is the Go toolchain's own formatter (not a shell utility), found on PATH or under
|
||||
`go env GOROOT`/bin. Decoy: scripts/test_gate_decoys.py ("gofmt"), in a scratch copy of this script.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ROOTS = ["cmd", "internal"]
|
||||
|
||||
|
||||
def find_gofmt():
|
||||
p = shutil.which("gofmt")
|
||||
if p:
|
||||
return p
|
||||
go = shutil.which("go")
|
||||
if go:
|
||||
try:
|
||||
root = subprocess.check_output([go, "env", "GOROOT"], text=True).strip()
|
||||
cand = os.path.join(root, "bin", "gofmt")
|
||||
if os.path.isfile(cand):
|
||||
return cand
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def in_ci():
|
||||
"""True on the Gitea Actions runner (act sets GITHUB_ACTIONS, Gitea sets GITEA_ACTIONS)."""
|
||||
return os.environ.get("GITEA_ACTIONS") == "true" or os.environ.get("GITHUB_ACTIONS") == "true"
|
||||
|
||||
|
||||
def main():
|
||||
gofmt = find_gofmt()
|
||||
if not gofmt:
|
||||
# The CI runner may have no Go toolchain (felhom.eu CLAUDE.md, R-887 note). There the gate
|
||||
# says so OUT LOUD and passes: the pre-push hook on the developer's machine, where Go always exists, is where
|
||||
# formatting is enforced. Anywhere else a missing gofmt stays INCONCLUSIVE — never a silent pass.
|
||||
# Pinned by the "gofmt: CI without Go" and "gofmt: dev without Go" decoys in test_gate_decoys.py.
|
||||
if in_ci():
|
||||
print("gofmt gate NOT CHECKED in CI: no Go toolchain on the runner — the pre-push hook (with Go) enforces it")
|
||||
return 0
|
||||
print("gofmt gate INCONCLUSIVE: gofmt not found on PATH or under `go env GOROOT`/bin")
|
||||
return 2
|
||||
roots = [r for r in ROOTS if os.path.isdir(os.path.join(ROOT, r))]
|
||||
if not roots:
|
||||
print("gofmt gate INCONCLUSIVE: none of %s exists under %s" % (ROOTS, ROOT))
|
||||
return 2
|
||||
p = subprocess.run([gofmt, "-l"] + roots, cwd=ROOT, capture_output=True, text=True)
|
||||
if p.returncode != 0:
|
||||
print("gofmt gate INCONCLUSIVE: gofmt exited %d: %s" % (p.returncode, p.stderr.strip()[:400]))
|
||||
return 2
|
||||
bad = [l for l in p.stdout.splitlines() if l.strip()]
|
||||
if bad:
|
||||
for f in bad:
|
||||
print(" not gofmt-clean: %s" % f)
|
||||
print("GOFMT GATE FAILED: %d file(s) — run `gofmt -w <file>` (formatting only, no behaviour change)" % len(bad))
|
||||
return 1
|
||||
print("gofmt gate OK — every Go file under %s is gofmt-clean" % ", ".join(roots))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -66,6 +66,9 @@ COVERS = {
|
||||
"inside an HTML comment (R-426)",
|
||||
"observations": "R-419 in THIS repo's REPORT.md: an Observations note SAYING it carries no marker - vs the "
|
||||
"two genuine markers (R-426)",
|
||||
"gofmt": "R-1160: an unformatted .go file planted under internal/ of a SCRATCH copy is convicted by name - vs the "
|
||||
"same tree formatted (passes); with no gofmt reachable, CI passes OUT LOUD and a dev machine is "
|
||||
"INCONCLUSIVE, never a silent pass",
|
||||
"gate-decoys": "this suite, run as a gate of agent_gates.py's full run, which CI runs on every push (R-1116): "
|
||||
"every case above is a decoy of its own gate, and ONE case that stops convicting turns the run "
|
||||
"red - red-proved 2026-10-17 by flipping a reuse-refs FACT case to expect rc 0",
|
||||
@@ -291,6 +294,32 @@ def release_cases(base, ws):
|
||||
|
||||
|
||||
# ── the shared felhom.eu scripts, against THIS repo's inputs ─────────────────────────────────────
|
||||
def gofmt_cases(ws):
|
||||
"""R-1160: the gofmt gate in a scratch root (its ROOT is its own grandparent directory), never the real tree."""
|
||||
root = os.path.join(ws, "gofmt-root")
|
||||
os.makedirs(os.path.join(root, "scripts"))
|
||||
os.makedirs(os.path.join(root, "internal", "x"))
|
||||
shutil.copy(os.path.join(ROOT, "scripts", "gofmt_gate.py"), os.path.join(root, "scripts", "gofmt_gate.py"))
|
||||
gate = os.path.join(root, "scripts", "gofmt_gate.py")
|
||||
planted = os.path.join(root, "internal", "x", "zz_gofmt_decoy.go")
|
||||
io.open(planted, "w", encoding="utf-8").write(u"package x\n\nfunc F( ) int {return 1}\n")
|
||||
if shutil.which("gofmt") or shutil.which("go"):
|
||||
rc, out = run([sys.executable, gate], root)
|
||||
report("gofmt: FACT: an unformatted file under internal/", rc, out, 1, ("zz_gofmt_decoy.go", "GOFMT GATE FAILED"))
|
||||
io.open(planted, "w", encoding="utf-8").write(u"package x\n\nfunc F() int { return 1 }\n")
|
||||
rc, out = run([sys.executable, gate], root)
|
||||
report("gofmt: GENUINE: the same file formatted", rc, out, 0, ("gofmt gate OK",))
|
||||
else:
|
||||
print(" -- gofmt: not runnable here (no Go toolchain) - the two cases below pin that mode")
|
||||
for ci, want_rc, want, name in ((True, 0, "NOT CHECKED in CI", "gofmt: CI without Go"),
|
||||
(False, 2, "INCONCLUSIVE", "gofmt: dev without Go")):
|
||||
env = {"PATH": os.path.join(ws, "empty-path"), "HOME": ws}
|
||||
if ci:
|
||||
env["GITEA_ACTIONS"] = "true"
|
||||
rc, out = run([sys.executable, gate], root, env=env)
|
||||
report(name, rc, out, want_rc, (want,))
|
||||
|
||||
|
||||
def shared_cases(ws):
|
||||
"""A scratch WORKSPACE: a clone of this repo beside symlinks to the siblings, because the shared
|
||||
scripts reach across (REUSE.md cites hub paths; instructions_gate reads the workspace CLAUDE.md)."""
|
||||
@@ -353,6 +382,7 @@ def main():
|
||||
published_cases(base)
|
||||
release_cases(base, ws)
|
||||
shared_cases(ws)
|
||||
gofmt_cases(ws)
|
||||
finally:
|
||||
srv.shutdown()
|
||||
srv.server_close()
|
||||
|
||||
Reference in New Issue
Block a user