From dd8b2ace0295ed141d2f906e5bd6386fd618e7e6 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Fri, 16 Oct 2026 17:23:46 +0200 Subject: [PATCH] release-agent.sh: only a clean 404 proves a version is new (R-1098) An unreachable registry (000) or a server error passed the existing-version check as new. Test: scripts/test_release_push_once.sh, red-proved. No agent release. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- CHANGELOG.md | 9 ++++++ REPORT.md | 46 ++++++---------------------- scripts/release-agent.sh | 4 +++ scripts/test_release_push_once.sh | 50 +++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 37 deletions(-) create mode 100644 scripts/test_release_push_once.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f3e928..1843a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## scripts only — `release-agent.sh` refuses when the registry cannot say the version is new (R-1098, 2026-10-16) + +No agent release; no binary changes. `scripts/release-agent.sh` already refused a version whose tag exists or whose +package answers 200. It let everything else through — a registry it could not reach (curl prints `000`) or a 500 read +as „new". Now only a clean `404` proves the version is new; anything else refuses with the code it got. New +`scripts/test_release_push_once.sh` (the real script, a fake `curl` and `go` on PATH; published / absent / unreachable / +server error / tag exists), red-proved: with the 404 rule removed, the unreachable and server-error cases fail. Same rule +as the controller `build.sh` and, since today, felhom.eu `scripts/build-hub.sh`. + ## v0.158.1 — security review of v0.158.0: the mkfs wrapper trusts no sibling claim; the internal-place helper fails closed (2026-10-16) **v0.158.0 is superseded — do not vouch it.** A security review of `912f7c1` found two defects; both fixed here, each test diff --git a/REPORT.md b/REPORT.md index 1a2eb24..b2cac27 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,42 +1,14 @@ -# REPORT — v0.158.1: the internal data place, partitions as places, add_internal_place (2026-10-16) +# REPORT — release-agent.sh: only a clean 404 proves a version is new (R-1098, 2026-10-16) -Written in ASD-STE100 Simplified Technical English. Design read first: felhom.eu `architecture/16-storage-physical-identity.md` -(§5–§7, §9, §13), `03-host-agent.md`. Task: storage identity build B. v0.158.0 is superseded (security review) — do not vouch it. +Written in ASD-STE100 Simplified Technical English. Part of the apps burndown (felhom.eu +`REPORT-burndown-apps-2026-10-16.md`). No agent release, no binary change. -## What changed - -- `configs/felhom-internal-place` (bundle): the ruled size rule, the only lvcreate/mkfs of a system-disk volume; fails closed - on any failed read. Run by the installer (1.33.0 step 5b) and by the new signed op. -- `internal/localapi/internalplace.go`: the record → mount at `/mnt/belso` by fs-UUID like a drive, kind `internal`; never takes a - household drive's `/mnt/belso`; eject/decommission/format refused. -- `internal/signedjobs/internalplace.go`: `add_internal_place` (operator key, no parameters). -- `internal/storage/partitions.go`: each part of a free disk listed (empty / filesystem / in_use, `formattable`); a data-bearing - part's wipe id binds disk id + part number + fs-UUID (`durable_device.go`). -- `configs/felhom-mkfs-guarded.sh`: every mount of every node outside /mnt/felhom-drives refuses (kernel mount table); an - unlistable disk refuses; NO exemption for a sibling claiming to be Felhom's own (0.158.0 had one — removed). -- Sudoers `FELHOM_INTERNALPLACE` (`^(plan|create) 0$`); capability manifest + injection cases. - -## Tests and red-proofs - -configs/test_felhom_internal_place.py (13), configs/test_felhom_mkfs_guarded.py (8 — first tests of the wrapper; the 0.158.0 -wrapper and the old one measured against them), internalplace_test.go (9), internalplace (signedjobs, 4), partitions_test.go -(3), partition_durable_test.go (4), the sudo rule widened → caught. Every new test seen failing first. `agent_gates.py`: green. - -## Release and delivery - -`release-agent.sh 0.158.1`: sha256 `20264df6…84b76a1`, bundle `c23c3d35…cb1c41b`. The bundle ADDS a path, so boxes on an older -wrapper refuse it (R16): a step bundle `0.158.1-step1` (sha `fd968ee1…5c2eb9`, the 0.156.1 bundle with only `felhom-os-apply` -replaced) was published, then per box: agent_update 0.158.1 → step bundle → bundle 0.158.1. Done on Tester 1, demo-hp, the N100. -**Vouched by the operator** (with golden 0.318.0). - -## Live proof - -A fresh one-disk install (512 GB VM, installer 1.33.0): the place made (213 GiB), mounted by the agent, registered by the -controller; nothing of it in a whole-box copy; same-disk internal vs sys_drive: same disk, proven; partitions listed, formatted, -used. Evidence: felhom.eu `audits/storage-identity-buildB-2026-10-15/live/`. +- **What was wrong:** `scripts/release-agent.sh` refused a version whose tag exists or whose package answers 200. A + registry it could not reach (`000`) or a server error (`500`) passed as „new". +- **What changed:** only `404` proves the version is new; anything else refuses and names the code. +- **Test:** `scripts/test_release_push_once.sh` — 5 cases, all right; with the new line removed, 2 fail (unreachable, + server error). `agent_gates.py --fast` OK. ## Observations -1. FILED: R-1093 — an empty part asks for the typed confirmation: the probe reads the PTTYPE a partition inherits from its disk. -2. NOT-A-FINDING: the step bundle was needed and is the documented route (R-880); the first 0.158.0 bundle jobs were refused by - R16 as designed, nothing changed on the boxes. +1. The release test is not run by `agent_gates.py` (the same as felhom.eu's hub build tests): it is run by hand. NOT-A-FINDING: the script changes rarely, and its header names the test to run with a change; the controller's and the hub's build tests are run the same way. diff --git a/scripts/release-agent.sh b/scripts/release-agent.sh index d8ccd18..d215129 100755 --- a/scripts/release-agent.sh +++ b/scripts/release-agent.sh @@ -69,6 +69,10 @@ fi existing="$(curl -fsS -o /dev/null -w '%{http_code}' \ "$GITEA_BASE/api/packages/$GITEA_OWNER/generic/felhom-agent/$VERSION/felhom-agent" 2>/dev/null || true)" [[ "$existing" != "200" ]] || die "version $VERSION is ALREADY PUBLISHED — bump the version instead" +# R-1098 (2026-10-16): only a clean 404 proves the version is new. Until now a registry that could not be reached +# (curl prints 000) or answered anything else passed this check — „I could not ask" is not that proof. +# Test: scripts/test_release_push_once.sh. +[[ "$existing" == "404" ]] || die "could not establish whether $VERSION is published (registry answered HTTP '${existing:-none}') — refusing" # ── 3. Build ──────────────────────────────────────────────────────────────────────────────────── BIN="$(mktemp -t felhom-agent-XXXXXX)" diff --git a/scripts/test_release_push_once.sh b/scripts/test_release_push_once.sh new file mode 100644 index 0000000..d747aac --- /dev/null +++ b/scripts/test_release_push_once.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# test_release_push_once.sh — release-agent.sh releases a version only when the registry says it is NEW (R-1098). +# +# The REAL release-agent.sh runs from a copy inside a scratch git repo (so its tag check reads scratch tags), with +# RELEASE_ALLOW_DIRTY=1 (the clean-tree gate has its own history) and a fake `curl` and `go` first on PATH: curl answers +# the package probe as FAKE_MODE says, and `go` stops the run right after the check, so nothing is built or published. +# published (200) -> refused, "ALREADY PUBLISHED" +# absent (404) -> passes the check, reaches the build +# unreachable (000) -> refused, "could not establish" +# server error (500) -> refused, "could not establish" +# the tag already exists -> refused, "already exists" +# Run: bash scripts/test_release_push_once.sh (exit 0 all cases right · 1 a case wrong) +set -uo pipefail +HERE="$(cd "$(dirname "$0")" && pwd)" +WS="$(mktemp -d)"; trap 'rm -rf "${WS:?}"' EXIT +fails=0; ran=0 +mkdir -p "$WS/bin" "$WS/repo/scripts" +cp "$HERE/release-agent.sh" "$WS/repo/scripts/release-agent.sh" +(cd "$WS/repo" && git init -q -b main && git -c user.name=t -c user.email=t@t commit -q --allow-empty -m init) +cat > "$WS/bin/curl" <<'FAKE' +#!/bin/bash +case "${FAKE_MODE}" in + published) printf 200; exit 0 ;; + absent) printf 404; exit 22 ;; + down) printf 000; exit 7 ;; + error) printf 500; exit 22 ;; +esac +FAKE +printf '#!/bin/sh\necho "FAKE-GO-REACHED $*"; exit 1\n' > "$WS/bin/go" +chmod +x "$WS/bin/curl" "$WS/bin/go" +run() { # name, mode, expected rc, required fragment, forbidden fragment + local out rc + out=$(cd "$WS/repo" && FAKE_MODE="$2" RELEASE_ALLOW_DIRTY=1 GITEA_USER=u GITEA_TOKEN=t PATH="$WS/bin:$PATH" \ + bash scripts/release-agent.sh 9.9.9 2>&1); rc=$? + ran=$((ran+1)) + if [[ "$rc" != "$3" ]] || ! grep -qi -- "$4" <<<"$out" || { [[ -n "$5" ]] && grep -qi -- "$5" <<<"$out"; }; then + fails=$((fails+1)); echo " FAIL $1 (exit $rc)"; echo "$out" | tail -4 | sed 's/^/ /'; return + fi + echo " ok $1" +} +echo "release-agent.sh — a version is released once, and only when the registry says it is new" +run "published (200) -> refused" published 1 "ALREADY PUBLISHED" "FAKE-GO-REACHED" +run "absent (404) -> proceeds" absent 1 "FAKE-GO-REACHED" "could not establish" +run "unreachable (000) -> refused" down 1 "could not establish" "FAKE-GO-REACHED" +run "server error (500) -> refused" error 1 "could not establish" "FAKE-GO-REACHED" +(cd "$WS/repo" && git tag v9.9.9) +run "the tag exists -> refused" absent 1 "already exists" "FAKE-GO-REACHED" +echo +if [[ $fails -eq 0 ]]; then echo "all $ran cases behaved"; exit 0; fi +echo "$fails of $ran cases FAILED"; exit 1