fix: suite asks for uncompressed bytes in the Content-Length check; boot-check replaces operator-only carlos vet; scratch rule

Found by the Go pilot: Carloku's edge gzips, so the header is the
compressed size; carlos vet needs operator access.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev daea4d52a7da0cf47314aeab5c47c671d010bc75 parent c26bdf6
3 files changed, +42 −5
  • BRIEF.md +12 −3
  • scripts/boot-check.sh +26 −0
  • tests/behaviour/robustness.spec.ts +4 −2
diff --git a/BRIEF.md b/BRIEF.md
index 17148fc..4a0a8cb 100644
--- a/BRIEF.md
+++ b/BRIEF.md
@@ -124,13 +124,22 @@ carlos instances create --account bfj --app todo-<slug> --host todo-<slug>.bfj.o
After that, `make deploy` runs
`carlos deploy --account bfj --app todo-<slug> --target linux-arm64 --message "<what changed>" dist/app-linux-arm64`.
The version is the git short SHA of your commit. Before the first
-deploy, run `carlos vet --boot` on the shipped version as
-`references/platform.md` describes. Then run the shared suite against
-`https://todo-<slug>.bfj.oncarlos.com`. It must pass there too.
+deploy, run `scripts/boot-check.sh dist/app-linux-arm64`. It starts the
+arm64 binary under qemu on a unix socket, checks `/healthz` and
+`/api/version`, and checks for a clean exit on SIGTERM. (`carlos vet`
+needs operator access, which you don't have.) After deploying, run the
+shared suite against `https://todo-<slug>.bfj.oncarlos.com`. It must pass
+there too.
If a `carlos` command refuses or behaves unexpectedly, read its `-h` and
`references/platform.md` before trying something else.
+## Scratch files
+
+`$TMPDIR` is shared with other sessions. Make your own directory with
+`mktemp -d -p "$TMPDIR"`, keep every scratch file in it, and delete only
+that directory. Never delete with a glob in `$TMPDIR`.
+
## Git
Commit on your branch (`app/<slug>`) as you go, with clear messages
diff --git a/scripts/boot-check.sh b/scripts/boot-check.sh
new file mode 100755
index 0000000..6677c80
--- /dev/null
+++ b/scripts/boot-check.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+# The platform boot check, run locally on the shipped arm64 binary under
+# qemu: start it on a unix socket, require 200 from /healthz and
+# /api/version, then require a clean exit on SIGTERM.
+# scripts/boot-check.sh <dist/app-linux-arm64>
+set -euo pipefail
+bin=$(cd "$(dirname "$1")" && pwd)/$(basename "$1")
+root=${BAKEOFF_TOOLCHAINS:-$HOME/.local/share/bakeoff-toolchains}/arm64root
+work=$(mktemp -d -p "${TMPDIR:-$HOME/.cache}")
+pid=
+trap '[ -n "$pid" ] && kill "$pid" 2>/dev/null; rm -rf "$work"' EXIT
+cd "$work"
+CARLOS_HOST=boot-check.invalid qemu-aarch64-static -L "$root" "$bin" --socket "$work/app.sock" --db "$work/app.db" > "$work/out.log" 2>&1 &
+pid=$!
+for _ in $(seq 1 100); do [ -S "$work/app.sock" ] && break; sleep 0.2; done
+[ -S "$work/app.sock" ] || { echo "boot-check: no socket after 20 s"; cat "$work/out.log"; exit 1; }
+for path in /healthz /api/version; do
+ code=$(curl -s -o "$work/body" -w '%{http_code}' --unix-socket "$work/app.sock" "http://boot-check.invalid$path")
+ [ "$code" = 200 ] || { echo "boot-check: $path answered $code"; exit 1; }
+ echo "boot-check: $path 200 $(head -c 60 "$work/body")"
+done
+kill -TERM "$pid"
+set +e; wait "$pid"; status=$?; set -e
+pid=
+[ "$status" = 0 ] || { echo "boot-check: exit status $status after SIGTERM"; exit 1; }
+echo "boot-check: clean exit on SIGTERM"
diff --git a/tests/behaviour/robustness.spec.ts b/tests/behaviour/robustness.spec.ts
index 43c4949..a52f24a 100644
--- a/tests/behaviour/robustness.spec.ts
+++ b/tests/behaviour/robustness.spec.ts
@@ -23,10 +23,12 @@ test('round-trips UTF-8 and sends correct Content-Length', async ({ page }, ti)
await addTodo(page, ti, title);
await page.reload();
expect(await todos(page).nth(0).locator('label').textContent()).toBe(title);
- const res = await page.request.get('/');
+ // Ask for the bytes as the app sends them: an edge that compresses
+ // reports the compressed size, which the decoded body can't match.
+ const res = await page.request.get('/', { headers: { 'Accept-Encoding': 'identity' } });
const body = await res.body();
const declared = res.headers()['content-length'];
- if (declared !== undefined) expect(Number(declared)).toBe(body.length);
+ if (declared !== undefined && !res.headers()['content-encoding']) expect(Number(declared)).toBe(body.length);
expect(body.toString('utf8')).toContain(title);
});