Skip to content

release-train: staging -> main - #473

Merged
tracebloc-release-train[bot] merged 14 commits into
mainfrom
release-train/to-main
Jul 29, 2026
Merged

release-train: staging -> main#473
tracebloc-release-train[bot] merged 14 commits into
mainfrom
release-train/to-main

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Automated promotion by the release train (RFC-0008 D14). Head is the train-managed release-train/to-main branch (a mirror of staging), so it never collides with a human PR. Merged only when the fr-gate is green.


Note

Medium Risk
Touches fleet-facing install/bootstrap paths, Helm release gating, and MySQL image pinning; risk is mitigated by extensive bats/Pester/drift coverage but mistakes could block installs or mis-publish releases.

Overview
Automated staging → main promotion packaging installer, chart, CI, and doc changes accumulated on the train.

Release & chart: Helm chart 1.9.7; release-helm-chart gains a shared verify job (tag vs Chart.yaml, demote mis-marked stable non-final tags) that release and sign-installer-manifest both depend on. images.mysqlClient is pinned to a 5.7-lineage digest (with a helm unittest asserting @sha256: rendering).

Installers (bash + PowerShell):Execute-gates for kubectl/k3d/helm; bounded k3d create / helm install (spin deadlines, TB_*_TIMEOUT_MIN); stricter preflight (e.g. auth.docker.io, conditional tool-download hosts; early_data_dir_guard before logging on NFS homes); Windows improvements (Docker wait, pinned k3d/helm without GitHub API, GPU job progress/remedies, UNC-safe jobs). scripts/manifest.sha256 updated for touched libs.

Docs & tests: New RFC-CLIENT-0002 (least-privilege install). check-drift adds execute-gate and preflight-host parity checks; e2e auto-upgrade asserts --reuse-values ingestor-pin behavior by baseline era.

Reviewed by Cursor Bugbot for commit 6267ee9. Bugbot is set up for automated code reviews on this repo. Configure here.

