fix(release): refuse to gate or record against a foreign acceptance server
Quality gates / quality (push) Successful in 16m30s

A leftover checkout answering on 127.0.0.1:4173 satisfied the readiness probe,
so every acceptance flow and the recorded demo described another tree while the
build reported green. The builder now fails closed if the port already has a
listener, re-checks the spawned server after readiness, and verifies the served
app.js and src/domain.js bytes match the clone under test before the first gate.

Verified against a real stale tree: the occupied-port guard aborts, the
foreign-bytes guard aborts, and the matching tree still passes.
This commit is contained in:
mercury committed 2026-10-22 12:37:57 +00:00
1 parent 4ac71915fb
commit 3565ce87a4
2 files changed
+47

No files matched your search

+31
View File
@@ -9,6 +9,7 @@ import os
from pathlib import Path
import re
import shutil
import socket
import subprocess
import sys
import tarfile
@@ -76,6 +77,29 @@ def sha256(path: Path) -> str:
return digest.hexdigest()
def assert_port_free(host: str, port: int) -> None:
"""Fail closed when something is already listening where the acceptance server must bind."""
with socket.socket() as sock:
sock.settimeout(1.0)
if sock.connect_ex((host, port)) == 0:
raise SystemExit(
f"Port {port} is already served by another process; refusing to gate or record this "
"release against a tree that is not the one under test."
)
def assert_served_tree(tree: Path, host: str, port: int) -> None:
"""Prove the responding server is the clone under test, not a stale checkout on the same port."""
for relative in ("src/domain.js", "app.js"):
served = urllib.request.urlopen(f"http://{host}:{port}/{relative}", timeout=5).read()
expected = (tree / relative).read_bytes()
if hashlib.sha256(served).hexdigest() != hashlib.sha256(expected).hexdigest():
raise SystemExit(
f"The acceptance server is serving {relative} from a different tree; "
"every gate and the demo would describe a release that is not this commit."
)
def tracked_files(tree: Path) -> list[str]:
names = run(["git", "ls-files"], tree, capture=True).splitlines()
return sorted(name for name in names if name and not name.startswith(EXCLUDED_PREFIXES)
@@ -133,6 +157,10 @@ def main() -> int:
run(["python3", "scripts/build_vision_corpus.py", "--verify"], tree)
server_env = dict(os.environ)
server_env.update({"HOST": "127.0.0.1", "PORT": "4173", "TIMMY_VISION_ENABLED": "false"})
# A leftover server from another checkout answers on this port and the readiness probe cannot
# tell it apart from ours, so every gate and the recorded demo would silently describe a
# different tree. Refuse to build over a live listener, and verify the served bytes afterwards.
assert_port_free("127.0.0.1", 4173)
server_log = (release_dir / "acceptance-server.log").open("w", encoding="utf-8")
server = subprocess.Popen(["node", "server.mjs"], cwd=tree, env=server_env,
stdout=server_log, stderr=subprocess.STDOUT, text=True)
@@ -147,6 +175,9 @@ def main() -> int:
time.sleep(0.1)
else:
raise SystemExit("Acceptance server did not become ready")
if server.poll() is not None:
raise SystemExit("Acceptance server exited after reporting ready")
assert_served_tree(tree, "127.0.0.1", 4173)
run(["npm", "run", "test:ui"], tree)
run(["npm", "run", "test:photo"], tree)
run(["npm", "run", "test:mobile-capture"], tree)
+16
View File
@@ -373,3 +373,19 @@ test('release demo shows a versioned backup, an upgraded older file, and a refus
assert.match(builder, /"portable_backup_restore": "passed"/);
assert.match(builder, /"test:portability"/);
});
test('the builder refuses to gate or record a release against a foreign server', async () => {
const builder = await readFile(builderPath, 'utf8');
// A leftover checkout answering on the acceptance port once satisfied every flow and the demo,
// so the release described a tree that was not the commit under test. The builder must refuse a
// live listener before it starts, then prove the served bytes are this tree's before any gate.
assert.match(builder, /def assert_port_free/);
assert.match(builder, /assert_port_free\("127\.0\.0\.1", 4173\)/);
assert.match(builder, /def assert_served_tree/);
assert.match(builder, /assert_served_tree\(tree, "127\.0\.0\.1", 4173\)/);
const identityCheck = builder.indexOf('assert_served_tree(tree, "127.0.0.1", 4173)');
const firstFlow = builder.indexOf('run(["npm", "run", "test:ui"]');
assert.ok(identityCheck > 0, 'the served-tree check must be present');
assert.ok(firstFlow > identityCheck, 'the served-tree check must run before the first acceptance flow');
});