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:
@@ -5,6 +5,11 @@
|
||||
- The R-973 comments in `cmd/felhom-agent/main.go` and `internal/localapi/backup_cap.go` named the hub event
|
||||
`agent_capability_degraded`; since hub v0.159.0 (R-1150) a refused backup raises `whole_guest_backup_refused`.
|
||||
Found by the hub helper of the day brief 2026-10-19.
|
||||
- **gofmt, R-1160:** `1edfd8e` formats the 14 Go files that were not gofmt-clean (formatting only, `go test ./...`
|
||||
green). New gate `scripts/gofmt_gate.py` (the controller's R-454 shape) in `agent_gates.py`, fast set; its decoys in
|
||||
`scripts/test_gate_decoys.py` plant an unformatted file in a SCRATCH copy (convicted by name), the formatted twin
|
||||
(passes), and the no-Go modes (CI passes out loud, a dev machine is INCONCLUSIVE). Red-proved: with the conviction
|
||||
switched off the FACT case reports a LIVE HOLE. The next release carries it; no release for it.
|
||||
|
||||
## v0.160.0 — five fixes: an abandoned or un-typed-back escrow ceremony keeps the old code (R-978, R-1151); a whole-guest backup flood is capped (R-973) and the local tier keeps one point per day (R-1149); an empty partition is not „data" (R-1093) (2026-10-19)
|
||||
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
# REPORT — agent v0.160.0 (2026-10-19, day brief)
|
||||
# REPORT — agent: gofmt clean-up and gate (2026-10-19, second day brief)
|
||||
|
||||
**Released on the operator's yes and running on demo-hp, Tester 1 and demo-felhom; NOT vouched** (the operator's act):
|
||||
version 0.160.0, sha256 `7ebb547f2b1ab93544ba828d3040351b60c4126327a5b85bbcb33f8f2b3dfca1`, config bundle
|
||||
`6b06090190ff609af1fe9842447ebf385fd52da4be382bc7ca708b558c8e202f`.
|
||||
No release. Agent 0.160.0 stays the newest release; the operator vouched it on 2026-10-19 (08:24).
|
||||
|
||||
- R-978 (an abandoned second ceremony keeps the first code), R-973 (the whole-guest cadence cap), R-1093 (an empty
|
||||
partition is blank), **R-1149** (the local tier keeps one copy per day: `keep-daily=N`, operator D17), **R-1151** (the new
|
||||
package reaches the hub only at `POST /escrow/ceremony/confirm` with two typed words; an old controller keeps the claim
|
||||
order).
|
||||
- Proven on demo-hp: a normal whole-guest backup green and counted by the cap; the escrow confirm and abandon cases through
|
||||
the wizard's own calls (code 1 opens the hub's package; an abandoned code 2 opens nothing); an empty partition formatted
|
||||
through the real route with no confirmation (a RAM disk), and the confirmation asked once it held ext4 (the control).
|
||||
- After the release, unreleased: `4545819` — two stale texts (no behaviour change).
|
||||
|
||||
Tests and red-proofs: `CHANGELOG.md` v0.160.0. Evidence: felhom.eu `documentation/audits/day-2026-10-19/P1/`. Full report:
|
||||
felhom.eu `REPORT-day-2026-10-19.md`.
|
||||
- `1edfd8e` — `gofmt -w` on the 14 files that were not gofmt-clean. Formatting only; `go build`, `go vet` and
|
||||
`go test ./...` green before the commit.
|
||||
- `scripts/gofmt_gate.py` — every Go file under `cmd/` and `internal/` is gofmt-clean (R-1160), the controller's
|
||||
`gofmt_gate.py` shape: exit 1 names each file, a missing gofmt is INCONCLUSIVE on a dev machine and NOT CHECKED (out
|
||||
loud) in CI. Registered in `agent_gates.py` (fast set, so the pre-push hook runs it).
|
||||
- Decoys (`scripts/test_gate_decoys.py`, `gofmt_cases`): an unformatted file in a scratch copy → rc 1 naming it; the
|
||||
formatted twin → rc 0; no Go + CI → rc 0 "NOT CHECKED in CI"; no Go on a dev machine → rc 2. Red-proof: with the
|
||||
conviction switched off, the FACT case reported `rc=0 expected 1 - LIVE HOLE`. Restored; `agent_gates.py` all OK.
|
||||
|
||||
@@ -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