From c6a567122ecc7b03087a5eeb22b1e295209ef9d9 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 15 Oct 2026 21:09:38 +0200 Subject: [PATCH] v0.316.4: build.sh refuses a push from a dirty or unpushed tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An admission and its mechanism. Building 0.316.3 published another session's work: the shared DooPlex clone held 31 uncommitted files, and the build directory's build.sh is a symlink into that clone's WORKING COPY — which predated the FELHOM_REPO_DIR override, so the override was ignored and the build copied the shared tree. The banner printed 'Commit: 0225255' and no 'Repo:' line; the evidence was on screen and the image was already pushed. Nobody deployed it, which is luck, not a control. The clean-tree rule has been a sentence in a runbook all along. A rule without a mechanism is a wish, so it is now two questions the build asks and refuses on: is every tracked file committed, and is HEAD what origin/main has. A directory that is not a git checkout is refused too — a build whose source cannot be identified is not reproducible. Four new cases in the test (ten in total), each driving the real script; red-proved by deleting the gate, which fails all four. 0.316.3 must never be deployed. It is not re-pushed or deleted: a version number is pushed once, and that applies to a number I spoiled. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y --- CHANGELOG.md | 18 ++++++++++ controller/build.sh | 37 +++++++++++++++++++ controller/scripts/test_build_push_once.sh | 42 +++++++++++++++++++++- 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fcca08..175f20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## v0.316.4 — the clean-tree gate is a mechanism, not a sentence (2026-10-15) + +**MinAgent: 0.155.0** (unchanged). **Do not deploy `0.316.3`** — see below. + +- **`build.sh` refuses `--push` from a tree that is dirty or not at `origin/main`**, and from a + directory that is not a git checkout at all. Four more cases in + `scripts/test_build_push_once.sh` (ten in total), red-proved by deleting the gate. +- **Why, and it is an admission.** Building 0.316.3 published another session's work. The shared + DooPlex clone held 31 uncommitted files (the physical-disk storage work); the build directory's + `build.sh` is a symlink into that clone's WORKING COPY, which predated the `FELHOM_REPO_DIR` + override, so the override was silently ignored and the build copied the shared tree. The banner + said `Commit: 0225255` with no `Repo:` line — the evidence was on screen and the image was + already pushed. **Nobody deployed it; that was luck, not a control.** The clean-tree rule existed + only as a sentence in a runbook, and a rule without a mechanism is a wish. +- **`0.316.3` must never be deployed.** It carries another session's unreleased code under this + repo's version number. It is not re-pushed or deleted — a version number is pushed once, and that + applies to a number I spoiled. R-1082. + ## v0.316.3 — the app page as one column, finished (2026-10-15) - **The column sat too far from the top and the sidebar.** `.app-page` carried the mockup's diff --git a/controller/build.sh b/controller/build.sh index d3a78c1..2cda5c9 100755 --- a/controller/build.sh +++ b/controller/build.sh @@ -110,6 +110,43 @@ if [[ "${ACTION}" == "--push" || "${ACTION}" == "--multiarch" ]]; then exit 1 fi info "${VERSION} is not present in the registry — proceeding." + + # AND THE TREE IT BUILDS FROM MUST BE CLEAN AND PUSHED. + # + # Until v0.316.3 this was a sentence in a runbook, and a rule without a mechanism is a wish. It + # cost a bad push the same evening it was written down: the shared clone had 31 files of another + # session's work in progress, and a build from it put their unreleased code into a published + # image under my version number. Nobody deployed it; that was luck, not a control. + # + # Two questions, both of which must be YES: + # is every tracked file committed? — otherwise the image holds something git does not + # is HEAD what origin/main has? — otherwise the image holds something nobody can fetch + # „An unpushed change does not exist" (workspace CLAUDE.md), and an image built from one is a + # thing that exists and cannot be reproduced. + if [[ -d "${REPO_DIR}/.git" ]]; then + dirty=$(cd "${REPO_DIR}" && git status --porcelain 2>/dev/null | wc -l) + if [[ "${dirty}" -ne 0 ]]; then + error "${REPO_DIR} has ${dirty} uncommitted change(s) — refusing to push a build from it." + error "An image built from a dirty tree cannot be reproduced, and on a SHARED clone it" + error "may carry work that is not yours. Commit and push, or build from a clean clone" + error "with FELHOM_REPO_DIR pointing at it." + (cd "${REPO_DIR}" && git status --porcelain | head -10 | sed 's/^/ /') + exit 1 + fi + head_sha=$(cd "${REPO_DIR}" && git rev-parse HEAD 2>/dev/null || echo unknown) + main_sha=$(cd "${REPO_DIR}" && git rev-parse origin/main 2>/dev/null || echo unknown) + if [[ "${head_sha}" != "${main_sha}" ]]; then + error "${REPO_DIR} is not at origin/main — refusing to push." + error " HEAD ${head_sha}" + error " origin/main ${main_sha}" + error "An unpushed change does not exist; an image built from one cannot be fetched again." + exit 1 + fi + info "Source tree is clean and at origin/main." + else + error "${REPO_DIR} is not a git checkout — refusing to push a build whose source cannot be identified." + exit 1 + fi fi # ========================================================================= diff --git a/controller/scripts/test_build_push_once.sh b/controller/scripts/test_build_push_once.sh index 9ae7dc1..bf83468 100644 --- a/controller/scripts/test_build_push_once.sh +++ b/controller/scripts/test_build_push_once.sh @@ -72,7 +72,7 @@ check() { echo " ok $name" } -echo "build.sh — a version number is pushed once" +echo "build.sh — a version number is pushed once, from a clean tree" # 1. The tag is already in the registry -> refuse, before building, and push nothing. run_build present 0.999.0 --push @@ -102,6 +102,46 @@ check "--push, auth failure -> refuses" 1 "Could not establish whether" "FAKE-DO run_build present 0.999.0 check "local build, tag present -> not refused" 0 "" "already exists in the registry" +# ── THE SOURCE TREE MUST BE CLEAN AND PUSHED (v0.316.4) ───────────────────────────────────────── +# Earned the same evening: a build from the shared clone, which held 31 files of another session's +# work in progress, published their unreleased code under this repo's version number. +scratch_repo() { + local d; d="$(mktemp -d)" + git -C "$d" init -q 2>/dev/null + git -C "$d" config user.email t@t; git -C "$d" config user.name t + mkdir -p "$d/controller"; echo x > "$d/controller/f" + git -C "$d" add -A >/dev/null; git -C "$d" commit -qm one >/dev/null + git -C "$d" update-ref refs/remotes/origin/main HEAD + echo "$d" +} +run_build_in() { # $1 = mode, $2 = repo dir, rest = args + local mode="$1" repo="$2"; shift 2 + local tmp; tmp="$(mktemp -d)" + make_fake_docker "$tmp/bin" + FAKE_MODE="$mode" FELHOM_REPO_DIR="$repo" PATH="$tmp/bin:$PATH" bash "$BUILD_SH" "$@" >"$tmp/out" 2>&1 + rc=$?; out="$(cat "$tmp/out")"; rm -rf "$tmp"; return $rc +} + +CLEAN_REPO="$(scratch_repo)" +run_build_in absent "$CLEAN_REPO" 0.999.0 --push +check "clean + at origin/main -> proceeds" "$rc" "Source tree is clean and at origin/main" "refusing to push" + +echo "dirt" > "$CLEAN_REPO/controller/uncommitted" +run_build_in absent "$CLEAN_REPO" 0.999.0 --push +check "dirty tree -> refuses" 1 "uncommitted change" "FAKE-DOCKER-REACHED" +rm -f "$CLEAN_REPO/controller/uncommitted" + +echo y > "$CLEAN_REPO/controller/f2" +git -C "$CLEAN_REPO" add -A >/dev/null; git -C "$CLEAN_REPO" commit -qm two >/dev/null +run_build_in absent "$CLEAN_REPO" 0.999.0 --push +check "ahead of origin/main -> refuses" 1 "not at origin/main" "FAKE-DOCKER-REACHED" +rm -rf "$CLEAN_REPO" + +NOT_A_REPO="$(mktemp -d)"; mkdir -p "$NOT_A_REPO/controller" +run_build_in absent "$NOT_A_REPO" 0.999.0 --push +check "not a git checkout -> refuses" 1 "not a git checkout" "FAKE-DOCKER-REACHED" +rm -rf "$NOT_A_REPO" + echo if [[ $fails -eq 0 ]]; then echo "all $ran cases behaved — a pushed version cannot be overwritten"