Release develop → main#383
Open
shujaatTracebloc wants to merge 20 commits into
Open
Conversation
From Lukas's review: emoji are wanted, not stripped. - Guard's emoji check removed (now enforces hardcoded brand colour + 'workspace' only); STYLE.md documents emoji as welcome (👋 💚 🚀 🟢/🟡/🔴 ⚠). - summary.sh: restore the 🟢 traffic-light on the "secure environment is live" and "🟢 Online" lines (the earlier ● replacement is reverted). - Manifest regenerated (R8) for the summary.sh change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…71) (#370) * feat(install): host capability/privilege probe + audit (RFC 0001, #1171) New scripts/lib/probe.sh: side-effect-free probes (docker info; cgroup v2 + unprivileged userns; id/sudo/sudo-n trio) that classify the lowest-privilege install tier (0/1/2), plus render_host_audit. Wired read-only into --diagnose (a console panel + a plain readout in the support bundle); tier ROUTING is the follow-up (#1172), so nothing in the install path changes behaviour yet. Registered probe.sh in both FILES arrays (install.sh + gen-manifest.sh) and regenerated the R8 manifest. 15 bats tests; full suite (318) green; shellcheck --severity=warning clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(install): add probe.sh to the bootstrap fixture + manifest lists Foundation added probe.sh to install.sh FILES; install-bootstrap.bats hard-codes the fetched-file list in two places (staging + manifest) and must include it, or the bootstrap happy-path tests fail with "no entry in manifest" for probe.sh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): precise non-Linux tier + wire the --verify probe (Bugbot #370) classify: split the non-Linux case — macOS => needs-docker-desktop (correct remedy), any OTHER non-Linux (Git Bash/MINGW) => unsupported-os with a "use install.ps1 on Windows" note, instead of misdirecting to Docker Desktop. run_host_probes now actually invokes _probe_verify_runtime under TB_PROBE_VERIFY=1 (it was defined + tested but never wired), so a daemon that answers `docker info` but cannot run a container is correctly demoted from Tier 0. 2 new bats. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…72) (#374) Wires the host audit (probe.sh, #1171) into the real install flow and adds the routing skeleton: - main() step a now runs host_audit, so the user sees the capability/privilege probe + chosen tier before install, and INSTALL_TIER is set for routing. - install_linux calls _route_install_tier first: honours the detected tier (TB_FORCE_TIER overrides for QA) and FAILS FAST with an actionable remedy when the host genuinely cannot run containers without admin (Tier 2 = no cgroup v2 / unprivileged userns) AND we are neither root nor able to sudo — instead of a cryptic mid-install crash. Tier 0/1 proceed; a stale bootstrap without probe.sh proceeds exactly as before. Per-tier body optimisation (Tier 0 skip privileged steps #1175, Tier 1 rootless #1177) stacks on top. 5 new bats; full suite adds no new failures; shellcheck + manifest clean. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…nary go/no-go (#1176) (#373) Deliverable for the rootless-primary spike: kernel-prerequisite coverage by distro, rootless install mechanics (subuid/subgid + systemd --user), k3d-on- rootless-socket caveats (cgroup delegation), the fuse-overlayfs push-perf risk, a concrete validation plan for real hosts (incl. a hardened/userns-disabled node to exercise the Tier-2 fall-through), and a provisional GO conditioned on those experiments. The §5 experiments need real target hosts and gate #1177 (Tier 1); nothing here blocks #1172 (routing) or #1175 (Tier 0). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(install): Tier 0 — reuse an existing usable Docker, zero privileged steps (#1175) When the host audit selects Tier 0 (docker info OK), install_linux now skips the entire privileged path — sudo priming, the Docker engine install, system-package + kernel-module setup, and the privileged GPU-driver install — and installs only the user-space tools (kubectl/k3d/helm) before create_cluster reuses the runtime. The biggest unlock for shared/managed hosts: a researcher in the docker group installs with no admin at all. The tool install is shared with the full flow via _install_userspace_tools so they cannot drift. 3 new bats assert the branch (Tier 0 skips every privileged step; Tier 1 / unset run the full flow). Full suite adds no new failures; shellcheck + manifest clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): Tier 0 installs tools user-space (no sudo) — honour the zero-root promise (Bugbot #1175) The Tier-0 fast path claimed "no administrator rights needed" but install_kubectl/k3d/helm still sudo-wrote /usr/local/bin, so a docker-group user without root failed (or hit a password prompt hidden under spin_cmd) at that step. New _set_tools_target: on Tier 0 the tools install into ~/.local/bin with NO sudo (kubectl mv without sudo; k3d + helm via their own USE_SUDO=false + *_INSTALL_DIR knobs) and ~/.local/bin is put on PATH so create_cluster finds them. The full-flow path (/usr/local/bin + sudo, incl. the RHEL secure_path sudo-env-PATH handling) is unchanged. 2 new bats. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): Tier 0 default tool target + skip privileged docker autostart (Bugbot #1175 r2) Two follow-ups on the Tier-0 zero-root path: 1. install_kubectl/k3d/helm read TB_TOOLS_DIR/TB_TOOLS_SUDO — module-level defaults (/usr/local/bin + sudo) so direct callers (bats, e2e) that skip _set_tools_target keep the system behaviour instead of an empty dir / spurious no-sudo branch. 2. create_cluster -> ensure_cluster_autostart ran `sudo systemctl enable docker` on Tier 0 too, prompting for a password on /dev/tty after the zero-privilege promise. Skip it on Tier 0 (the k3d --restart policy already covers reboot for the common case); the full flow is unchanged. New cluster.bats + the k3d test is green again. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): Tier 0 helm chmod without sudo (Bugbot #1175 r3) _ensure_helm_executable ran `sudo chmod` unconditionally; on the Tier-0 no-admin path (helm in ~/.local/bin, user-owned) that would prompt on the tty — the same zero-privilege break already guarded for systemctl. Use a plain chmod when TB_TOOLS_SUDO is empty (Tier 0), sudo otherwise. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): Tier 0 reuses the NVIDIA k3d GPU flag when the runtime is configured (Bugbot #375) The Tier-0 early return skipped dispatch_gpu_setup, which is the only place K3D_GPU_FLAGS=(--gpus=all) was set — so an NVIDIA Tier-0 host silently got a CPU-only k3d cluster even with the toolkit installed. New _tier0_gpu_flags sets the flag when Docker's NVIDIA runtime is already configured (reused, no admin); otherwise it stays CPU-only with guidance (installing the toolkit needs admin). 3 new bats. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * tier0: persist ~/.local/bin to the shell rc so new terminals find the tools On Tier 0, kubectl/k3d/helm install into ~/.local/bin and _set_tools_target only prepends that dir to the CURRENT process PATH. Nothing persisted it to the user's shell rc, so a fresh terminal couldn't find the tools and the summary's suggested `kubectl …` commands failed — worst when the CLI lives in /usr/local/bin (already on PATH), so no other step triggered a PATH fix either (Bugbot #375). Add _persist_tools_on_path (called from _install_userspace_tools): when the user-local dir was used (Tier 0), append `export PATH="$HOME/.local/bin:$PATH"` to the shell-correct rc — idempotent (skips if the rc already references ~/.local/bin) and best-effort (never fatal). _tools_rc_for_shell mirrors the CLI installer's per-shell rc resolution (zsh/.zshrc, bash+mac/.bash_profile, bash+linux/.bashrc, else /.profile). No-op on the full flow (/usr/local/bin). Tests: rc resolution per shell; Tier-0 append; idempotency; full-flow no-op. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * tier0: fix broken GPU prepare-host hint + fish PATH persistence (Bugbot #375) Two Bugbot findings: - Broken prepare-host GPU hint: _tier0_gpu_flags told admins to re-run with `bash -s -- prepare-host`, but that mode doesn't exist on this branch (it lands in the stacked prepare-host PR), so the arg was ignored and a normal install ran — the suggested remedy couldn't enable GPU. Point instead to installing/configuring nvidia-container-toolkit directly, which is correct and self-contained here. - Fish PATH persist incomplete: _persist_tools_on_path appended a bash `export PATH=…` line to ~/.profile for fish users, which fish never loads, so Tier-0 kubectl/k3d/helm stayed missing in new fish terminals. Detect fish and hint the fish-correct `fish_add_path "$HOME/.local/bin"` (persists as a universal var AND applies now) instead of writing a dead POSIX line. Note in _tools_rc_for_shell that fish is handled separately, never routed there. Tests: fish path emits fish_add_path and writes no ~/.profile. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(tier0): sandbox HOME in the Tier-0 install_linux test (Bugbot #375) The test ran the real _install_userspace_tools (only leaf steps are stubbed), so _set_tools_target mkdir-ed ~/.local/bin and _persist_tools_on_path appended a PATH export to the shell rc in the developer/CI real home. Set HOME="$BATS_TEST_TMPDIR" like the sibling PATH/tools tests so both side effects land in the throwaway tmpdir. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(install): root-aware sudo — no sudo needed when root; honest "sudo missing" vs "no access" (A2, #1173) The installer primed and ran ~70 privileged steps as `sudo <cmd>`, which broke two real users: a root container/VM (sudo unnecessary, and often ABSENT on minimal images, so `sudo <cmd>` died with "command not found") and reported "sudo not installed" as "no sudo access" (a different, misleading fix). Shadow `sudo` with a function in common.sh: as root, run <cmd> directly (no sudo binary needed); else defer to the real sudo; else fail the way real sudo would (non-zero) so best-effort `sudo … || fallback` sites still fall back. Every existing call site is fixed with NO edit — the diff is confined to common.sh. preflight_sudo now short-circuits on root, gives an accurate message when sudo is genuinely absent, and primes credentials via the real sudo (`_real_sudo -v`). type -P finds the real binary past the shadow; helper seams (_have_sudo_bin, _real_sudo) make every branch unit-testable. 6 new bats; full suite adds no new failures; shellcheck introduces no new findings; manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): export the sudo shadow so bash -c subshells inherit it (review #372) The sudo() shadow is a shell function; without `export -f`, `sudo <cmd>` inside setup-linux.sh's `bash -c '…'` blocks (the apt-lock wait and the RHEL-rebuild Alma/Rocky/OL Docker install) resolved the REAL sudo binary — so a root box without sudo installed still hit "sudo: command not found" there, the exact case A2 eliminates everywhere else. Export sudo + _real_sudo + _have_sudo_bin. Harmless to non-bash children (they ignore BASH_FUNC_*). New bats asserts a child bash inherits the shadow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): silence type stderr in _have_sudo_bin (Bugbot #372) type -P sudo prints "type: sudo: not found" to stderr when sudo is absent, which leaked out right before preflight_sudo`'s clean "not root and sudo isn't installed" message — undercutting exactly the accurate messaging A2 adds. Add 2>/dev/null. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * probe: detect the real sudo binary, not the A2 sudo() shadow _probe_privilege used `has sudo` + a bare `sudo -n true`. Both resolve to the `sudo()` shell function common.sh installs (the A2 shadow), so on a host with no sudo at all the probe saw "sudo present" and reported the posture as sudo_pw instead of no_sudo — misrouting the install tier (Bugbot #372). Switch to the same primitives preflight_sudo uses: _have_sudo_bin (type -P, ignores functions) and _real_sudo (command sudo, bypasses the shadow). Update the three privilege bats cases to mock those helpers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * common: make _have_sudo_bin set -e-safe (drop the command substitution) _have_sudo_bin wrapped `type -P sudo` in `[ -n "$(...)" ]`. On bash <4.4 (Amazon Linux 2's 4.2, and macOS's 3.2) a failing command substitution trips `set -e` even when the helper is called from an `if`/`elif` — so the non-root, no-sudo path that preflight_sudo is meant to handle with a clear message could abort before that message ran (Bugbot #372). Use `type -P sudo >/dev/null 2>&1` as the whole function body: no substitution, same result (0 iff a real sudo binary is on PATH, ignoring the sudo() shadow), still quiet. Add a regression test that runs the helper under `set -e` with no sudo on PATH and asserts it returns cleanly instead of aborting. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…380) * feat(chart): one-time metadata backfill as a post-upgrade Helm hook Adds a post-upgrade/post-install hook Job that runs the ingestor image's `tracebloc-backfill` entrypoint (data-ingestors #393, backend #1166/#1198) once per chart upgrade. The auto-upgrade CronJob runs `helm upgrade` when a new chart publishes, which fires the hook — so a client that auto-updates to this release runs the backfill automatically, no operator step. The sweep brings every already-registered pre-cutover dataset up to the federated-alignment metadata shape (enriched schema + feature_stats, recomputed from the persisted MySQL rows) and re-folds any competition built from them, so old competitions/experiments keep working under the new backend alignment code. - Reuses the same credentials Secret (CLIENT_ID/CLIENT_PASSWORD) and in-cluster MySQL (mysql-client) as jobs-manager, plus tracebloc.proxyEnv for corporate egress; resolves the same ingestor image (repo/tag/digest) the ingestion path spawns. - Best-effort: retries a few times in-pod then exits 0 regardless, so a backfill hiccup never fails the auto-upgrade (which would strand the cluster on the old chart). Operators read the outcome via `kubectl logs`. - One-time/idempotent: before-hook-creation replaces the prior run; the sweep skips datasets already carrying new-shape metadata and the upsert is a safe overwrite, so re-runs are cheap. Opt out with metadataBackfill.enabled=false. - Hardened pod: non-root, no SA-token, read-only rootfs, caps dropped. Tests: 7-case helm-unittest (renders by default, hook annotations, image tag/digest modes, cred+MySQL env, hardened pod, disabled-by-flag). Full chart suite green (274). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(chart): nil-guard metadataBackfill for --reuse-values upgrades The auto-upgrade E2E upgrades from the last published release via --reuse-values, whose stored values predate the metadataBackfill key — so .Values.metadataBackfill is nil and `.enabled` failed template rendering ("nil pointer evaluating interface {}.enabled"), the same hazard the images.ingestor keys already guard. Guard the whole block with `$mb := default (dict) .Values.metadataBackfill` and read deadlineSeconds/resources through $mb. Absent key => feature off on the --reuse-values path; the production auto-upgrade uses --reset-then-reuse-values, which layers in the new default (enabled: true), so the backfill still runs on the real upgrade path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(chart): stop the backfill hook from failing the auto-upgrade (Bugbot #380) Two Bugbot findings on the metadata-backfill hook: - Medium: the ingestor-tag fallback was "0.5", but values.yaml and jobs-manager's INGESTOR_IMAGE_TAG default to "0.7"; on the fallback path the Job would pull an ingestor predating the tracebloc-backfill entrypoint and no-op. Aligned to "0.7". - High: swallowing the entrypoint exit code didn't make the hook best-effort — activeDeadlineSeconds (1800, and > autoUpgrade.timeout 10m), image pull/OOM, or Helm's wait timeout still fail the Job and thus `helm upgrade`. Now the container bounds ITS OWN runtime with `timeout $backfillTimeoutSeconds` (default 300s, under the 10m Helm timeout) and ALWAYS exits 0, so the pod completes successfully regardless of how the sweep goes. Removed activeDeadlineSeconds (a deadline kill = failed Job = failed upgrade) and set backoffLimit 0. A run that hits the bound stops cleanly and resumes on the next upgrade (the sweep is idempotent / skips already-backfilled datasets). Residual: a container that never starts (image pull) or is OOM-killed can still fail the hook; those are rare, retried on the next hourly auto-upgrade, and mitigated by the generous memory limit + floating-tag Always pull. Test: asserts no activeDeadlineSeconds, backoffLimit 0, and the timeout-wrapped always-exit-0 command. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(chart): backfill timeout headroom + values.schema entry (Bugbot #380) - Medium: backfillTimeoutSeconds was 300s = Helm's default --timeout (5m) that a manual `helm install/upgrade` uses. Since Helm counts image-pull + scheduling against its hook wait (outside the in-container `timeout`), a 300s sweep could push a manual upgrade past 5m and mark it failed. Lowered to 180s, leaving ~2m headroom under the 5m default (and far more under the auto-upgrade's 10m). - Low: schematized the new metadataBackfill block in values.schema.json (enabled: bool, backfillTimeoutSeconds: int>=1, resources), matching the autoUpgrade/imageRefresh blocks so helm type-checks it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(chart): hard-stop the backfill timeout with -k (Bugbot #380) `timeout "$TIMEOUT" tracebloc-backfill` only sends SIGTERM; if the entrypoint ignores it or is wedged, timeout waits indefinitely, the wrapper never reaches exit 0, and the hook hangs until Helm's --timeout — failing the upgrade, the very mode removing activeDeadlineSeconds was meant to avoid. Add `-k 30` so timeout escalates to SIGKILL 30s after the SIGTERM. Worst-case wrapper runtime is TIMEOUT+30s (210s at the default), still well under the 5m manual --timeout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(chart): widen backfill Helm headroom to 120s (Bugbot #380) 180s sweep + 30s SIGKILL grace = 210s worst-case in-container, leaving only ~90s of Helm's default 5m --timeout for a cold ghcr.io pull + scheduling on a manual install/upgrade. Lower backfillTimeoutSeconds to 120s so worst-case in-container is 150s, leaving ~150s for pull/scheduling under the 5m default (and ~450s under the auto-upgrade's 10m). The sweep is idempotent/incremental, so a smaller per-run budget just spreads a large backlog across upgrades. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(chart): backstop ImagePullBackOff with activeDeadlineSeconds (Bugbot #380) backoffLimit 0 doesn't bound an ImagePullBackOff pod — it stays Pending (never a failed attempt), so with no activeDeadlineSeconds the Job neither completes nor fails and Helm waits its full --timeout before failing the release. The in-container timeout/exit-0 only apply once the container starts, so they don't cover a pod that never runs. Restore activeDeadlineSeconds as a Job-level backstop = backfillTimeoutSeconds + 120s (240s at the default), well under Helm's 5m default, so a stuck/Pending pod (pull failure) fails FAST and the hourly auto-upgrade retries instead of hanging the whole wait. It does not gate a healthy run: the in-container `timeout` exits ~150s in, before the deadline. A genuinely un-pullable image still fails the upgrade (irreducible for a synchronous hook running the ingestor image) but now quickly and self-healing once the registry recovers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(chart): make backfill deadline consistent with the pull budget (Bugbot #380) The Job deadline (backfillTimeoutSeconds + 120 = 240) contradicted the values comment's ~150s cold-pull budget: a pull of 90-150s plus a full in-container run (150s) exceeded 240s and DeadlineExceeded a HEALTHY sweep. Since activeDeadlineSeconds is Job wall-clock and includes the pull, it must exceed (cold pull + full run). Re-derive the deadline as backfillTimeoutSeconds + 30s grace + 120s pull = 270s and reconcile the docs: in-container <=150s, cold-pull budget 120s, deadline 270s. A healthy run (warm pull ~instant, or a cold pull up to ~120s + <=150s container) fits; a Pending/stuck pod is still bounded (270s). Sized for the auto-upgrade's 10m --timeout (the target path); manual installs on a cold cluster should pass --timeout 10m. A pull slower than the budget fails that upgrade and self-heals on the next (image cached by then). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(chart): backfill hook is post-upgrade only, not post-install (Bugbot #380) The hook was post-upgrade,post-install. On a fresh install there are no pre-cutover datasets to backfill, yet Helm waits on hooks even without --wait, so a cold ingestor-image pull / ImagePullBackOff could fail the whole client install (the bash/Windows installers run `helm upgrade --install` under the default 5m timeout and assume a ~10-15s apply). Drop post-install so the hook never runs at install time; a cluster that actually carries pre-cutover data reaches it on its first auto-upgrade (post-upgrade). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Installer copy catalog: byte-exact golden of what a user sees
Adds scripts/tests/copy-catalog.bats — a golden catalog mirroring the CLI's
(cli repo, internal/cli/testdata/golden/), so the installer's user-facing copy
can be reviewed without running an install. Two files under
scripts/testdata/golden/, ordered like the run:
00-install.golden — the banner (versioned + direct-run variants), the
"2. Installing" roadmap, the six running step headers
(a-f; titles read live from install-k8s.sh so they can't
drift), and --help.
01-outcomes.golden — print_summary's five end states (connected / starting /
bad creds / image pull / crash) + the reboot footer,
incl. the macOS/Windows variant.
Rendered colour-off (NO_COLOR) so the text is byte-exact; the one live read
(_chart_version) is stubbed for determinism, and $HOME collapses to ~, so the
goldens are stable across machines/CI. The tests fail on drift; regenerate with
TB_UPDATE_GOLDEN=1 bats scripts/tests/copy-catalog.bats.
Not in the R8 manifest (tests/ + testdata/ aren't installer scripts); CI already
globs scripts/tests/*.bats and excludes .bats from shellcheck.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(installer): clear TRACEBLOC_BANNER_SHOWN in copy-catalog setup
Bugbot: print_banner no-ops when TRACEBLOC_BANNER_SHOWN is set (the curl|bash
bootstrap exports it after drawing the banner). The copy-catalog setup() never
cleared it, so an inherited value — from the shell or a prior install session —
would blank both banner samples in emit_install, failing the drift check or
regenerating a banner-less 00-install.golden. unset it in setup(), matching the
precedent in common.bats. Golden already has both banners (generated clean), so
no regen needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
…g-gated) (#368) * feat(installer): node-local storage prototype (RFC-0003 Option C, flag-gated) TB_STORAGE_MODE=node-local (default: hostpath, unchanged) switches the local k3d install to store datasets on k3s's built-in local-path provisioner INSIDE the node instead of bind-mounting ~/.tracebloc. Data then dies with `cluster delete`, is not a browsable host folder, and needs no chmod 777 — the RFC-0003 goal for the local install. - common.sh: TB_STORAGE_MODE flag + validation; C1 forces AGENTS=0 (single-node) since local-path is RWO/WaitForFirstConsumer and the shared data PVC is mounted by jobs-manager-spawned Jobs. - cluster.sh: node-local drops the -v ~/.tracebloc:/tracebloc@all bind-mount, KEEPS k3s local-storage (removes --disable=local-storage), and skips the world-writable host-dir pre-create. - install-client-helm.sh: node-local emits storageClass.create=false + name=local-path, hostPath.enabled=false; skips _ensure_release_dirs. - cluster.bats: cover both modes. Default (hostpath) path is byte-for-byte unchanged; existing installs on --reuse-values do not migrate. Refs #367, backend#1151, tracebloc/cli#366. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): gate the second _ensure_tracebloc_dirs call for node-local install_client_helm() calls _ensure_tracebloc_dirs independently of create_cluster; the first gate missed it, so node-local installs still created empty world-writable ~/.tracebloc/{data,logs,mysql} dirs. Now node-local only ensures the base dir exists (for values.yaml + the install log) and skips the 777 data subdirs. Verified on a real tb-nodelocal install. Refs #367. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(installer): document TB_STORAGE_MODE in install-k8s.sh usage header The node-local storage flag (RFC-0003 Option C) was undocumented in the env-var overrides list. Add it alongside HOST_DATA_DIR with a one-line note on what node-local does (k3s local-path, no host dirs, AGENTS=0). Regenerated manifest.sha256 (install-k8s.sh is manifest-covered). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): guard node-local storage mismatches (Bugbot #368) Two silent-misroute footguns Bugbot flagged on the node-local path: 1. node-local + HOST_DATASET_DIR: node-local forces hostPath.enabled=false, so a HOST_DATASET_DIR network export was accepted but ignored — datasets would land on ephemeral local-path storage (gone on 'cluster delete'). Combining the two is a documented follow-up (backend#743 + RFC-0003); validate_config now rejects the combo instead of misrouting. 2. Reusing a cluster built for the other storage topology: storage mode is baked in at create time (hostpath bind-mounts /tracebloc + disables k3s local-storage; node-local does neither). A node-local install onto a hostpath cluster requests a local-path StorageClass that was disabled (PVCs Pending); a hostpath install onto a node-local cluster points hostPath PVs at an unmounted /tracebloc (ephemeral). New _check_existing_cluster_storage_mode fails fast both ways, mirroring _check_existing_cluster_dataset_mount. The /tracebloc bind mount is the discriminator: present <=> hostpath cluster. Tests: cluster.bats 35/35 (5 new storage-mode cases), common.bats +1 (node-local + HOST_DATASET_DIR rejected). shellcheck: no new findings. Regenerated manifest.sha256 (cluster.sh + common.sh are manifest-covered). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): node-local must force SERVERS=1 too, not just AGENTS=0 (Bugbot #368) The C1 single-node guarantee only forced AGENTS=0. But k3s server nodes are schedulable (unlike a full k8s control plane), so TB_STORAGE_MODE=node-local with SERVERS>1 still produced multiple nodes — the RWO local-path data PVC binds on one while jobs-manager Jobs land on another and stay Pending, the exact failure C1 exists to prevent. Now clamps both AGENTS=0 and SERVERS=1. Tests: common.bats +2 (node-local clamps both; hostpath leaves them untouched). shellcheck: no new findings. Regenerated manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(install): PATH-aware final CTA — stop telling users to run a CLI this shell cannot find (B2, #1174) install-cli.sh records TB_CLI_USABLE_NOW (1 only when the CLI resolves in THIS shell, not just a fresh terminal). summary.sh reads it via _cli_runnable_now (with a live has-tracebloc fallback) so the final CTA says "Run tracebloc" only when that is true, and "Open a new terminal, then run tracebloc" otherwise — matching the honest guidance install-cli already prints for the ~/.local/bin case. 2 new summary bats; install-cli suite green; shellcheck + manifest clean. The complementary cli-repo change (installer prefers a writable dir already on PATH so the same-terminal case happens more often) is a separate PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): B2 CTA relies solely on the fresh-shell signal, not the mutated process PATH (Bugbot #371) _cli_runnable_now dropped its `has tracebloc` fallback: install.sh + provision.sh prepend ~/.local/bin to THIS process PATH, so `has tracebloc` was true even when the user's launching shell could not resolve it — so the CTA wrongly said "Run tracebloc" on the common curl|bash path. Now it trusts only TB_CLI_USABLE_NOW (set from install-cli.sh's FRESH-shell probe); unset/0 → the honest "open a new terminal" branch. Also fixed a pre-existing summary.bats test that assumed the unconditional CTA (pinned TB_CLI_USABLE_NOW=1) — it was RED on Linux CI (masked locally by my having the CLI installed) — and tightened the new B2 test to assert the "Run …" branch specifically. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): B2 usable-now gate keys on the CLI install dir, not process PATH (Bugbot #371 r2) TB_CLI_USABLE_NOW was still set from `has tracebloc` in the installer process, whose PATH install.sh prepended with ~/.local/bin — so it was true even when the users returning shell cannot resolve the CLI. New _cli_at_system_dir keys the flag on WHERE the CLI landed: a system dir (unconditionally on PATH) => usable now => "Run tracebloc"; a $HOME bin (~/.local/bin, ~/bin) or unresolved => conservative => "open a new terminal". Unit test for the gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): B2 step verdict must match the summary CTA (Bugbot #371) _verify_tracebloc_cli set TB_CLI_USABLE_NOW=0 for a ~/.local/bin install but then printed the usable-now verdict ("... ready — run tracebloc to use it") and returned, because `has tracebloc` is always true (install.sh prepends ~/.local/bin to the process PATH). The summary CTA, keyed on TB_CLI_USABLE_NOW=0, said "open a new terminal" — a direct contradiction on the least-privilege path, and following the step fails command-not-found. Gate the usable-now verdict on `has tracebloc && _cli_at_system_dir`; otherwise fall through to the honest "installed — open a new terminal" guidance and keep TB_CLI_USABLE_NOW=0 so the summary agrees. Add a regression test; the two existing usable-now tests now force _cli_at_system_dir (they mocked tracebloc as a function, so command -v never returned a system path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): summary CTA distinguishes not-on-PATH-yet from new-terminal (Bugbot #371) TB_CLI_USABLE_NOW=0 covered two different states, and the summary always said "open a new terminal" for both: - case A: installed to ~/.local/bin, persisted — a NEW terminal resolves it (only this login shell does not). "Open a new terminal" is correct. - case B: _cli_on_fresh_path failed — a new terminal will NOT find it either; install-cli.sh prints the exact PATH fix and deliberately avoids "open a new terminal". The summary contradicted that, pointing users at a useless step. Add a second signal TB_CLI_ON_FRESH_PATH (set 1 on the fresh-path branch, 0 in the case-B fall-through) and a third summary CTA branch: case B now says "Add tracebloc to your PATH (see above)", matching install-cli.sh. Update the case-A test to set the flag; add a case-B CTA test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): unset fresh-path flag defaults to new-terminal, not the see-above PATH fix (Bugbot #371) The case-B CTA branch fired on `else`, catching BOTH TB_CLI_ON_FRESH_PATH=0 (install-cli.sh ran and printed the exact PATH fix) AND the flag being UNSET (CLI step skipped/failed, so nothing was printed above). In the unset case the "Add tracebloc to your PATH (see above)" message pointed at guidance that never appeared. Gate case B on the explicit "0"; fold unset + case A into the safe "open a new terminal" default (matching _cli_runnable_now's documented default). Add an unset-flag test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install): honest CTA when a system tracebloc pre-exists but the CLI step is skipped (Bugbot #371) Bugbot (Medium): install_tracebloc_cli hard-defaulted TB_CLI_USABLE_NOW=0, so if the CLI step is skipped or fails (download/installer/temp-dir miss → early return, _verify_tracebloc_cli never runs) the connected summary said 'open a new terminal' even when tracebloc was ALREADY on a system PATH dir and works in the user's current shell. Seed the default from the PRE-install state instead: usable-now iff a tracebloc is already resolvable AND at a system dir. Gate on _cli_at_system_dir, NOT bare 'has tracebloc' — install.sh prepends ~/.local/bin to THIS process, which would false-positive a ~/.local/bin install the returning shell can't yet see (the exact trap the earlier #371 findings closed). _verify_tracebloc_cli still overrides per this run's outcome; summary.sh's _cli_runnable_now is unchanged. Tests: pre-existing system tracebloc + failed install → stays 1; ~/.local/bin + failed install → 0. install-cli.bats green; manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(installer): pin TB_CLI_USABLE_NOW in copy-catalog so the CTA golden is stable (#371) Merging #371's PATH-aware connected-state CTA into a develop that has the #366 copy catalog broke copy-catalog.bats: emit_outcomes renders print_summary, and with TB_CLI_USABLE_NOW unset the new CTA branch prints 'Open a new terminal…' while 01-outcomes.golden still has the happy-path 'Run tracebloc to get started.'. Pin TB_CLI_USABLE_NOW=1 in setup() (mirrors #371's summary.bats) so the catalog deterministically renders the 'Run' line the golden captures — no golden regen needed, and it's robust to an inherited flag (same env-stability class as the TRACEBLOC_BANNER_SHOWN unset). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
…8) (#379) The prod overlay pinned the 0.7 index digest as resolved on 2026-07-13 (v0.7.0 era). v0.7.4 (di#368 correlation-id, di#369 orphan-row reconcile) and v0.7.5 have shipped since, so a prod install spawned a pre-fix ingestor while dev/staging (floating 0.7) already run v0.7.5. Re-resolved via scripts/resolve-ingestor-digest.sh 0.7 --write per the release convention documented in the overlay; multi-arch index verified (linux/amd64 + linux/arm64). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…repo (#385) (#389) The repo presence guard string-matched (helm repo list 2>&1) | Out-String. On a fresh machine helm reports 'no repositories' on stderr, and Windows PowerShell 5.1 renders that ErrorRecord with this script's own ...\tracebloc-installer-<n>\install-k8s.ps1 temp path — which contains 'tracebloc' — so the add was skipped on every fresh install and Step 4 died with 'Error: repo tracebloc not found'. - always 'helm repo add --force-update' (idempotent, heals wrong URLs, refreshes the index; the unchecked 'helm repo update' pass is gone) - add failures abort with helm's actual output - preflight probes the chart repo index.yaml strictly (ps1 + bash twins): the site root 404s by design, so plain reachability proved nothing - manifest.sha256 regenerated (R8) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: resolve Bugbot findings on promotion PR #383 - Golden step scrape: broaden [a-f] to [a-z] so new steps stay covered - Golden summary capture: route stdout+stderr through a file for deterministic ordering - check_golden: point at TB_UPDATE_GOLDEN=1 when the golden is missing - Host audit: stop promising "no admin" for Tier 1 until rootless lands Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: refresh installer manifest for probe.sh audit-copy change The supply-chain manifest pins a SHA per sub-script; the Tier 1 host-audit wording fix changed scripts/lib/probe.sh, so regenerate scripts/manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…r install's data (#376) (#384) * feat(installer): guard against silently adopting leftover data (#376) A new install (one that creates a fresh cluster) used to mkdir -p its data dirs and silently adopt whatever was already under HOST_DATA_DIR — data from an earlier install (different layout, older version, custom dir) got picked up, so a "fresh" install was not guaranteed fresh. Add guard_leftover_data (cluster.sh), called from create_cluster only when creating a NEW cluster (an existing cluster is an in-place reuse/upgrade whose data stays by design, §3.3). It detects real client data — MySQL or dataset files — across both the flat and per-release layouts, and forces a choice instead of adopting it: - interactive: reuse / wipe / new dir / abort (abort is the default) - non-interactive: --reuse-data / --wipe-data / --data-dir=PATH (or TB_LEFTOVER_ACTION); no terminal + no choice => fail-safe abort - TRACEBLOC_SKIP_LEFTOVER_GUARD=1 bypasses entirely Scoped to HOST_DATA_DIR only — HOST_DATASET_DIR may be a shared network mount, so it is never scanned or wiped. Also doubles as the node-local migration prompt (#367): existing ~/.tracebloc data is never silently stranded. Implements RFC-0003 D3 (cli#366, §4). Adds scripts/tests/leftover-guard.bats (13 tests), README + --help/env docs, and regenerates manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(installer): refresh 00-install golden for the leftover-guard --help lines The copy-catalog byte-exact golden captures install-k8s.sh --help output; the new --reuse-data/--wipe-data/--data-dir usage + leftover-data section drifted it. Regenerated via TB_UPDATE_GOLDEN=1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): pipefail-safe leftover detection + sanitize prompt input (Bugbot #384) - _leftover_data_dirs: a find|head|grep pipeline SIGPIPEs find once its output exceeds the pipe buffer (a real multi-table MySQL dir); under the installer's set -o pipefail that read as "empty" and the guard silently adopted real leftovers — the exact failure it exists to prevent. Read the first path from a process substitution instead (find's status stays out of the check, still short-circuits). Regression test seeds 3000 files + pipefail. - Interactive reply/newdir reads now go through _read_sanitized (_strip_paste_garbage + whitespace trim), so pasted CSI escapes can't enter HOST_DATA_DIR and a spaces-only path no longer slips past the non-empty check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(installer): refresh manifest.sha256 for cluster.sh (Bugbot #384 fixes) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): wipe fails closed when data can't be removed (Bugbot #384) _wipe_leftover_data only warned on rm -rf failure and the guard still returned success + logged "wiped"; create_cluster then adopted whatever survived — common when container MySQL dirs are root-owned and the host user can't remove them — silently breaking the "wipe means gone" fail-safe. Now _wipe_leftover_data verifies each target is actually gone (not just rm's exit code) and returns non-zero if any survived; the guard's wipe branch errors out instead of proceeding. Regression test: an unremovable dir makes wipe abort (exit 1) with the survivor left in place, not adopted. Regenerates manifest.sha256 for the cluster.sh change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): assert HOST_DATA_DIR non-empty + under $HOME before wipe (Lukas review #384) Belt-and-suspenders for the destructive path: _wipe_leftover_data now asserts HOST_DATA_DIR is a non-empty path strictly under $HOME before any rm. Today _leftover_data_dirs already returns empty when it's unset (so the wipe never runs), and validate_config enforces the invariant — but placing the check in the destructive function guards the rm even if a future refactor changes call ordering (an empty HOST_DATA_DIR would collapse the "$HOST_DATA_DIR"/* scope pattern to /*). Two tests: empty + outside-$HOME both refuse. Regenerates manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): detection fails closed on unreadable data dirs (Bugbot #384) _leftover_data_dirs used find ... 2>/dev/null, so a root/container-owned mysql/data dir the host user can't list produced no paths AND swallowed the Permission-denied error — looking like a clean slate, letting create_cluster adopt the leftovers (the exact fail-open the guard exists to prevent, and the same ownership case the wipe path already treats as fatal). Now capture find's stderr: a file found OR any find error => leftover. Keeps the pipefail-safe read < <(find) (no find|head pipe). Regression test: a chmod 000 mysql dir is detected, not skipped. Regenerates manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): drop mktemp from leftover detection — no fail-open path (Bugbot #384) The previous fix redirected find's stderr to a temp file; if mktemp failed, stderr went to /dev/null and the unreadable-dir check never fired -> a root/container-owned leftover dir looked empty and got adopted. Remove the temp file entirely: - explicit [[ -r && -x ]] check fails closed on an unlistable top dir (the common container-owned case), - a temp-file-free $(find … 2>&1 >/dev/null) fallback (only when no top-level file was read) catches an unreadable subdir too. Keeps the pipefail-safe read < <(find). Added a subdir-unreadable regression test. Regenerates manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): symlink-safe wipe + tilde/empty data-dir handling (Bugbot #384) Three findings on the new-dir/wipe surface: - Symlink traversal (Med): detection walked $HOST_DATA_DIR/*/ incl. dir symlinks, so a planted 'evil -> /etc' gave …/evil/mysql and wipe's rm -rf followed it outside scope. Detection now skips symlinked candidates (-L check on both the per-release glob and each candidate); wipe refuses to rm a symlink as a backstop. - Tilde paths (Med): --data-dir=~/foo / new-dir '~/foo' were read literally, so validate_config turned them into $HOME/~/foo and failed. Expand a leading ~ / ~/ at the validate_config choke point. - Empty data-dir (Low): --data-dir= set HOST_DATA_DIR='' which resolved to a surprise dir; validate_config now fails closed on empty. Tests: symlink-not-a-candidate, wipe-refuses-symlink, tilde-expands, empty-fails-closed. Regenerates manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): probe TB_TTY openability, not just -r (Bugbot #384) `guard_leftover_data` decided interactivity with `[[ -r "$TB_TTY" ]]`, but /dev/tty is world-readable even with no controlling terminal (CI, curl|bash). Those runs took the interactive branch, `read` failed immediately, and the guard aborted with the generic abort text instead of the non-interactive guidance that lists --reuse-data / --wipe-data. Add `_tty_usable` — the same `{ : <"$TB_TTY"; }` openability probe assess.sh already uses — and gate both the reuse/wipe/new-dir prompt and the new-dir read on it. Regression test: readable-but-unopenable /dev/tty -> non-interactive guidance (skips in an interactive shell). Regenerates manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): reject HOST_DATA_DIR == $HOME, require a subdirectory (Bugbot #384) validate_config allowed HOST_DATA_DIR to equal $HOME, and the new tilde expansion maps a bare `~` to $HOME — so a common new-dir / --data-dir input would select the whole home directory. The installer would then chmod 777 home-level logs/mysql dirs, bind-mount all of $HOME into the cluster, and leftover detection would treat any existing ~/data or ~/mysql as install data. Require HOST_DATA_DIR to be strictly UNDER $HOME (never $HOME itself); this also closes the same footgun via HOST_DATA_DIR=$HOME / --data-dir=$HOME. Tests: `== $HOME` rejected, bare `~` rejected. Regenerates manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): don't rescan flat data dirs as release dirs (Bugbot #384) The per-release scan walked every subdir of HOST_DATA_DIR, including the flat-layout `mysql`/`data` dirs. A real MySQL datadir always contains a nested `mysql` system schema, so detection reported both $HOST_DATA_DIR/mysql and $HOST_DATA_DIR/mysql/mysql as leftover roots — mislabeling layout, confusing the prompt, and doubling wipe targets. Skip the flat data dirs in the per-release walk; they are already candidates. Test: a datadir with a nested `mysql` schema reports exactly one root. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
…te-limit-proof installs (#382) * fix(installer): pin the k3d release (K3D_VERSION) — no more releases/latest lookup The upstream k3d install script resolves its version via a GitHub releases/latest redirect, which breaks under GitHub rate limiting on shared egress IPs (CI runners, corporate NAT) and failed 2/9 distro CI jobs on main on 2026-07-21 with a bare 'curl: (22) 404 / Failed to install k3d'. Retrying can't help — the limit persists per-IP. Pin it the way K8S_VERSION already pins k3s: - K3D_VERSION (common.sh, default v5.9.0) — the install script is now fetched AT the pinned tag (immutable bytes instead of k3d's mutable main) and TAG=<pin> makes it skip the releases/latest lookup entirely. Its checksums.txt verification of the binary still runs. - K3D_VERSION="" or "latest" restores resolve-at-install-time. - Covers both the Tier-0 (user-space) and sudo install paths. - Help text + golden catalog + signed manifest regenerated. Verified: scripts/tests/distro-prereqs.sh passes in fresh amazonlinux:2023 and ubuntu:24.04 containers (the two failing CI jobs), installing k3d v5.9.0; setup-linux.bats 47/47 incl. two new pin tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(installer): K3D_VERSION guard + honest latest semantics (Bugbot r1) - Fail closed on malformed K3D_VERSION before any fetch: the value lands in a raw.githubusercontent URL path, so a crafted value carrying '/' could traverse to a script outside k3d-io/k3d and run it privileged. Accept only vX[.Y.Z…] tags or 'latest'. - Stop claiming K3D_VERSION="" restores resolve-at-install-time — the common.sh :- default re-pins on empty (same convention as K8S_VERSION); only 'latest' opts out. Comments now say so. - New bats case: malformed tag errors out with nothing fetched/run. - Manifest regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * k3d: verify the release binary ourselves — upstream install.sh checks nothing (review) Asad pulled the pinned v5.9.0 install.sh: downloadFile fetches the bare binary, installFile chmod+cp's it — no checksum is downloaded or verified, so the previous comment/PR claim ('the script still verifies the binary') was wrong, on a sudo path. - Linux: drop upstream install.sh entirely; fetch k3d-linux-<arch> + checksums.txt straight from the pinned release and sha256-verify before installing (mirrors _fetch_kubectl, fail-closed). K3D_VERSION=latest now resolves the tag via the plain /releases/latest redirect and takes the SAME verified path. - Windows: the ps1 fetched sha256sum.txt, which has never existed on k3d releases (asset is checksums.txt) — it 404'd into a fail-open catch, so that verification silently never ran either. URL fixed. - preflight: the k3d tool-host probe now points at github.com (the install script from raw.githubusercontent.com is no longer fetched). - bats: 5 tests for the new path (verified download / mismatch fail-closed / missing-asset fail-closed / sudo target / latest-resolve); the 3 script-era tests removed with the script. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * k3d fetch: stall protection + honest empty-value comments (Bugbot r2) - connect-timeout + speed-limit floor on the release downloads (no --max-time: the ~50 MB binary on a slow-but-healthy link must not be capped), max-time on the small releases/latest HEAD resolve — a hung transfer under spin_cmd would otherwise spin forever - comments: only the literal K3D_VERSION=latest opts out; also corrected the adjacent pre-existing K8S_VERSION comment that claimed "" resolves latest (the :- default re-pins on empty) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * k3d (Windows): fail-closed checksum verification (Bugbot r3) An unfetchable checksums.txt, a missing asset line, or a mismatch now all abort and remove the download — matching the Linux path and the kubectl precedent — instead of skipping silently or logging into a fail-open catch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
#391) * fix(installer): followable elevation steps for the irm|iex flow (#386) 'Right-click > Run as Administrator' is impossible in the documented one-liner flow (there is no file). Field-observed dead-end: the user did not know how to open an elevated PowerShell. Give the actual steps (Win+X -> Terminal (Admin) / Ctrl+Shift+Enter, accept UAC, re-run the one-liner). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * elevation re-run one-liner: explicit https:// (Bugbot) Scheme-less irm resolves the first hop of the bootstrap trust root over cleartext HTTP (and canonical docs use https://). Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…t hook (RFC-0003 §8.2–8.4) (#378) * feat(seal-check): label contract + storage-assertions test hook (RFC-0003 S8.2 / backend#1184) Establish the seal-check enumeration contract and fill the storage gap in the chart conformance suite: - Label every conformance helm-test Job (and its pods) with tracebloc.io/seal-check="true" + tracebloc.io/seal-check-name=<check> -- the contract the tracebloc CLI consumes (cli#393). Applied to the existing egress-enforcement probe and backend-reachability check via a new tracebloc.sealCheckLabels helper. - New storage-assertions helm-test Job (sealCheck.storageAssertions.*, default on, fully nil-guarded for --reuse-values upgrades): release PVCs Bound on the expected StorageClass; in dynamic-PVC mode (hostPath.enabled=false) no release PVC backed by an unmanaged hostPath PV -- catches the RFC-0003 D3/D4 leftover-PV claimRef capture. k3s local-path node-local paths tolerated and reported (Option C); clusterScope=false degrades the PV scan to a name check with an explicit WARNING, never silently. - Least-privilege SA/RBAC as negative-weight test hooks (PVC read in the namespace; PV read only when clusterScope allows a ClusterRole). - docs/SEAL-CHECK.md: label contract, coverage today, UNSEALED philosophy, per-substrate coverage view, and the k3d/k3s NetworkPolicy egress verification runbook (documented honestly as not yet executed). - 20 new helm-unittest cases; 287/287 pass, helm lint --strict clean on all four platform values, check-drift clean. Part of tracebloc/backend#1184 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(seal-check): segment-bounded node-local prefixes; drop misleading WARNING after legacy FAIL Two Bugbot findings on the storage-assertions check (PR #378): - The pv-hostpath allowlist matched entries with a bare "$prefix"* glob, and the default /opt/local-path-provisioner entry carries no trailing slash, so sibling trees like /opt/local-path-provisioner-evil/... were accepted as node-local and could pass the storage seal. Entries are now normalized (trailing slash stripped) and matched on whole path segments: a prefix admits exactly itself or paths under it, never siblings. The default entry in values.yaml / template dig fallback / schema / docs is normalized to the trailing-slash style; either entry style now behaves identically. - With clusterScope=false (no PV read), a claim captured by a chart legacy hostPath PV printed FAIL and then still printed the PARTIAL verification WARNING claiming the claim was "verified not bound" to a legacy PV. The WARNING is now suppressed for a claim whose legacy-name check just FAILed - the FAIL alone is the honest verdict there; claims that pass the name check keep the WARNING unchanged. Validated: helm lint --strict on the 4 CI platform values; helm template (near-default / node-local / flag off / sealCheck: null); helm unittest 289/289 (2 new cases pin both guards); sh -n + shellcheck on the rendered script; mocked-kubectl runs covering sibling-path FAIL (both entry styles), exact-prefix-root OK, legacy FAIL without WARNING, no-legacy WARNING x3, unmanaged-tree FAIL, PV-read-error FAIL. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * seal-check docs: literal job names for --filter (review F1) The generic '--filter name=<release>-<check>-check' formula silently no-ops for backend-reachability (its Job is egress-reachability-check): helm test matches zero hooks and exits 0 -- a silent pass. List the literal Job name per check instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…-aware memory hints (#387) (#392) * feat(installer): fail fast on disabled firmware virtualization + WSL2-aware memory hints (#387) - Step-1 preflight: new Get-PfVirtualization reader (HypervisorPresent first -- Hyper-V owns VT-x and firmware reads false then -- else VirtualizationFirmwareEnabled). Disabled -> hard fail with the BIOS fix + Task Manager confirmation tip, BEFORE Docker is installed/launched. Field case: Docker Desktop's own 'Virtualization support not detected' appeared mid-install with zero guidance. - The three memory hints pointed at Docker Desktop -> Resources -> Memory, a slider that does not exist on the WSL2 backend (the default) -- now give both levers (.wslconfig + wsl --shutdown / Hyper-V slider). - Docker wait-loop: name the error-window case instead of only 'still starting up'. - GPU: soft VRAM note for entry-level cards (a 2 GB GT 710 passes every check but cannot fit a model) -- Hint only, never a gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * memory hints: the Hyper-V slider lives at Settings -> Resources -> Advanced Verified against Docker's settings docs ('Memory limit ... Mac, Linux, Windows Hyper-V' under Resources > Advanced; WSL2 explicitly deferred to the WSL utility VM). Review question by Lukas. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a6acca9. Configure here.
…hook docs) (#394) * fix: resolve Bugbot findings on promotion PR #383 (summary copy + backfill docs) - Reboot note: only promise auto-restart when docker.service autostart was actually enabled; Tier 0 (zero-privilege) leaves it to the user, so be honest - Connected summary Data path: branch on TB_STORAGE_MODE — node-local keeps datasets in-node on k3s local-path, so don't point at a host /tracebloc - Backfill docs: correct values.yaml + values.schema.json to say post-upgrade ONLY (not post-install) to match the Job's helm.sh/hook annotation - Refresh scripts/manifest.sha256 for the summary.sh + cluster.sh changes Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: seed reboot auto-restart promise from pre-install docker.service state Bugbot: on Tier 0, ensure_cluster_autostart deliberately never runs `systemctl enable docker`, and TB_DOCKER_AUTOSTART was set only when *this* run enabled the service. A host where docker.service is already enabled on boot (a normal package install) therefore kept the flag unset, so _reboot_note told the user to start Docker manually even though the cluster returns on its own after a reboot. Seed TB_DOCKER_AUTOSTART from the current on-boot state via an unprivileged `systemctl is-enabled docker` read (matching only the persistent "enabled" state, not the transient "enabled-runtime"). Add cluster.bats coverage for the pre-enabled, disabled, and enabled-runtime cases; refresh manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…kend#1236, option A) (#393) * feat(installer): size the default training budget to the machine (backend#1236, option A) The static cpu=2,memory=8Gi default was wrong at both ends: dead on arrival on nodes under 8 GiB (the WSL2 field case -- doctor 'not ready', nothing could ever schedule) and ~12% utilisation of a 64 GiB box, while the wizard itself calls max 'recommended'. Both installers now write RESOURCE_REQUESTS/LIMITS sized to this machine: largest node allocatable minus the ~1 CPU / 3 GiB platform overhead (largest, not summed -- k3d's server+agent are one machine, cli#399). Precedence: TRACEBLOC_TRAINING_RESOURCES override > the installed release's existing choice (a 'tracebloc resources set' decision now survives re-install instead of being clobbered back to a default) > machine sizing > the historic static fallback (tiny/unreadable machines). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * machine-sizing probe: bound kubectl with --request-timeout (Bugbot) A wedged API server must degrade to the static default, never hang values generation. Both installers; the sizing tests' stubs now only answer a bounded call, so dropping the flag fails them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * carry-lookup: gate helm get values behind a bounded probe (Bugbot) helm get has no request timeout, so the existing-choice lookup now runs only after 'kubectl get namespace --request-timeout=5s' succeeds -- a wedged API degrades to machine sizing / the static default instead of hanging values generation. Both installers; stubs answer only bounded calls. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * carry-lookup: never carry the historic static default (Bugbot) An older install stored the chart default cpu=2,memory=8Gi; carrying it as a 'choice' would keep the unschedulable budget on exactly the machines this sizing exists to fix. The static default was the absence of a choice -- only a differing value survives re-install. Both installers + tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ps1 sizing: jsonpath cpu/memory lines, no full-JSON parse (Bugbot r5) Extract exactly the two allocatable fields via jsonpath (one 'cpu memory' line per node), mirroring the bash twin -- a parse hiccup on unrelated node JSON (e.g. long image-digest fields under PS 5.1 string handling) can no longer fall into the catch and silently reinstate the static default this change exists to remove. Pester mocks moved to the line contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Release: promote
develop→mainPromoting 11 commits from
developto production (main). Nostagingbranch in this repo, so develop promotes straight to prod.Commits being promoted
024b708chore(chart): refresh prod ingestor digest pin to v0.7.5 (backend#1028) (chore(chart): refresh prod ingestor digest pin to v0.7.5 (backend#1028) #379)c25ed40fix(install): PATH-aware final CTA (B2, #1174 client half) (fix(install): PATH-aware final CTA (B2, #1174 client half) #371)53ef7eefeat(installer): node-local storage prototype (RFC-0003 Option C, flag-gated) (feat(installer): node-local storage prototype (RFC-0003 Option C, flag-gated) #368)412baa0Installer copy catalog (byte-exact golden of what a user sees) (Installer copy catalog (byte-exact golden of what a user sees) #366)c3cf38efeat(chart): one-time metadata backfill as a post-upgrade Helm hook (feat(chart): one-time metadata backfill as a post-upgrade Helm hook #380)5fd6487fix(install): root-aware sudo (A2, #1173) (fix(install): root-aware sudo (A2, #1173) #372)054b1fefeat(install): Tier 0 — reuse an existing usable Docker (#1175) (feat(install): Tier 0 — reuse an existing usable Docker (#1175) #375)99dca47docs(rfc): rootless-Docker spike — analysis, validation plan, preliminary go/no-go (#1176) (docs(rfc): rootless-Docker spike — analysis + validation plan (#1176) #373)b876b7afeat(install): tier detection + honest-failure routing (RFC 0001, #1172) (feat(install): tier detection + honest-failure routing (#1172) #374)88df75dfeat(install): host capability/privilege probe + audit (RFC 0001, #1171) (feat(install): host capability/privilege probe + audit (RFC 0001, #1171) #370)efb243cEmoji welcome + restore the 🟢 online indicatorSummary
Mostly installer hardening (privilege/capability probing, tiered Docker reuse, root/sudo-aware behavior, PATH-aware CTA, copy catalog), two Helm chart updates (post-upgrade metadata backfill hook, prod ingestor digest pin v0.7.5), and installer UX polish.
Note
High Risk
Changes the default install/upgrade path (tier routing, cluster storage topology, post-upgrade metadata backfill touching MySQL/backend) and adds cluster-scoped seal-check RBAC; mitigations include guards, fail-closed checks, and best-effort backfill, but fleet-wide impact warrants careful rollout validation.
Overview
Release promotion bundling installer, chart, and docs work from
develop.Linux installer (RFC 0001) adds read-only host audit (
probe.sh) that classifies install tiers (usable Docker → Tier 0 with no sudo/Docker install; rootless-capable → Tier 1; else Tier 2 with honest fail-fast whenno_sudo). Root-awaresudoshadow runs privileged steps as root without requiring thesudobinary. Tier 0 installs kubectl/k3d/helm into~/.local/bin, persists PATH, reuses existing NVIDIA runtime when configured, and skipssystemctl enable docker.RFC-0003 install hygiene: leftover-data guard on new cluster create (reuse/wipe/new dir/
--reuse-data/--wipe-data);TB_STORAGE_MODE=node-local(k3slocal-path, single-node, no~/.traceblocbind) vs default hostpath, with mismatch checks against existing clusters.Helm chart:
tracebloc.sealCheckLabelson egress test Jobs; newstorage-assertionshelm testJob (+ RBAC);metadataBackfillpost-upgrade hook (ingestortracebloc-backfill, best-effort, never fails upgrade).sealCheck/metadataBackfillin values + schema; prod ingestor digest → v0.7.5. Newdocs/SEAL-CHECK.md.Windows
install-k8s.ps1: fix helm repo add (always--force-update), strict Helm index preflight, k3d checksums.txt fail-closed, virtualization/VRAM/Docker guidance, clearer admin elevation steps.Other: CLI summary CTA distinguishes system PATH vs
~/.local/bin; pinned K3D_VERSION with verified direct download on Linux; emoji allowed in STYLE + style guard; README on reinstall/leftover data; rootless-Docker RFC spike doc.Reviewed by Cursor Bugbot for commit 9c03bc9. Bugbot is set up for automated code reviews on this repo. Configure here.