LukasWodkaand others added 14 commits July 27, 2026 16:40
…roaming-profile machines (#409) (#437)
PowerShell background jobs spawn their runspace in $HOME; on managed
machines with roaming profiles that is a UNC share, and every cmd.exe a
job starts there prints 'CMD.EXE was started with the above path as the
current directory. UNC paths are not supported.' plus a RemoteException
error record — 2-6 alarming red blocks on an otherwise healthy install.
All five Start-Job sites (wsl --update, wsl --set-default-version,
wsl --list, NCT install, NCT verify) now pass a shared
-InitializationScript that pins the job to $env:SystemRoot (always
local; no-op off-Windows). An AST-based Pester gate fails any future
Start-Job that forgets it.
Closes#409
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…lookups (re-land #410 onto develop) (#446)
* fix(installer): pin k3d + helm versions — drop api.github.com latest lookups (#410)
Ports the bash pins (#382) to the Windows installer: K3D_VERSION defaults
to v5.9.0 and HELM_VERSION to v4.2.3 (lockstep with scripts/lib/common.sh
until #435 single-sources them), env-overridable, validated against a
release-tag shape before any URL is built (path-traversal gate mirroring
the bootstrap and cli install.ps1).
The unauthenticated releases/latest API allows 60 req/hour per IP — one
shared corporate NAT exhausts it and fails installs (observed live on a
customer install 2026-07-27). The literal value 'latest' still works but
resolves API-free: the /releases/latest redirect Location for k3d, and
get.helm.sh/helm-latest-version for helm — exactly like lib/setup-linux.sh.
A Pester gate now fails the suite if any https://api.github.com fetch
reappears in the installer.
Closes#410
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): restore retry parity on the 'latest' version resolvers (Bugbot #438)
The new API-free 'latest' resolvers ran a single request — the old
lookups and lib/setup-linux.sh retry 3x5s. Resolve-ToolVersion now
drives the resolver through Invoke-WithRetry (resolvers throw on
failure), so a one-off network blip on flaky corporate egress retries
instead of aborting the install; a persistent failure still fails
closed with the pin-a-tag remedy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): bound the 'latest' lookup requests (-TimeoutSec 30, Bugbot #438)
A host that accepts the TCP connect but never responds would hang the
version resolvers indefinitely; the bash peers bound this with
--connect-timeout 15 --max-time 30. Both resolver requests now carry
-TimeoutSec 30; a timeout throws, so the retry ladder + fail-closed
remedy from the previous commit take over. AST-based Pester gate keeps
the timeouts in place.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Lukas Wuttke <lukas@tracebloc.io>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…447)
* fix(installer): fail fast on k3d spawn failure; bound the create wait (#439)
* fix(installer): fail fast on k3d spawn failure; bound the create wait (#412, #426 Windows half)
Two fixes to New-K3dCluster's create path, both observed live:
1. Start-Process failure ('%1 is not a valid Win32 application' from a
broken k3d.exe) is non-terminating by default, leaving $k3dProc null —
and 'while (-not $null.HasExited)' is always true, so the installer
spun 'Creating compute environment...' forever over a dead install.
The spawn now runs under -ErrorAction Stop in a try/catch that cleans
up the temp logs + proxy config and fails with the real exception,
the log path, and a remedy.
2. k3d cluster create --wait has no timeout of its own, so a stalled
image pull spun the spinner indefinitely. The wait is now bounded
(15 min default, TB_CREATE_TIMEOUT_MIN override): on expiry the
process is killed, the last stderr lines and the install-log path are
printed, and the install fails loudly. Extracted as
Wait-ProcessWithDeadline so the deadline/kill path is unit-tested.
Closes#412. Windows half of #426 (the bash half — k3d create + helm
timeouts in cluster.sh / install-client-helm.sh — follows separately).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): remove the partial cluster when the create wait times out (Bugbot #439)
Killing k3d mid --wait skips its own rollback, so the timeout path left
a half-created cluster behind — and the next run's reuse path would see
serversRunning > 0 and print 'Compute environment already running' over
a broken environment. The timeout path now deletes the partial cluster
(bounded at 2 min via Wait-ProcessWithDeadline) before failing, and
tells the operator the exact manual command if the delete itself fails.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): Docker engine wait — 10 minutes, elapsed progress, named failure state (#440)
* fix(installer): Docker engine wait — 10 minutes, elapsed progress, named failure state (#413)
A first-ever Docker Desktop start on AV-heavy corporate machines
routinely needs 5-10 minutes (WSL bootstrap, image unpack). The old
3-minute cap turned a normal cold start into a failed install plus a
manual re-paste of the one-liner — observed as a recurring wait-then-
re-run loop on hospital installs.
- Wait bound: 3 min -> 10 min default, TB_DOCKER_WAIT_MIN override.
- After the first minute the spinner shows elapsed minutes and the
expected worst case, so the wait doesn't read as a hang.
- On expiry the failure names the observed state: Docker Desktop
process gone (start it / fix its error window) vs. running with the
engine still down (tray-icon guidance) - instead of one generic line.
Closes#413
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): exited-Docker path drops the slow-start advice (Bugbot #440)
When Docker Desktop's process has exited, 'a first start can be slow'
and the TB_DOCKER_WAIT_MIN hint contradict the diagnosis and steer
operators toward raising the wait instead of restarting/fixing the
crash. The slow-start reassurance + override hint now print only on
the engine-still-starting path; the exited path fails with its own
start-and-fix remedy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#441)
* fix(installer): run the network-FS guard before the log dir is created (#432)
setup_log_file mkdirs HOST_DATA_DIR and tees the whole session's output
onto it BEFORE run_preflight fires — so on the exact machine the
network-FS guard was built for (NFS home + sudo + root_squash), the
unguarded mkdir failed with a bare error, or the log dir landed
squashed/nobody-owned, before the friendly named failure could print.
New early_data_dir_guard runs right after validate_config and before
setup_log_file: same filesystem classification (extracted as the shared
_pf_is_network_fstype), console-only, silent on local/undetermined
filesystems, defers to the full check's warning under
TRACEBLOC_ALLOW_NETWORK_FS. The call is declare -F-guarded so a stale
bootstrap without the new helper proceeds as before.
Closes#432
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): early NFS guard names a followable remediation (Bugbot #441)
The guard advised HOST_DATA_DIR=/local/path, but validate_config
requires the data dir under $HOME (Bugbot #384 hardening) — so the
exact audience this guard exists for (network home) was pointed at a
fix that fails validation on re-run. The copy now states the real
constraint and the two workable paths (local-home user, or the explicit
TRACEBLOC_ALLOW_NETWORK_FS=1 override with its risk), and notes that
datasets may stay on network storage via HOST_DATASET_DIR. Test asserts
the impossible advice stays gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): early NFS guard skips an existing data dir (Bugbot #441 r2)
The early guard's job is protecting the pre-log mkdir; an existing data
dir has no at-risk mkdir, and a healthy machine's re-run must keep
reaching the assess hand-off exactly as it did when the network-FS check
lived only in run_preflight. Existing dir -> silent pass; the full
preflight guard still classifies network storage for real (re)installs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…angs (bash half of #426) (#442)
* fix(installer): bound k3d create and the helm calls — no indefinite hangs (#426, bash half)
k3d cluster create --wait had no deadline of its own and its spinner none
either: a stalled image pull (rate-limited registry, TLS-intercepting
proxy) span the create forever. Both helm invocations (install +
reconcile) ran under the deadline-less spin_cmd and could hang the same
way against a wedged kube-apiserver.
- create: --wait now always pairs with --timeout (default 15m,
TB_CREATE_TIMEOUT_MIN override — the same env knob the Windows
installer adopted); k3d aborts with a real error that the existing
failure path dumps. spin() gained an optional deadline, used as a
+5min backstop in case k3d itself wedges past its own timeout.
- helm install + reconcile: new spin_cmd_bounded (spin_cmd + hard
deadline, rc 124 with an explicit timeout note + log tail), default
10m via TB_HELM_TIMEOUT_MIN.
Completes #426 (the Windows half shipped with the #439 stack).
Closes#426
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): spin's deadline kills the wrapper's children too (Bugbot #442)
The create backstop passes the `( k3d … )` wrapper-subshell PID into
spin, and the deadline path signalled only that PID — orphaning k3d,
which kept creating the cluster after the installer had already exited
as failed, racing any retry. The deadline path now TERMs the children
while the parent is still alive (afterwards they reparent to init and
pkill -P can't see them), then the wrapper, with a KILL sweep after the
grace period. Harmless when bash exec-optimizes the wrapper away. Test
proves the child dies with the wrapper.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): reparent-safe KILL sweep + explicit create-timeout UX (Bugbot #442 r2)
1. spin's deadline path captured no child PIDs, so once the TERM'd
wrapper died, its children reparented to init and the pkill -KILL -P
sweep found nothing — a TERM-immune k3d could survive the backstop.
Child PIDs are now captured BEFORE any signal and the KILL sweep
addresses them directly; test proves a trap-''-TERM child dies.
2. When the create backstop fires, the k3d log is often empty (hung
daemon) — the operator saw a bare failure with no timeout hint. The
124 path now names the timeout, points at TB_CREATE_TIMEOUT_MIN, and
deletes the partially created cluster (bounded) so a re-run cannot
adopt it via the 'already exists' branch — parity with the Windows
fix on #439.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): spin's deadline path is set -e-proof (Bugbot #442 r3)
Bare pkill (returns 1 with no children), kill on a reaped pid, and wait
after a kill (reports the signal) could each abort the deadline path
before 'return 124' under the installer's set -e — callers would see
143/1: no timeout copy, no partial-cluster cleanup, no bounded note.
Every signal step is now failure-proofed, and a bash -c 'set -euo
pipefail' end-to-end test pins the 124 contract for the childless
(exec-optimized) case that reproduces the abort.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): helm timeout names the pending-release unwedge (Bugbot #442 r4)
A SIGKILLed helm (spin_cmd_bounded deadline) can leave the release
wedged as pending-install/pending-upgrade — the next run then fails
with Helm's 'another operation is in progress' and no guidance. Both
call sites now capture the rc (if-! discarded it) and, on 124, print
the exact unwedge commands (uninstall for a half-installed release,
rollback for an upgrade) before the error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(installer): fix over-broad grep assertions on the helm-bound tests
Count invocation/hint lines, not comment mentions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): rollback hint names the release, not the namespace (Bugbot #442 r5)
The adopt path tracks release ($_rel) and namespace ($_ns) separately;
the reconcile-timeout unwedge hint said 'helm rollback $_ns', which is
a non-working command whenever they differ.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): base-10-normalize the timeout knobs — octal trap defused (Bugbot #442 r6)
TB_CREATE_TIMEOUT_MIN / TB_HELM_TIMEOUT_MIN sanitizers accepted 08/09,
which bash arithmetic reads as invalid octal — aborting $(( … )) under
set -e mid-create (k3d already backgrounded → partial cluster left
behind), and 010 silently became 8. One shared, unit-tested helper
(tb_minutes_or) now normalizes via 10# at all three call sites.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* docs(rfc): least-privilege install (0001) — for discussion
Draft RFC: the installer assumes blanket root/sudo, which excludes non-admin
users (hospital/university/HPC researchers). Audit shows the ONLY privileged
surface is the container runtime + two kernel modules; tools + the cluster are
already user-space. Proposes a tiered model (zero-root when a runtime exists →
rootless → one scoped privileged step) and ties in A2 (sudo/root) + B2 (PATH).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* RFC 0001: record decisions (Docker-only, kernel=Tier-2, build prepare-host + host audit, WSL2=Linux, detection yes)
Resolves the five open questions from the first draft per Lukas 2026-07-22:
Docker as the single runtime (rootless Docker at Tier 1); kernel modules a hard
Tier-2 requirement with no userspace fallback; build a standalone prepare-host
step plus a short host-audit report shared with doctor; WSL2 treated as Linux and
native Windows preferring rootless Docker; and yes to side-effect-free detection.
Rollout re-sequenced so detection + audit land first (also closing A2/B2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* RFC 0001: adopt @saadqbal review — rootless Docker as primary target
Folds in Asad review of #369. Key flip: rootless Docker is the primary path,
not a fallback, which dissolves the kernel-module question (fuse-overlayfs +
slirp4netns remove overlay/br_netfilter from the privileged surface). Cant-modprobe
now falls to rootless rather than failing; we probe cgroup v2 + unprivileged userns
instead. prepare-host shipped as snippet + subcommand; WSL2 prefers rootless over
Docker Desktop (licensing); detection uses docker-info + the id/sudo/sudo-n trio.
Five open questions collapse to one hands-on spike: validate rootless Docker as the
k3d backend across target hosts. Rollout re-sequenced so that spike leads the core
build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(rfc): renumber to RFC-CLIENT-0002 and record the accepted status
Two fixes to get this RFC out of limbo.
**Renumber 0001 -> 0002.** `client` already has a `0001-rootless-spike.md` on
develop, so this file collided with it inside its own repo. 0002 is the next
free number in this repo. The header now carries the qualified ID
`RFC-CLIENT-0002` — RFC numbers are per-repo, so a bare "RFC 0001" names four
different documents across the org.
**Status: Accepted (2026-07-25).** The design has plainly been accepted in
practice: the tracking epic tracebloc/backend#1168 is 6-of-9 children merged,
including the foundation, tier routing, sudo handling, Tier 0 and the rootless
spike. Leaving the document as a permanent "do not merge" draft left the repo
with no record of a decision the team had already acted on. Implementation
status stays where it belongs — on the epic, not in this header.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…#415) (#449)
The NVIDIA container toolkit setup ran entirely through Log (file-only),
so the console sat blank through the 30s WSL probe, the multi-hundred-MB
Ubuntu install, and the 180s toolkit install — and on failure printed a
vague "set it up manually inside WSL later" with no actual commands.
- Add Wait-JobWithProgress: spinner + elapsed/timeout heartbeat while a
background job runs, bounded, stops the job on timeout. No GPU sub-step
now goes silent for more than ~2s.
- Add Show-GpuManualRemedy: copy-pastable install commands (same
--tlsv1.2 + connect/max-time floor as the automated path) plus a
`tracebloc doctor` follow-up, printed on every timeout/failure branch.
- Ubuntu install is now a progress-tracked job instead of a silent
`cmd /c ... | Out-Null`.
- Visible Info intro (optional; CPU mode works either way) + Ok on
success; drop the vague dead-end copy.
- Extract shared spinner frames ($script:SpinnerFrames).
- 11 Pester tests.
Acceptance: no silent window >10s; timeout output carries runnable
remedies. Kept in Step 1 (the --gpus flag must be decided before
New-K3dCluster; moving it would force cluster recreation, #431).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ge prod image (#454)
* fix(chart): pin mysql-client by digest — keep fleets on the 5.7-lineage prod image (backend#723)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(e2e): era-aware --reuse-values pin assertion — the published release now carries the prod pin
The path-1 assertion hardcoded the pre-#398 era: it expected NO ingestor
pin after a --reuse-values upgrade because the published release's
computed values predated images.ingestor.prodDigest. The #383 promotion
(2026-07-27) published a release that includes the pin, so replayed
computed values now carry it and the assertion fails on every
chart-touching PR (first hit: #454).
The expectation is now read from the baseline release itself
(helm get values --all): pin absent => must not arrive (old behavior);
pin present => the SAME digest must be replayed verbatim. Both eras keep
asserting the actual limitation: --reuse-values never injects new chart
defaults. Path-2's era-stale comment corrected, with a note on the
replay-contamination signal a future pin bump will surface.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(e2e): fail fast when jq is missing (review: local runs, not just CI runners)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(e2e): one era boundary, stated once (the #383 promotion); track the path-2 tripwire in #459 (review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(chart)+test: single-arch pin note + render assertion for the digest path (review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ow carries the prod pin (#455)
* fix(e2e): era-aware --reuse-values pin assertion — the published release now carries the prod pin
The path-1 assertion hardcoded the pre-#398 era: it expected NO ingestor
pin after a --reuse-values upgrade because the published release's
computed values predated images.ingestor.prodDigest. The #383 promotion
(2026-07-27) published a release that includes the pin, so replayed
computed values now carry it and the assertion fails on every
chart-touching PR (first hit: #454).
The expectation is now read from the baseline release itself
(helm get values --all): pin absent => must not arrive (old behavior);
pin present => the SAME digest must be replayed verbatim. Both eras keep
asserting the actual limitation: --reuse-values never injects new chart
defaults. Path-2's era-stale comment corrected, with a note on the
replay-contamination signal a future pin bump will surface.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(e2e): fail fast when jq is missing (review: local runs, not just CI runners)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(e2e): one era boundary, stated once (the #383 promotion); track the path-2 tripwire in #459 (review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#416) (#450)
* feat(installer): preflight probes every download host, cross-OS + hard (#416)
Preflight proved connectivity to registries + tracebloc endpoints, then Step 1
downloaded from hosts it never probed — so an all-green preflight was followed
~30s later by a blocked-download failure on TLS-intercepting / allowlist
networks, misleading the IT contact who just watched preflight pass.
- Probe every download host the default path fetches from, per-OS and HARD
(a blocked one is now a named red preflight line, not a warn), but only when
the fetch will actually happen (tool/app absent — a present tool is never
re-downloaded, so its host isn't probed).
* always: auth.docker.io (Docker Hub token host — allowed registry-1 but
blocked token host used to fail only at in-cluster pull time)
* Linux: get.docker.com, download.docker.com, github.com +
objects.githubusercontent.com, dl.k8s.io, get.helm.sh
* macOS: raw.githubusercontent.com (Homebrew), desktop.docker.com
* Windows: desktop.docker.com, dl.k8s.io, get.helm.sh, github.com +
objects.githubusercontent.com
- objects.githubusercontent.com is probed explicitly: release assets 302 there
and _pf_probe_url does not follow redirects, so github.com passing proved
nothing about the asset host.
- GPU hosts are deliberately excluded (GPU setup is optional; #415 handles its
failure with runnable remedies — hard-failing preflight would contradict that).
- New check-drift.sh parity check (_drift_preflight_hosts) locks the shared-core
host set so the two installers can't drift apart.
Fixes both halves in lockstep (preflight.sh + install-k8s.ps1). Tests: +5 bats
(preflight), +2 bats (drift), +7 Pester; the one existing warn-only assertion
(preflight.bats) is updated to the new hard behaviour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): Docker-engine host is warn, not hard; egress hint names all hosts (Bugbot #416)
Bugbot found the download-host hard-probing over-generalized which host each path
fetches Docker from, so a blocked but UNUSED host could abort a supported install:
- macOS: desktop.docker.com hard-failed headless Macs that use Colima via brew.
- Linux: get.docker.com / download.docker.com both hard, but pacman/zypper/Amazon
use distro repos and RHEL clones use only download.docker.com.
The Docker-engine install host is path/distro/environment-dependent, so it's now
WARN-only (soft bucket) on Linux + macOS. The k8s tool binaries (dl.k8s.io,
get.helm.sh, github.com + objects.githubusercontent.com) stay HARD — they're
always direct-downloaded from the same host. Windows is unchanged: Docker Desktop
is its sole path, so install-k8s.ps1 keeps desktop.docker.com hard.
Also (Bugbot medium): the egress hint listed only the old always-critical hosts;
it now names the tool-download hosts too, so a red line for a blocked download
host has matching remediation. Mirrored in preflight.sh + install-k8s.ps1.
Updated the two #416 bats tests that asserted the old hard behavior to assert
warn-only for the Docker-engine host.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): macOS also probes github.com for the Homebrew clone (Bugbot #416)
install_homebrew fetches the install script from raw.githubusercontent.com and
then git-clones Homebrew/brew + core from github.com. Preflight probed only the
raw host, so a network that allows it but blocks github.com passed preflight then
failed during Homebrew setup — the same multi-host gap already closed for k3d via
objects.githubusercontent.com. Probe github.com too on macOS when brew is absent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): probe formulae.brew.sh; drift check extracts probe URLs (review #416)
Two reviewer findings (saadqbal):
1. formulae.brew.sh unprobed on macOS: `brew install` pulls formula METADATA from
formulae.brew.sh (bottles come from ghcr.io, already probed), and it's hit even
when brew is already installed. A blocked metadata host = green preflight then a
failed `brew install`. Now hard-probed on macOS whenever a brew-installed tool
(kubectl/k3d/helm/docker) is absent.
2. Drift check couldn't detect a deleted probe: it grepped the whole file, so each
shared host also matched inside comments and the egress-hint strings — passing
even if a real probe line were removed (the AC wasn't enforced; the bats cases
only passed because the fixtures lacked that text). check-drift now extracts
only the hosts in an actual PROBE URL (bash "…|https://host/…", ps1
url = "https://host/…") and diffs those. tracebloc.github.io drops from the
shared set (ps1 probes it via $TRACEBLOC_HELM_REPO_URL, not a literal; Check 1
already pins the host map). Rewrote the drift bats fixtures to real probe
entries + added a case proving a comment/hint-only host is still flagged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): tighten drift host extraction + formulae.brew.sh trigger (Bugbot #416)
Two Bugbot findings, both refining the earlier reviewer fixes (not reverting them):
- Drift check (ps1 side) matched ANY `url = "https://…"`, including the winget
bootstrap download line, so deleting the real k3d github.com probe still passed.
Scope the extractor to hashtable probe entries (require `label =` on the line),
so only genuine preflight probes count. Added a bats case with a stray $url=
download line + a deleted probe -> now correctly flags drift.
- formulae.brew.sh was hard-probed on `! has docker` too, but GUI Macs install
Docker Desktop (desktop.docker.com), not brew — so docker-only-missing would
hard-fail a host the install never uses. Trigger only on kubectl/k3d/helm (the
tools that always install via brew). Added a bats case for docker-only-missing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): probe formulae.brew.sh for headless (Colima) docker installs too (Bugbot #416)
Round-3 dropped docker from the formulae.brew.sh trigger to avoid a GUI-Mac
false-fail (Docker Desktop, not brew). But headless Macs install colima/docker
via `brew install`, which DOES hit formulae.brew.sh — so docker-only-missing on a
headless box went green in preflight then failed in Step 1. Make it path-aware:
add a _pf_has_gui_session helper (mirrors setup-macos.sh) and probe formulae.brew.sh
for a missing docker only when there's NO GUI session (the Colima/brew path). GUI
Macs still skip it (Docker Desktop). Added bats cases for both GUI and headless.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(installer): consolidate macOS Docker-path egress probing (Bugbot #416)
Fold the r3/r4/r5 point-fixes into one GUI-aware block keyed on
_pf_has_gui_session, so each host is hard-probed only on the path that fetches it:
- kubectl/k3d/helm absent -> formulae.brew.sh (always brew).
- docker absent + GUI -> desktop.docker.com HARD (the actual Docker Desktop path;
was warn-only -> a blocked CDN passed preflight then failed mid-download, r5).
- docker absent + headless -> formulae.brew.sh (colima/docker via brew, r4).
desktop.docker.com leaves the warn bucket entirely (it's hard on GUI, unprobed on
headless). No behaviour is looser than before; the GUI Desktop CDN is now caught.
Tests refreshed: GUI vs headless for both desktop.docker.com and formulae.brew.sh.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… Step 1 (#411) (#451)
* fix(installer): execute-gate installed tools so a broken binary fails Step 1 (#411)
The only post-install "verification" was a log-only, failure-masking
interpolation, so a corrupt or wrong-architecture binary (winget shims /
partial installs skip checksum verify; brew delivers with no checksum of ours)
still printed "System tools" and only died at cluster-create in Step 2.
All three OSes had the gap.
- New shared execute-gate helpers: assert_tool_runs (common.sh, bash) and
Assert-ToolRuns (install-k8s.ps1, PowerShell). Each runs the tool's self-check;
on non-zero exit or an exception it removes the located binary and fails loudly
with an arch-aware remedy, so the tool step fails instead of the cluster step.
- Gate kubectl / k3d / helm after install on Linux (setup-linux.sh), macOS
(setup-macos.sh), and Windows (install-k8s.ps1), on both the fresh-install and
already-present paths. "System tools" success only prints once all gates pass.
- kubectl is gated with `version --client` (NOT --short — removed in kubectl
1.28+, which would false-fail the gate).
- New check-drift.sh parity check (_drift_execute_gates) so no installer can
silently drop a gate for a tool.
Tests: +2 bats (common — working tool passes / broken tool errors + removes the
binary), +2 bats (drift parity), +7 Pester (exit-nonzero / exception /
binary-removal / arch remedy / static gates).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(installer): make setup-linux tool mocks runnable for the execute-gate (#411)
The execute-gate (#411) runs `<tool> version` after install, but the
setup-linux.bats harness only marked tools "present" via has() — it never
provided a RUNNABLE k3d/kubectl/helm. On the toolless CI runner the gate's
probe found no binary and failed 4 install_k3d tests; locally it false-passed
because a real k3d was on PATH. Add silent (non-recording) runnable stubs to
setup() so the gate has something to execute and they shadow any real tool on a
dev host. Test-only; no production change.
* fix(installer): gate only removes binaries we placed; helm bare version; drift extracts calls (review #411)
Three review findings on #411 (saadqbal + Bugbot):
- Execute-gate deleted the wrong binary on the already-present path. Removal is now
OPT-IN via `assert_tool_runs --rm <path>` / PS `-BinPath`, passed ONLY by the
fresh-install callers that placed the binary. On the present / brew / winget path
we don't pass it, so a broken pkg-managed binary is left in place (deleting a brew
symlink just wedges the re-run, and the bad copy may be elsewhere on PATH). bash:
--rm on Linux fresh-install, none on present or macOS/brew. ps1: -BinPath tracks
the direct-download dest ($null for winget/present).
- helm was gated with `version --short`; --short can be dropped like kubectl's was,
which would false-fail the gate and (previously) delete a good binary. Use bare
`helm version` on all three OSes.
- _drift_execute_gates used a whole-file grep that could match comments. It now
strips comment lines and matches the actual call (`assert_tool_runs … <t> version`
/ `Assert-ToolRuns -Name "<t>"`), handling the new --rm form. (no grep -q under
pipefail — SIGPIPE would false-fail.)
Tests: common.bats split into --rm-removes vs no-rm-leaves; check-drift.bats adds
--rm-form + commented-out-gate cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): execute-gate removes a broken binary only if WE placed it AND it ran (Bugbot #411 r2)
Round-1 made removal opt-in but only on the fresh-install path, so a broken
installer-placed kubectl/k3d on the ALREADY-PRESENT path (left by a prior run)
stayed → `has` true → re-run couldn't self-heal (Bugbot). helm already handled it.
Unify all three: callers pass --rm "$TB_TOOLS_DIR/<tool>" (bash) / -BinPath
"$TOOL_DIR\<tool>.exe" (ps1) on EVERY path, and the gate removes that path only
when the binary that actually ran resolves to it — `command -v … -ef <path>` on
bash, `(Get-Command).Source -eq $BinPath` on ps1. So a broken copy we own (fresh
or prior-run) self-heals, while a brew/winget/pkg-manager copy elsewhere on PATH
is never touched (satisfies the earlier reviewer guard too). Dropped the now-moot
$k3dDest/$helmDest null-tracking and helm's -f branch.
Tests: common.bats +decoy-copy case; install-k8s.Tests.ps1 removal split into
ran-here (removed) vs resolved-elsewhere (left); check-drift already covers --rm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… train) (#460)
* ci(release): tag/chart-version guard + strict stability rule (backend#1301 Q5)
The tag's base X.Y.Z must equal client/Chart.yaml's version (train-cut or
manual), and any non-plain-semver tag must be a PRE-release -- otherwise
it would become 'latest' (the installer bootstrap) and enter the helm
index as stable. Tag passed via env per R8 (backend#889).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(release): guard gates BOTH jobs + self-healing demotion (Bugbot)
The guard lived only inside the release job; sign-installer-manifest has
no needs and kept stamping + attaching installers for a bad release --
which, left marked stable, already IS 'latest' (what the bootstrap
resolves). Restructured: a dedicated verify job gates both jobs. And
instead of fail-and-strand, an unmarked non-final tag is DEMOTED to
prerelease ('latest' snaps back to the previous stable) and publishing
continues as a proper pre-release. Chart-version mismatch stays a hard
fail -- nothing to auto-fix without a bump.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(release): demote before hard-fail (Bugbot round 2)
A release that is BOTH mis-marked stable AND chart-version-mismatched hit
the exit 1 before the demotion ran, stranding it as 'latest' with no
assets (404ing bootstrap). Demotion now runs first -- it is safe in
isolation and must not be skippable by any other failure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

👋 Heads-up — Code review queue is at 42 / 30

Above the WIP limit. The team convention is to review existing PRs before opening new work.

Open PRs currently in Code review (oldest first):

Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.)

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6267ee9. Configure here.

@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

Comment threadscripts/lib/preflight.sh
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6267ee9. Configure here.

@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@tracebloc-release-traintracebloc-release-trainBot added gate-nudge Toggled by the release train to (re-)fire the fr-gate and removed gate-nudge Toggled by the release train to (re-)fire the fr-gate labels Jul 29, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6267ee9. Configure here.

@tracebloc-release-train
tracebloc-release-trainBot merged commit b94bc91 into mainJul 29, 2026
80 of 81 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LukasWodka@shujaatTracebloc