release-train: staging -> main - #609
Merged
Merged
Conversation
#552) (#570) requests-proxy hardcoded `imagePullPolicy: Always` while its image line already read `images.requestsProxy.digest` — so a pinned digest was ignored for the pull policy and every restart re-pulled the image even when it was already cached. Make it digest-aware, matching jobs-manager / pods-monitor / resource-monitor: digest set -> repo@digest + IfNotPresent (restart-safe offline) digest empty -> repo:tag + Always (unchanged default) +2 helm-unittest cases (default -> Always; digest -> repo@digest + IfNotPresent). Chart 1.9.11 -> 1.9.12 (chart-content change requires a version bump). Scope: this is the narrow, unambiguous part of #552. The broader control-plane offline-restart update-model change (jobs-manager/pods-monitor rely on Always for the image-refresh CronJob; resource-monitor too) is a deliberate design decision tracked in #569. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…end#1497) (#580) Two unbounded external calls (Bugbot on the client#571 prod promotion) that can hang a CI job to the GitHub Actions cap with no useful failure — same class as the image-refresh CronJob fix (#572). - scripts/tests/e2e-seal-check.sh: add --request-timeout=10s to the point-in-time kubectl API calls (get/run/logs/delete/describe) so a wedged API server fails the seal-check fast. `kubectl wait` (L57) is deliberately left as-is: it is already bounded by --timeout=180s, and a --request-timeout on its underlying watch would truncate it and risk flakes. - .github/workflows/helm-ci.yaml: add --connect-timeout 15 --max-time 120 to the pinned kubeconform download so a stalled endpoint fails the template matrix instead of hanging. Retries/-f behaviour unchanged. Values mirror the repo's existing bounds (--request-timeout=5s/10s elsewhere; --connect-timeout/--max-time in install.sh / setup-linux.sh). Neither file is in the R8 signed manifest (test script + workflow), so no manifest bump. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…utput (#576) (#579) * fix(installer): stop leaking user PII + tracebloc internals in logs/output (#576) Root-caused from a client's shared install log, which exposed both her identity (Username/RunAs/Machine — the PowerShell transcript header) and our internals (the `& $cosign @cosignArgs` source line + internal codes) when cosign errored. Main installer (install-k8s.ps1): - Drop Start-Transcript entirely — its fixed header IS the PII, and it also captured PowerShell's raw error rendering (source lines, internal identifiers) into the log. The shareable install-*.log is now written only via the curated Log() writer, with a PII-free header. - Route the message helpers (Info/Ok/Warn/Err/Step/PromptHeader/Hint) through Log() so the log stays useful without the transcript — it mirrors the curated on-screen output: no user PII, no tracebloc internals. Drop the Stop-Transcript calls. Bootstrap (install.ps1): - Capture cosign's output (2>&1 | Out-Null under EAP=Continue) instead of letting a native-stderr NativeCommandError dump THIS script's source line + internal identifiers to the console / any user transcript. Sanitize the verification- failure messages to plain language (no RFC-0001 R8 / manifest.sha256 / $_). Bash (common.sh): drop the username from the HOST_DATASET_DIR "not writable" error (keep the uid number). Tests: +2 Pester (main: no transcript; helpers feed the curated log) and +3 Pester (bootstrap: cosign output captured, messages carry no internal codes, still fails closed). Both suites green (418/0/9). Manifest regenerated. Scope: the top-level error boundary that stops PowerShell rendering ANY unhandled throw raw is #577; a broader sweep of internal identifiers out of all remaining messages continues under #576. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): route preflight + summary failures to the curated log (Bugbot #579) Removing Start-Transcript (for #576) left Write-PfFail and a few summary failure headlines screen-only, so a forwarded install-*.log no longer showed the actual failing preflight checks (disk/virt/storage/connectivity/memory) — only the generic Err summary + hints. Route Write-PfFail and the bad_creds / image_pull_ca / diagnostics-archive failure headlines through Log(). +1 Pester (Write-PfFail reaches the log). Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): log the classified final state for every summary branch (Bugbot #579) The default Print-Summary branch (image_pull / crash / other non-ready) still printed its failure headline via Write-Host only, so those outcomes missed the curated log after the Start-Transcript removal — while bad_creds / image_pull_ca were routed. Add a central `Log "Final client state: <state>"` before the switch so EVERY branch is covered, and route the default headline too. +1 Pester (Print-Summary records the final state). Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(bootstrap): sanitize the bash cosign-failure message too (#576 review parity) Reviewer (saadqbal) on #579: install.ps1's cosign message was sanitized but the bash bootstrap still printed 'cosign signature verification FAILED for manifest.sha256 — refusing to install', leaking the same internals. Match the PowerShell wording: 'Couldn't confirm the installer download is authentic, so the install stopped before changing anything on your machine.' Same for the success line ('Download verified as published by tracebloc'). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(bootstrap): expect the sanitized cosign-failure message (#576 review) install.sh's cosign-failure message was sanitized in 2aa35db (reviewer parity), so install-bootstrap.bats's 'cosign signature failure aborts' test — which pinned the old 'signature verification FAILED' string — must expect the new plain wording. Behaviour coverage (aborts + never degrades to a same-channel sha256) is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…rd (#586) (#587) House-style alignment (from the #576 review): the product name is lowercase `tracebloc` always, and the installers must feel one-to-one across platforms (bash is the gold standard). PowerShell had capital-T "Tracebloc" in two user-facing lines while the rest of the copy is lowercase. - install.ps1: "Downloading Tracebloc client installer" / "Running Tracebloc environment setup" -> lowercase `tracebloc`. - check-style.sh: new guard fails on capital-T `Tracebloc` in user-facing text so the casing can't drift again (same spirit as the #435 single-source facts guard). Exempts comments + PascalCase identifiers (Get-Tracebloc… , the TraceblocInstallerResume resume key); honours `# style-guard: allow`. Verified: check-style.sh clean; the guard flags a real "Tracebloc client" line and excludes identifiers/comments; shellcheck + PS parse clean. Closes #586. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Set-Content/Add-Content default to PS 5.1's ANSI encoding, corrupting non-ASCII host paths/messages in the -Diagnose log; every other writer in this file already pins -Encoding UTF8. (Bugbot on the staging promotion, backend#1497 sibling.)
…th a raw stack (#577) (#588) * fix(installer): top-level error boundary so PowerShell never crashes with a stack (#577) The main installer (install-k8s.ps1) had NO top-level try/catch, so any unhandled throw (or PS runtime error) rendered PowerShell's raw source line + stack and terminated the session — exactly Anubha's screen. Wrap the whole main run (inside the TB_PESTER guard) in a top-level try/catch that routes any crash through a new Show-FatalError: one clean "Installation stopped" line + the reason (the exception MESSAGE, curated at the throw sites per #576 — never the stack) + the log location + a safe-to-re-run hint, then exit 1. Intentional exits (fast-path, Err, final) pass straight through — verified exit-in-try is not caught while throw-in-try is. Stack traces are never shown OR logged (no internals). Bootstrap (install.ps1) already had a top-level try/catch; enhance its message to the same clean "Installation stopped … safe to re-run" shape. +3 Pester (main wrapped in try/catch -> Show-FatalError; clean render with reason + re-run hint and no stack/source; reason logged, stack never). Manifest regenerated. Built on #576 (needs its no-transcript / curated Log world); PR opens once #576 lands so it can target a clean develop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): guaranteed finally + trap for the PS error boundary (#577) Completes the PowerShell side of #577 to Lukas's acceptance. The top-level boundary now has a guaranteed `finally` + a last-resort `trap`, mirroring bash's exit-code-guarded install_cleanup: - $script:OutcomeReported is set on every terminal path (normal finish, Err, caught crash via Show-FatalError, fast-path, help/diagnose). - The finally shows a clean "interrupted" line (Show-Interrupted: log + re-run, no stack) ONLY when nothing reported an outcome — i.e. Ctrl-C / abnormal termination — so the window never just vanishes. - The trap catches anything that terminates OUTSIDE the try (defined inside the TB_PESTER guard so it never fires under the test dot-source). Verified: reported exits stay quiet, crashes route through Show-FatalError, an unreported exit fires Show-Interrupted; exit codes preserved. +3 Pester. Manifest regenerated. Bash symmetry (capture a failing tool's raw stderr -> curated line before the closer) is next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): bash graceful failure — capture direct native-call stderr (#577) Completes #577's bash symmetry. The big steps already curate (spin_cmd_bounded captures tool output to the log + shows a clean ✖ line) and install_cleanup is a clean conditional closer; the residual raw-stderr-before-the-closer leaks were a few DIRECT native calls under `set -euo pipefail`: - cluster.sh: `k3d cluster start` (reuse path) now redirects to the log + surfaces a curated error() on failure. - gpu-plugins.sh: `kubectl apply` (device-plugin manifest) and `kubectl rollout status` now redirect to the log, so only the caller's curated error/warn shows. A failing tool now surfaces our plain-language line, not its raw stderr, before the closer — bash graceful *failure*, not just a graceful closer. Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): GPU device-plugin failure is recoverable, not fatal (#577) Per #577's fatal-vs-recoverable acceptance (optional steps warn + continue): a GPU device-plugin download/apply failure was fatal on BOTH platforms — gpu-plugins.sh `error "Failed to enable GPU acceleration"` and install-k8s.ps1 `Err "Failed to enable GPU acceleration"` both exit — so a GPU hiccup aborted the whole install instead of falling back to CPU mode (which the client fully supports). Now both warn + continue in CPU mode: - bash: `_apply_remote_manifest` failure -> warn + return 0. - PS: wrap the download/apply in a catch so a failure can't reach the top-level boundary; warn + continue; also capture `kubectl apply` raw stderr (#577). Mirrors the NVIDIA-container-toolkit timeout, which already warns and carries on. +1 Pester. Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): gate GPU success message on kubectl exit code (#577) Bugbot (High): the GPU device-plugin apply/rollout discarded output into $null and never checked $LASTEXITCODE. Because a native kubectl non-zero exit does not throw, a failed apply fell through to Ok "GPU acceleration enabled." - a false success the operator would trust, and (unlike bash) the failure was neither warned nor logged. Capture each kubectl call's output to the install log and gate the success message on $LASTEXITCODE; on any non-zero exit, warn + continue in CPU mode (mirrors the bash gpu-plugins.sh path). Adds a regression test asserting the success message is exit-code-gated and the false-success $null-discard pattern is gone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): reboot-pending stop marks outcome reported (#577) Bugbot (Medium): the reboot-pending path prints reboot/resume guidance and exits 2, but never set $script:OutcomeReported. The new top-level finally then treated that intentional stop as an interruption and appended Show-Interrupted, so on a common Step 1 path the operator saw a contradictory second outcome. Set $script:OutcomeReported before the reboot block's exits (covers both exit 2 paths and the Restart-Computer path), so the finally leaves the clean reboot guidance as the sole outcome. Adds a regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): bound k3d start + GPU apply so log-redirected calls can't hang (#577) Bugbot flagged two hang risks introduced by the #577 log redirects: piping a call's output to the log hides console progress, so an unbounded call now hangs silently instead of failing into the new curated error/warn paths. - cluster.sh: `k3d cluster start` waits for the server with no deadline by default; add `--wait --timeout 5m` (parity with the Windows installer's 5-minute start deadline) so a wedged Docker fails into the curated error instead of hanging. - gpu-plugins.sh: the shared `_apply_remote_manifest` (nvidia + amd) now bounds `kubectl apply` with `--request-timeout=30s`, mirroring the node-probe in the same file, so a wedged API server falls through to the recoverable CPU-mode warn. - install-k8s.ps1: same `--request-timeout=30s` on the PS GPU apply for parity (the PS `k3d cluster start` is already bounded by Wait-ProcessWithDeadline). Adds regression tests: cluster start is --timeout-bounded (bats), the GPU apply carries --request-timeout (bats + Pester). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): gate bash GPU success on rollout + fix -Diagnose interrupt gap (#577) Bugbot round 4: - gpu-plugins.sh (Medium): after a non-zero `kubectl rollout status`, bash still printed success "GPU acceleration enabled." while the failure went only to the log - the false-success bug already fixed on the PS side, still present in bash. Gate the success on the rollout exit code (nvidia AND amd paths); on failure warn + continue in CPU mode, matching the PS peer. - install-k8s.ps1 (Low): $script:OutcomeReported was set to $true BEFORE the long Invoke-DiagnoseBundle ran, so an interrupt mid-collection skipped Show-Interrupted - the silent death the boundary exists to prevent. Set the flag AFTER the bundle completes. Adds regression tests: nvidia success is rollout-gated (bats); the -Diagnose flag is set only after the bundle (Pester). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): skip GPU verify when the plugin deploy failed / CPU-mode (#577) Bugbot round 5 (Medium): making the GPU device-plugin step recoverable (#577) means a failed apply/download now continues the install - but the caller still ran the GPU verify step unconditionally. Operators saw "continuing in CPU mode", then waited ~90s and got a contradictory "still initializing" warning for a plugin never deployed. Have the deploy signal whether the plugin was actually deployed, and gate verify on it: - gpu-plugins.sh: _deploy_nvidia_plugin / _deploy_amd_plugin return non-zero on every CPU-mode path (apply failure, unconfirmed rollout, master-fallback failure); 0 when deployed/already-present. - install-k8s.sh: `if deploy_gpu_device_plugin; then verify_gpu; fi` (the `if` also keeps a non-zero deploy from tripping set -e). - install-k8s.ps1: Install-GpuDevicePlugin returns $true/$false (Invoke-WithRetry sunk to $null to avoid return-value pollution); caller runs Confirm-GpuNode only on $true. Adds regression tests: verify is gated (bats + Pester), and a functional bats test that _deploy_nvidia_plugin returns non-zero on a CPU-mode path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): close the GPU "can't hang" parity + gate the amd master fallback (#577) Review follow-ups (saadqbal) on the GPU path: - Bound the last unbounded kubectl probes so a wedged API server can't hang before the bounded apply is reached: the nvidia/amd existence checks (bash + PS) and PS Confirm-GpuNode's node probe now carry --request-timeout=5s, matching bash's verify_gpu. Closes the "can't hang" goal symmetrically. - Gate the amd master fallback on rollout like the primary paths: a master apply that never rolls out now warns + continues in CPU mode instead of returning a false success that made the caller's verify poll ~90s. - Extract the shared _gpu_rollout_gate helper (nvidia, amd-primary, amd-master) so the "no false enabled / no dead verify wait" behaviour is identical everywhere and not duplicated three times. Tests: adds _gpu_rollout_gate functional tests (rollout fail -> warn+non-zero; ok -> success+0), asserts the existence probes are --request-timeout-bounded, that both amd paths gate on rollout, and that the PS probes carry --request-timeout. Updates the in-branch nvidia-gating test to the refactored (helper-delegated) form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Resolves the install-k8s.ps1 + manifest.sha256 conflict from #588 (graceful error boundary). Re-applies the two -Encoding UTF8 edits to the log writers on top of develop and regenerates the R8 manifest hash.
…ead (#582) (#590) * feat(installer): preflight network profile — proxy + TLS-inspection read (#582) Child 1/4 of #578. On a restricted/corporate network the installer dove straight into the long work and only failed minutes in, with no up-front sense of what the network allows. Add a fast, bounded, non-fatal network probe that runs BEFORE the endpoint checks and prints a plain-language "network profile" line: - Explicit proxy detected from the environment (HTTPS preferred), announced as a bare host:port with any user:pass credentials stripped (PII-free, #576). - A configured corporate CA bundle (TRACEBLOC_CA_BUNDLE / CURL_CA_BUNDLE) announced. - TLS inspection detected affirmatively (best-effort): read the issuer of the cert served for a well-known public host (through the proxy when set); a non-public issuer means a corporate CA is re-signing TLS. Bounded and non-hanging — bash needs openssl + a timeout tool (else "unknown"); PowerShell uses .NET with an 8s timeout and a scoped, restored cert-validation callback. A plain direct connection stays silent (the reachability lines already confirm egress). The existing break-and-inspect hint keeps owning the actionable CA fix. Cross-platform with one-to-one copy (preflight.sh _pf_network_* / install-k8s.ps1 Show-NetworkProfile). preflight.sh + install-k8s.ps1 are manifested; manifest regenerated. Tests: 9 bats + 7 Pester covering credential stripping, proxy precedence, issuer classification, the silent-direct path, and the noteworthy line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): TLS-inspection probe must connect through an authenticated proxy (#582) Bugbot (Medium): the network profile stripped proxy credentials for DISPLAY (correct, PII-safe) but reused that stripped host:port for the probe's own CONNECTION. On an authenticated corporate proxy the HEAD/CONNECT then 407s, issuer capture fails, and the profile returns "unknown" — so inspection is never reported on the exact TLS-inspecting networks the probe exists to detect. Connect with the real credentials; keep display stripped: - preflight.sh: add _pf_env_proxy_raw (verbatim env value). _pf_detect_tls_inspection passes -proxy host:port plus -proxy_user/-proxy_pass (openssl >= 3.0) when the proxy carries credentials. _pf_env_proxy / the profile line stay credential-stripped. - install-k8s.ps1: add Get-EnvProxyRaw. Get-TlsInspectionState builds the WebProxy from the raw URL and sets WebProxy.Credentials (NetworkCredential) when UserInfo is present; Show-NetworkProfile still uses the stripped Get-EnvProxy for the printed line. Credentials reach openssl / the WebProxy only — never printed or logged. Tests: bash + Pester assert the raw helper preserves creds while display strips, that the openssl CONNECT carries -proxy_user on an auth proxy (with the password absent from the result), and that the probe uses the credentialed proxy while the display path does not. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): harden the bash TLS-inspection auth-proxy path (#582) Four Bugbot follow-ups on the bash credential-passing added last commit: - [High] Password no longer sits in openssl argv (visible via ps / /proc/*/cmdline): pass it via env: — openssl reads $_TB_PROXY_PASS, exported ONLY inside the probe's command-substitution subshell, so it never reaches argv or the parent shell. - [Med] The issuer pipeline is guarded with `|| issuer=""`, so a failed/timed-out probe returns "unknown" instead of aborting _pf_detect_tls_inspection under `set -euo pipefail` (mirrors _pf_probe_url). The bats setup now stubs the probe by default (like _pf_probe_url) so connectivity tests never hit the real github.com; the real-probe tests source preflight.sh fresh in a subshell. - [Med] Username-only proxies (http://user@host, no password) no longer reuse the username as the password — split on ':' only when present. - [Med] Proxy credentials are URL-decoded (_pf_urldecode) before use, matching the PowerShell peer's Uri.UnescapeDataString, so %-encoded secrets authenticate the same on Linux and Windows. Tests: url-decode parity, env:-not-argv (password absent from openssl argv), username-only-not-reused, and the openssl-absent "unknown" guard — all sourcing the real probe in a hermetic subshell. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Re-resolves the recurring install-k8s.ps1 + manifest conflict; re-applies the two -Encoding UTF8 log-writer edits on develop's tip and regenerates the R8 manifest.
…591) Set-Content -Encoding UTF8 prepends a BOM on PowerShell 5.1, so the log started with EF BB BF. Use the file's own no-BOM writer ([System.IO.File]::WriteAllText with UTF8Encoding($false)); the Add-Content append path was already BOM-free. Regenerates the R8 manifest hash.
…s (Bugbot, #591) Start-InstallLog now writes the curated install log as UTF-8 without a BOM, but Edit-Redaction -- which -Diagnose runs over every collected file before zipping, including the copied install-*.log -- still read via `Get-Content -Raw` with no encoding. On Windows PowerShell 5.1 a bare read decodes a BOM-less file as ANSI, so it mangled every non-ASCII host path/message back into mojibake in the bundle operators send: the exact corruption this PR set out to fix. Pin `-Encoding UTF8` on that read. It reads the BOM-less log correctly and still reads the Out-File -Encoding utf8 outputs (which carry a BOM on 5.1) fine -- the BOM is detected and stripped. Regenerated scripts/manifest.sha256 for the new install-k8s.ps1 hash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(installer): pin -Encoding UTF8 on install-log writes
… curl (#583) (#592) * feat(installer): wire the corporate CA into cosign/helm/git, not just curl (#583) Child 2/4 of #578 — the single biggest lever for TLS-inspecting corporate networks. A break-and-inspect proxy re-signs HTTPS with a corporate root CA; tools that don't trust that root fail x509. curl already honored CURL_CA_BUNDLE and the k3d NODES got the CA at cluster-create (#424), but cosign, helm and git got nothing — the class behind both field failures (the k3d-checksum and the cosign/sigstore x509 failures). Extend the SAME resolved CA to every host tool that doesn't inherit the system store: - Bootstrap (install.sh / install.ps1): export SSL_CERT_FILE from TRACEBLOC_CA_BUNDLE / CURL_CA_BUNDLE before cosign runs, so keyless verification's HTTPS calls trust the corporate CA (cosign's Go client reads SSL_CERT_FILE). Not manifested (trust root). - Main installer: wire_ca_trust (bash) / Set-ToolTrust (PS) export SSL_CERT_FILE + GIT_SSL_CAINFO (+ CURL_CA_BUNDLE) from the resolved bundle, run BEFORE preflight's probes and any download, so helm, git and curl all trust it. Plain-language line: "Trusting your company's certificate for cosign, helm, git and downloads." No-op when unconfigured; fails fast on a set-but-unreadable bundle. When no CA is provided, tools fall back to the system store (which enterprise IT usually populates) — no user knowledge of CAs required in that common case. Tests: bats (wire_ca_trust exports/no-op/hard-fail; bootstrap cosign sees SSL_CERT_FILE via a recording mock) + Pester (Set-ToolTrust exports/no-op; bootstrap sets SSL_CERT_FILE). cluster.sh / install-k8s.* are manifested; manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): don't re-export CURL_CA_BUNDLE when wiring the corporate CA (#583) Bugbot (Medium): _bootstrap_wire_ca / wire_ca_trust / Set-ToolTrust exported CURL_CA_BUNDLE derived from the resolved bundle. But CURL_CA_BUNDLE is replace-not- augment, and TRACEBLOC_CA_BUNDLE is typically a corp-root-ONLY PEM (its documented k3d-node use), so re-exporting it could REPLACE curl's working trust with a bundle missing the public roots — breaking the manifest/sig fetches that were succeeding via the system store. The PowerShell bootstrap already set only SSL_CERT_FILE. curl already honors the user's own CURL_CA_BUNDLE natively, so we never re-export it. We only wire the tools that had NO corporate trust before: cosign/helm/Go (SSL_CERT_FILE) and git (GIT_SSL_CAINFO). This also makes the bash and PS bootstraps symmetric (both set only SSL_CERT_FILE). Adds a regression test asserting wire_ca_trust leaves a pre-set CURL_CA_BUNDLE intact while still exporting SSL_CERT_FILE for the other tools. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): SSL_CERT_FILE is Linux-only for Go; be honest on Win/mac + fail fast (#583) Two Bugbot follow-ups: - [High] SSL_CERT_FILE is inert for cosign/helm on Windows AND macOS. cosign/helm are Go; Go reads SSL_CERT_FILE only on Linux — on Windows it uses the certificate store and on macOS the Keychain, ignoring the env var. My comment wrongly claimed modern Go honors it on Windows. Corrected: * Linux: keep SSL_CERT_FILE (effective) + GIT_SSL_CAINFO; announce cosign/helm/git. * macOS (wire_ca_trust): still set the vars but announce only git + downloads, and hint that cosign/helm read the Keychain (add the CA there, or use the offline path). * Windows (Set-ToolTrust): set GIT_SSL_CAINFO (Git-for-Windows is OpenSSL-backed); do NOT set SSL_CERT_FILE (inert/misleading); hint cosign/helm read the cert store. * Windows bootstrap (install.ps1): drop the inert SSL_CERT_FILE set entirely. The robust cross-platform cosign fix for a PEM-only CA is the offline bundle (#584). - [Med] A set-but-unreadable CA bundle now fails fast with a clear "can't be read" message in both bootstraps, instead of silently no-opping and surfacing later as a generic cosign authenticity error. Tests updated for the platform-accurate behavior: Linux vs macOS announce, Windows sets only GIT_SSL_CAINFO (not SSL_CERT_FILE) + store hint, and the bootstrap fail-fast on a bad CA path (bash + PS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): don't clobber a pre-set SSL_CERT_FILE / GIT_SSL_CAINFO either (#583) Bugbot (Medium): wire_ca_trust / Set-ToolTrust set GIT_SSL_CAINFO unconditionally from the resolved (corp-root-only) bundle. GIT_SSL_CAINFO is replace-not-augment (same OpenSSL contract as CURL_CA_BUNDLE), so a fuller pre-set git CA bundle got overwritten and host git HTTPS could x509-fail on non-intercepted endpoints. The same applies to SSL_CERT_FILE. Apply the consistent rule everywhere we wire trust: only set a trust var the user hasn't already set — never override their existing bundle. Covers SSL_CERT_FILE (bash bootstrap + wire_ca_trust) and GIT_SSL_CAINFO (wire_ca_trust + Set-ToolTrust); curl's CURL_CA_BUNDLE was already left untouched. Adds regression tests (bash + Pester) that a pre-set SSL_CERT_FILE / GIT_SSL_CAINFO survives while an unset one is still wired. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): don't over-claim download trust; check CA readability on Windows (#583) Two Bugbot follow-ups: - [Med] Set-ToolTrust printed "…and downloads" trust the corporate CA, but on Windows downloads use the certificate store (Invoke-WebRequest/Schannel) which this path never configures — only GIT_SSL_CAINFO. Green message, still-failing fetch. The announce now names only what's actually wired (git), and the store hint covers cosign, helm AND the installer's downloads. Same over-claim dropped on Linux/macOS: curl "downloads" trust the user's own CURL_CA_BUNDLE (which we deliberately don't touch), so Linux announces "cosign, helm and git" and macOS "git" only. - [Med] The Windows bootstrap CA fail-fast only tested existence (Test-Path -PathType Leaf); a present-but-unreadable file slipped through to a generic cosign error. It now also opens the file (mirrors bash -r and Resolve-CaBundle) and fails fast with a clear "can't be read" message. Tests updated: Linux/macOS announce wording (no "downloads"), and the Windows success line names only git while the store hint covers downloads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#583): make the new CA-wiring assertions enforce The 20 assertions this PR adds to cluster.bats and install-bootstrap.bats were written multi-assertion without `|| return 1`, so under bats only the last command decided each test -- a regression in the cosign/helm/git CA wiring would have passed silently. Same hardening #527 applies suite-wide; appending it here keeps this PR green under #527's bats-hygiene gate whichever lands first. Both suites pass with enforcement on. The Pester additions need nothing: Should throws, so every assertion already enforces. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#583): on macOS, wire nothing — Keychain guidance instead (Bugbot ×2) Two Darwin holes, same root: exporting trust vars the platform ignores. - SSL_CERT_FILE: Go reads the Keychain on macOS, so the export helped neither cosign nor helm — while OpenSSL-backed curl DOES honor it, replace-not- augment, so a corp-root-only bundle shrank download trust for zero gain. Dropped from wire_ca_trust and platform-gated in _bootstrap_wire_ca (readability fail-fast still runs everywhere). - GIT_SSL_CAINFO: Apple's system git (SecureTransport) ignores it, and the clone that matters most — Homebrew's own bootstrap — runs system git. The "Trusting your company's certificate for git" claim was false on Darwin. Darwin now exports neither var and prints one honest hint: add the CA to the login Keychain so git, cosign and helm trust it. Same decision, same reason as Windows (store-based trust; no inert claims). Tests: Darwin announce updated, Darwin exports-nothing pinned at both layers, and the Linux bootstrap test now stubs uname so it doesn't flip on a macOS dev machine. Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#583): say only what was actually wired (Bugbot) wire_ca_trust and Set-ToolTrust printed the green "Trusting your company's certificate…" even when every only-if-unset guard skipped its export -- claiming wiring that did not happen, and masking a pre-set bundle that may still lack the corporate CA. Both now track wired vs kept per variable: the success names only what was actually exported, and anything kept gets an explicit "make sure that bundle includes your company's CA" hint instead. Pinned on both layers: both-kept claims nothing, partial pre-set claims only the wired half (bats), skipped export claims nothing (Pester). Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Lukas Wuttke <lukas@tracebloc.io>
…0) (#595) Pin every third-party (non-tracebloc, non-actions) action ref to the full 40-char commit SHA it currently resolves to, with a trailing exact-version comment (D10, RFC-BACKEND-1405). Behaviour-preserving: no version changes, only removal of silent tag mutation. Part of tracebloc/backend#1490. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ce (backend#1526) (#596) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…able (#525) * fix(installer): make the absent-key path in _extract_yaml_value reachable `_extract_yaml_value` piped into `grep`. On an ABSENT key grep exits 1; under `set -o pipefail` that rc propagates out of the pipeline and out of the assignment, so under `set -e` the function aborts at the assignment — making the very next line, `[[ -z "$line" ]] && return`, unreachable in exactly the shape it exists to handle. Latent, not live: all three call sites (lines 209, 651, 652) use the `$( )` command-substitution form, which suspends errexit for the function body. But the documented contract is "empty when the key is absent", so a bare call is the natural next refactor — and it would abort the install mid-step. Fix is the house idiom already used in assess.sh and common.sh `_chart_version`: `|| line=""` on the assignment. Catching any non-zero also keeps the path reachable if `head -1` ever SIGPIPEs grep (141), the sibling shape fixed in #522. Contract written down above the function. Verified (bash 3.2.57, GNU grep): - bare call, absent key, errexit live -> before: exit 1 (aborts, `return` never runs) · after: exit 0, empty output, execution continues - `v="$(_extract_yaml_value …)"`, absent key -> exit 0, "" (unchanged) - found-key, quoting, and unreadable-file paths unchanged Adds a bats case pinning the BARE-statement call under `set -euo pipefail`. Mutation-tested: it fails against the unfixed function, so it cannot rot into a no-op. Regenerated scripts/manifest.sha256 (R8 gate). Fixes #523 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: re-trigger — no workflow fired on the PR-open event (empty commit) Actions dispatched nothing for this PR: 0 runs on the branch 10 minutes after open, while a sibling PR opened 3 minutes later got all 7. Not a paths/types filter (standard-checks + chart-version-guard have no paths filter and also did not fire), not a draft, not an incident (status green), and PR head == remote head == local head. GitHub-side miss on the open event; `synchronize` re-dispatches all six gating workflows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(installer): drop head from the pipeline — a duplicate key must keep its value (Bugbot, #525) The first fix (`grep | head -1 || line=""`) traded one failure for another: on a DUPLICATE key, head exits after the first line and SIGPIPEs grep (141); under pipefail the `|| line=""` fallback then wiped the successfully captured value, so detect_installed_client could miss a clientId and fail open toward overwrite. Capture every match and take the first line in the shell (`${line%%$'\n'*}`) — no downstream consumer, so grep's rc is 1 exactly when there is no match, which is the one case the fallback exists for. Regression test pins the duplicate-key bare-call shape; manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* test(bats): make every assertion enforce — 1240 were advisory Under Bats (verified 1.13.0) only the LAST command in a test body decides the result, so a failing assertion anywhere earlier is silently ignored: @test "middle failure ignored" { [[ "abc" == *"zzz"* ]] # FALSE [[ "abc" == *"abc"* ]] # TRUE (last) } # -> ok This suite is written multi-assertion throughout, so most assertions could not fail their test. Appending `|| return 1` makes them enforce. Scope is about twice what it first looked. It is not only `[[ ]]`: single-bracket `[ ... ]` has identical semantics and there are MORE of them (609 vs 574), plus 61 negated bare commands. 1240 assertions across 15 files — setup-linux.bats 296, cluster.bats 153, install-client-helm.bats 146, common.bats 118, preflight.bats 102, and the rest smaller. Only whole-line assertions INSIDE an @test body are touched. Helpers and setup/teardown are excluded (a bare `return` there means something different), as are the 9 control-flow `if/while` conditions and 18 lines already chained with && / ||. All files still parse (bats --count), no control-flow line was modified, and nothing was double-appended. TRIAGE RESULT: zero new failures. All 1240 were already true — the suite was accidentally correct, so there was no hidden-bug vs stale-assertion split to report. No assertion was deleted or weakened to reach green. That result only means something if the hardening has teeth, so it was proven rather than assumed. cluster.bats's "_augment_no_proxy: empty host NO_PROXY" asserts 7 substrings and only enforced the last. Deleting `localhost` from TB_NO_PROXY_DEFAULTS — the entry that keeps a corporate proxy from intercepting loopback — is a real regression, and: mutated source + ORIGINAL tests -> ok (invisible) mutated source + HARDENED tests -> not ok (caught) Guard, so the pattern cannot come back: scripts/tests/bats-hygiene.bats plus a shared scanner, scripts/tests/unenforced-assertions.awk (one implementation, used by the guard and by its own self-tests). Three tests: the suite is clean; the scanner flags an un-hardened assertion and spares a hardened one; and it ignores control flow, chained lines, helpers and HEREDOC BODIES. That last exclusion is not cosmetic — the first version flagged its own fixture, which would have made any future test embedding example bats source a false positive. The guard was mutation-tested against the real suite too: un-hardening one line in cluster.bats makes it fail, naming the exact file:line. Gates: bats scripts/tests/*.bats -> plan 693, ok 693, not ok 0 (complete TAP run, plan line checked — a truncated read can look green while half the suite never reports); shellcheck --severity=error over the CI file set -> rc=0; check-style clean; check-drift no drift; gen-manifest.sh --check current (only tests changed, and tests are not part of the hashed set). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(bats-hygiene): scanner sees internal-OR and negated-bare assertions (Bugbot #527) Two Bugbot findings on the hygiene guard this PR introduces. Both real: the guard could report "suite is clean" while assertions stayed advisory — the exact failure mode the PR exists to close. Measured semantics first (bats 1.13.0, bash 3.2 system bash), because the old header's "only the LAST command decides" was too broad. Bats does run bodies under errexit; exactly two classes escape it: [[ ... ]] bash 3.2 (macOS system bash) does not fire errexit for a failing conditional expression — a middle one is ignored ! cmd POSIX: a status inverted with '!' is never propagated, so this escapes on EVERY bash, CI included grep -q ... a plain bare command DOES fail the test — correctly not reported 1) Scanner skips internal-OR assertions — REAL. `[[ a || b ]]` is ONE assertion whose ||/&& is internal; it exits non-zero on failure like any other and needs `|| return 1` too. The scanner skipped every line merely CONTAINING ||/&&, and only matched `[`/`[[` that closed on the same line, so it missed both single-line internal-OR and multi-line forms. Rewritten to build a logical line (trailing backslash, or a newline inside the brackets) and to locate the closer that matches the opener, so only a TOP-level chain earns the exemption: `[[ a ]] || fail` still skipped, `[[ a || b ]]` flagged, and `||` appearing only inside a quoted grep pattern no longer hides an assertion. Multi-line offenders are reported joined, at their first line. Six offenders it now catches (Bugbot named two; four are the same class): install-bootstrap.bats:144 and :154 — mid-body, so genuinely advisory — common.bats:179, install-client-helm.bats:887, preflight.bats:542, summary.bats:73. All six now end in `|| return 1`. 2) Guard omits negated bare commands — REAL. The PR hardened 61 `! cmd` assertions but the guard did not cover the class, so a later unhardened one would pass unnoticed — and this is the class that is advisory on every bash, not just 3.2. The scanner now flags standalone `! cmd ...`, while sparing `! cmd || return 1`, `if ! cmd`, bare `cmd`, and `run ! cmd`. Zero live offenders: the 61 are all hardened. Failing-test-first evidence, both directions verified by flipping the change: - two new bats-hygiene tests (internal-OR incl. both continuation styles and a pattern-only `||`; negated bare commands) fail on the old scanner, pass on the new one, and assert the spared cases so the scanner cannot over-report - with the new scanner and the un-hardened files, the "suite is clean" test fails and names all six offenders - end to end on real code: blanking install.sh's "not an immutable release tag" message left install-bootstrap.bats's two path-traversal tests GREEN before the fix and fails both after — an R8 regression the suite had been ignoring Also made the scanner portable (\b and `close` are not safe in every awk) and corrected the guard's header to the measured semantics. Local: bats scripts/tests/*.bats 695/695, shellcheck --severity=error clean, bash -n clean, gen-manifest.sh --check up to date, check-style.sh clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(chart-guard): harden the 46 assertions #530 added in parallel CI went red on the merge commit, not on this branch. Diagnosis first, because the answer changes the fix: #530 ("chart-guard: cover every published chart") merged scripts/tests/ chart-version-guard.bats into develop at 2026-07-31T15:59Z — 40 minutes AFTER this branch's last green Installer-tests run (30642417378, head 91a5fdd, 15:19Z). The file was written before this convention existed, so all 46 of its standalone assertions are bare. GitHub tests refs/pull/527/merge, so the guard correctly reported them. NOT caused by the scanner rewrite. The OLD scanner, exactly as shipped in 91a5fdd, flags the same 46 lines on that file — byte-identical output: awk -f <91a5fdd's scanner> chart-version-guard.bats | wc -l -> 46 awk -f <new scanner> chart-version-guard.bats | wc -l -> 46 diff of the two -> identical They are all plain single-line `[ ... ]` / `[[ ... ]]`, none of the classes this PR's rewrite added. So the branch head would have gone red on the same merge commit with or without my commit — this is develop drift meeting a guard that only just started existing, which is the guard doing its job on the first file that arrived after it. Fix: merge develop and append `|| return 1` to the 46. No assertion reworded, deleted or weakened; the guard is not relaxed to accommodate the new file. Local, post-merge: bats scripts/tests/*.bats 718/718, scanner reports 0, shellcheck --severity=error clean (incl. the new chart-version-guard.sh), gen-manifest.sh --check up to date, check-style.sh clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(bats-hygiene): a quoted <<TAG or a herestring is not a heredoc (Bugbot #527) REAL, and the most serious of the three findings — it made the guard lie. The heredoc detector matched `<<TAG` anywhere on a line, including inside a quoted string. Once tripped, it looked for a BARE terminator line that never comes, so the scanner silently ignored every remaining line in that file while the "suite is clean" test still reported clean. Proven on the real file, not just in theory. Appending an unhardened assertion to the end of bats-hygiene.bats — after its own `printf " cat > f <<'EOF'\n"`: awk -f unenforced-assertions.awk bats-hygiene.bats -> NO OUTPUT (invisible) bats bats-hygiene.bats -> ok 1 ... assertion ... ends in '|| return 1' The guard reporting clean while a bare assertion sits in the file it is scanning is the worst failure this PR could ship, since every other claim in the PR rests on that scan. A SECOND live instance Bugbot did not name: `<<<` herestrings. The regex matched from the second `<` of `run guard_leftover_data <<< "r"`, taking tag `r`, so leftover-guard.bats was swallowed from line 131 onward — the same canary appended there was equally invisible. Bugbot's Additional Locations listed only bats-hygiene.bats#L135-138. Fix, three parts: - `quoted_at()` walks shell quoting state, so a `<<TAG` inside '...' or "..." is text, not a redirection - `<<` immediately preceded by `<` is a herestring, not an opener - safety valve: an @test at column 0 ends heredoc-skip mode, so no future mis-detection can ever hide more than one test's worth of lines Real heredocs still skip their bodies: 12 genuine openers across the suite are still detected, and the pre-existing "ignores ... heredoc bodies" test fails if the tracking is deleted rather than fixed — so "stop tracking heredocs" cannot pass as a fix. Verified by disabling it: that test flips to not ok. Worth recording that the suite-clean scan CANNOT catch that regression (no real heredoc body in the suite contains a bare bracket line), so the fixture test is the only guard on it. Two new tests, flipped in both directions: old scanner -> not ok 5 (expected line 3 to be flagged) not ok 6 (expected line 6 to be flagged) new scanner -> ok 5, ok 6 Each fixture carries several distinguishable entries and asserts the exact offender count plus the spared lines, so neither can pass by over-reporting or by a fixture too small to tell an anchored rule from a loosened one. Local: bats scripts/tests/*.bats 720/720, scanner reports 0, shellcheck --severity=error clean, gen-manifest.sh --check up to date, check-style.sh clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(bats): enforce assertions in develop's newly-merged tests (#527 hygiene) Merging develop brought in test files/tests added after this branch forked (check-facts.bats, index-invariants.bats, setup-macos-lifecycle.bats, and new preflight.bats cases) whose standalone assertions were written in the bare, advisory form. bats-hygiene.bats — the enforcing-assertion guard this PR adds — correctly flagged 134 of them. Append `|| return 1` to each so every assertion can fail its test, exactly as this PR does across the rest of the suite. Mechanical: `|| return 1` inserted before any trailing inline comment; negated bare commands (`! grep …`) get the same enforcing form. Verified by re-running the scanner to zero offenders and the full bats suite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(bats): scanner enforces on real `|| return 1`, not the substring (Bugbot) The enforcing check was a line-wide substring match for `|| return 1`, so an assertion that merely MENTIONED the marker was treated as hardened though it does not enforce: `[[ "$output" == *"|| return 1"* ]]` (marker inside a quoted pattern) or `[[ "$x" == y ]] # ... || return 1` (marker only in a trailing comment) slipped through the guard (Cursor Bugbot, Medium). Add `strip_comment` (drop an unquoted trailing comment) + `is_enforcing` (require a `|| return 1` that is outside quotes and outside the comment), reusing the existing quote walker. New bats-hygiene self-test proves both fooling shapes are flagged and a real top-level `|| return 1` is still spared. Whole-suite sweep still reports 0 offenders, so the 134 conversions in the prior commit remain correctly recognized. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(bats): scanner catches compound-line assertions; harden the 107 it revealed (Bugbot) The unenforced-assertions scanner classified a line as a bracket assertion only when it OPENED with `[[`/`[`, so a mid-line assertion — `run x; [[ ... ]]`, the last command of a compound line — was invisible. On bash 3.2 that `[[` still cannot fail the test, so 107 such assertions across preflight/check-drift/setup-* were advisory: the exact failure mode this PR closes. - Scanner: check the last `;`-segment of a compound line for a standalone bracket assertion or negated bare command (`last_segment` + `classify`). Quote-aware; `bracket_tail` distinguishes an internal `||` from a real top-level chain. - Harden the 107 revealed assertions (append `|| return 1`, before any trailing comment). No test logic changed — 304 suite tests still pass, 0 failures. - bats-hygiene.bats: regression test for the compound-line case. The other Bugbot findings on this PR (internal-OR, negated-bare, substring, false-heredoc) were already handled by earlier commits; this closes the last one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(bats): scanner robust to nested braces, one-line tests, subshell semicolons (Bugbot) The hygiene scanner reported "clean" while missing real unhardened assertions in three shapes Bugbot flagged: - a nested `name() { ... }` stub's column-0 `}` ended the @test scan early (check-drift.bats had an unhardened `[ "$_drift" -ge 1 ]` after a helm() mock) -> track brace DEPTH, not the first `}`. - one-line `@test "x" { run ...; [ ... ]; }` bodies were consumed as a bare opener and never scanned (common.bats had two) -> scan the inline body after the opening `{`. - an assertion that is not the LAST statement of a compound/one-line body -> classify each `;`-separated statement (subsumes the earlier last_segment hack, more correctly); paren-aware so a `;` inside a `( )`/`$( )` does not split a hardened `! ( a; b ) || return 1`, and comment-aware so a `;` inside a trailing comment is not split either. Hardens the 26 assertions the improved scanner then surfaced: cluster.bats / assess.bats and the new #542/#547 check-facts tests (all pulled in by the develop merge), plus check-drift.bats and the two common.bats one-liners. Adds 3 regression tests (nested braces, one-line bodies, subshell `;`). Full suite 804/804; hygiene 12/12; scanner clean. * test(bats): top-level chain must ignore quotes/subshells; join mid-line multiline brackets (Bugbot) Two more scanner gaps Bugbot flagged on the rewrite, both real: - the `||`/`&&` "already chained" exemption matched the operator anywhere in the statement, including inside a quoted pattern (`! grep -q "a||b" f`) or a `( )` subshell -> an unhardened negated command was silently treated as chained. Now a quote- and paren-aware top-level scan (`has_toplevel_chain`). - `bracket_open` only saw a continued `[[`/`[` at the START of the logical line, so a bracket opening mid-line (`run x; [[ a ||` continued onto the next line) was never joined -> a multi-line compound bracket stayed invisible. Now also checks the last `;`-segment. Adds 2 regression tests. Full suite 806/806; hygiene 14/14; scanner clean. * test(bats): scan one-line bodies whose bracket abuts the group closer (Bugbot) A one-liner with no `;` before `}` (`{ … [ a ] }`, or the no-space `[ a ]}` / `[[ a ]]}` where the closer is not recognised) left a `}` in the assertion's post-closer tail, so it read as non-standalone and stayed invisible. Strip the one-liner's group-closing `}` before classifying. Valid bats needs a `;` before `}` (verified: `f() { [ 1 = 1 ] }` is a bash syntax error), which already splits the assertion off — so this is defensive for the degenerate shapes, not a live suite offender. Regression test 15. Full suite 807/807; hygiene 15/15; scanner clean. * test(bats): make the bracket-closer finder quote-aware (Bugbot) after_close matched a blank-delimited `]]`/`]` by a word-boundary heuristic but never walked quote state — the one structural walker that wasn't quote-aware. A closer inside a quoted pattern (`[[ "$x" == "a ]] b" ]]`, `[ "$x" = "] y" ]`) was mistaken for the real closer, so the assertion read as non-standalone and an unhardened offender could slip through. Require the closer position to be unquoted (reuses quoted_at), matching split_segments / has_toplevel_chain / brace_delta / after_first_brace / strip_group_close. Regression test 16. Full suite 808/808; hygiene 16/16; scanner clean. * test(bats): harden the 24 assertions that arrived via the develop merge The hygiene gate went red on its own merge commit: develop gained gpu-nvidia.bats (2 advisory assertions) and the #582 network-profile block in preflight.bats (22 more) after this branch's sweep. Same mechanical treatment -- append `|| return 1`, comments preserved in place. Scanner reports 0 offenders; gpu-nvidia, preflight and bats-hygiene suites pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(bats): a || return 1 inside a subshell is not hardening (Bugbot) is_enforcing scanned for an unquoted `|| return 1` anywhere in the statement, so `! ( cmd || return 1 )` was spared — but that return only exits the subshell while the `!` still escapes errexit, leaving the statement advisory. Rewritten on the same quote+paren walker as has_toplevel_chain: only a top-level `|| return 1` counts. Fixture pins both subshell shapes (`( )` and `$( )`) flagged and both top-level shapes spared; the full-suite scan stays clean, so no real assertion was relying on the loophole. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(bats): a <<TAG in a comment is not a heredoc opener (Bugbot) heredoc_tag_of was quote-aware but not comment-aware, so a trailing comment DOCUMENTING heredocs opened skip mode with no terminator coming and the rest of the @test body was silently swallowed — live in this very suite, where bats-hygiene.bats comments mention <<TAG. Scan the comment-stripped line; strip_comment returns a prefix, so positions stay aligned for the quote and herestring look-arounds. Fixture pins: comment-mention doesn't skip, a real heredoc still does, and scanning resumes after its terminator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: shujaat hasan <shujaat@tracebloc.io>
…eqs hang (#593) * fix(ci): bound the two unbounded network waits behind the ubuntu Prereqs hang Three times on 2026-08-04 (#525, #592) the "Prereqs — ubuntu:*" matrix jobs died at the 20-minute job timeout with nothing in the log but "Installing Docker…", and once more failed in 20 seconds with a registry-1.docker.io timeout (exit 125). Two unbounded waits, one per layer: - Workflow: `docker run` pulls the distro image implicitly with no timeout, so Hub connectivity trouble either failed fast (exit 125) or stalled the whole job. Both container-matrix jobs (distro-prereqs, path-persist) now pre-pull with three bounded attempts (timeout 300 + backoff) and an honest "runner-to-registry connectivity, not this PR" error. - setup-linux.sh: the get.docker.com convenience script's internal apt/download.docker.com fetches carry no timeout, so a stalled connection hung silently behind the spinner. The run is now bounded at 10 minutes (healthy installs take 1-3) and fails with a clear stalled-download message telling the operator to re-run; the fetch of the script itself already had retry + curl_secure timeouts. Same shape as the existing dpkg-lock and kubectl-fetch bounds. New bats test pins the timeout bound on the get.docker.com branch (hardened with || return 1 for the incoming #527 hygiene gate). Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): distinguish a stall from a real failure; keep the pull budget small (Bugbot ×2) - setup-linux.sh: `if ! spin_cmd …; then error "stalled 10 minutes"` fired on ANY failure, mislabelling a fast real apt/script error as a stall — and it bypassed the existing spin_cmd_bounded helper, which returns 124 only on the deadline and tails the log on every failure. Switched to it: rc 124 gets the stalled-download message, any other rc gets an honest install-failed message pointing at the log tail. Harness gains a default spin_cmd_bounded mock; the bats test now pins the helper + its 600s bound. - installer-tests.yaml: three timeout-300 attempts + backoff could eat ~16 of the job's 20 minutes, so a late-succeeding pull just moved the death from the pull to the install. Bounds resized (3 × timeout 90, 10/20s backoff, ~5.5 min worst case) so the job keeps most of its budget; a healthy pull takes seconds. Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(installer): prepare-host gets prepare-host re-run advice (Bugbot) The new get.docker.com stall/failure errors always said "re-run the installer" — but with TB_PREPARE_HOST_MODE set that points an admin at a full provision as themselves, the exact outcome prepare-host exists to prevent. Pick the re-run verb by mode, matching the daemon-check errors later in the same function. Manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… PVC mounts (RFC-0003 D9 Track B, client-runtime#203) (#594) Companion to client-runtime#261: flag-gated ClusterRole grants for the PV provisioning/GC path (persistentvolumes create/get/list/patch/delete, persistentvolumeclaims create/list/delete) + PER_DATASET_PVCS=1 into jobs-manager. Default off: byte-identical rendering. clusterScope: false + perDatasetPvcs fails the render (PVs are not namespace-grantable). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…#589) (#600) Bugbot on the staging mirror flagged 4 issues in the corporate-proxy / TLS- inspection probe (from #590/#582): - HIGH: the TLS probe cleared an already-captured issuer via `|| issuer=""`; openssl s_client often exits non-zero (SIGPIPE after x509) even on a good handshake, so MITM networks read as 'unknown'. Use `|| true` (empty capture is still caught below). - proxy password url-decode ran the whole string through printf '%b', mangling literal backslashes; escape them first so only percent-escapes expand (PS parity). - `openssl -help | grep -q` under pipefail dropped -proxy_user on authenticated proxies (grep -q closes the pipe, openssl gets SIGPIPE); capture then match. - PS Get-TlsInspectionState: [System.Uri] rejected schemeless proxy.corp:8080; prepend a scheme like the display path already does. Regenerated the R8 manifest for the install-k8s.ps1 change.
release-train: develop -> staging
…Rekor (#584) (#599) * feat(installer): offline Sigstore bundle — verify --offline, no live Rekor (#584) Child 3/4 of #578. Keyless cosign verification needs sigstore's Rekor at verify time, and our short-lived keyless cert is expired by install time — so a network that blocks or TLS-inspects sigstore fails verification even for a valid signature (the class behind the field Windows failure on a TLS-inspecting corporate network that #583's CA wiring does NOT cover, since Go ignores SSL_CERT_FILE on Windows/macOS). Skipping the tlog was proven not to verify the expired cert; the offline bundle carries the Rekor inclusion proof (SET) so the cert's validity at signing can be established without any live call. - Signing (release-helm-chart.yaml): cosign sign-blob now also emits manifest.sha256.bundle and publishes it as a release asset, alongside the .sig/.cert. - Verify (install.sh + install.ps1): prefer `verify-blob --bundle <bundle> --offline` (full check — signature + cert identity + tlog inclusion — with NO live Rekor). Falls through to the existing online .sig/.cert keyless path for releases cut before the bundle existed, or if the bundle doesn't verify — the SAME full check, just needing live Rekor, so it's a fallback, never a downgrade. - PS: extracted Invoke-CosignVerifyBlob so the fail-closed sentinel (nonzero $LASTEXITCODE seed) + stderr suppression (#576) back BOTH paths from one place. Tests: bootstrap prefers --bundle --offline when a bundle is published and does NOT hit the sig/cert path; falls back to sig/cert when no bundle (older release); fail-closed on a bad signature unchanged. Pester + bats green. SEQUENCING: needs a NEW release cut after merge so the bundle asset exists; until then the fallback (online) path runs. Existing releases keep working via the fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#584): cover the bundle-fails -> sig/cert fallback + both-fail fail-closed (reviewer) saadqbal: the bundle-present-but-verify-fails -> sig/cert fallback branch was never exercised (both bundle tests forced cosign exit 0; the fail-closed test had no bundle), so a regression there would stay green. Add: - bats: a fallback case (bundle published, cosign REJECTS the --bundle verify but ACCEPTS sig/cert -> the sig/cert path runs and the install proceeds) and a both-fail case (bundle present, every cosign verify fails -> fail closed, privileged step never runs). - Pester: behavioural (not source-text) equivalents driving Confirm-ManifestSignature — bundle-fails falls through to sig/cert (2 verify calls, no throw); both-fail throws the authenticity error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…r/offline (#585) (#601) * feat(installer): detect a blocked container registry + guide to mirror/offline (#585) Child 4/4 of #578, first slice: detection + clean guidance (the acceptance's core; the mirror/air-gap mechanisms + the offline bundle are follow-on slices). Some sites hard-block Docker Hub / GHCR outright — the images aren't reachable directly at all (distinct from a proxy or TLS-inspection). The preflight connectivity check already probes the registry hosts; now, when the blocked hosts are specifically the CONTAINER REGISTRIES, the installer surfaces the mirror / offline options in plain language instead of leaving only the generic egress hint — and it stays a clean preflight stop, never a raw pull failure (builds on #576/#577/#582). - preflight.sh: after the connectivity hints, if any failed critical is a registry host (registry-1.docker.io / auth.docker.io / ghcr.io), print mirror/offline guidance pointing at docs/INSTALL.md. - install-k8s.ps1: same, via a $regBlocked flag in Test-Preflight. - docs/INSTALL.md: new "Blocked container registry (mirror / air-gapped)" section — point the install at a reachable mirror via TRACEBLOC_VALUES_FILE overriding images.*.registry (+ dockerRegistry creds), or an air-gapped bundle for fully offline sites, with the honest limit stated. Tests: bats — the registry-block guidance fires when a registry is blocked and does NOT fire when only a non-registry host fails; Pester — Test-Preflight carries the detection + guidance + docs pointer. preflight.sh + install-k8s.ps1 are manifested; manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#585): enforce the new registry-guidance assertions (|| return 1) The bats-hygiene gate (#527) flagged the four bare `[[ … ]]` assertions in the two #585 preflight tests as advisory — under bats a non-final bare test can't fail its @test. Append `|| return 1` so they actually enforce. Fixes the "Unit tests" + "bats (bash unit, mocked)" CI failures on this branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ow) (#598) * test(e2e): full seal suite vs the dev backend — backend#1184 deferred fast-follow New e2e-full-seal.sh installs the working-tree chart on real k3d as the dedicated dev e2e-test-agent (real credentials, CLIENT_ENV=dev), waits for every release PVC to Bind and jobs-manager to hold a real backend session, then runs helm test UNFILTERED — egress-enforcement + backend-reachability + storage-assertions in one release, hook-presence-guarded so a regated check can't vanish silently. New helm-ci job full-seal-e2e runs it on push/dispatch only and skips green with a notice until the e2e-test-agent secrets (TB_E2E_CLIENT_ID / TB_E2E_CLIENT_PASSWORD) are provisioned. The egress positive control moves verbatim into e2e-common.sh (one copy, shared with e2e-seal-check.sh). SEAL-CHECK.md gains the CI-coverage map and drops three follow-ups that shipped since (#541 live probe, cli#393 verdict, cli#449 matrix). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(e2e): address Bugbot findings on the full-seal harness - credentials travel in a mode-0600 temp values file, never on argv (process-list exposure on a shared runner + helm --set comma/brace mangling); removed on every exit path — installer parity - the PVC wait does one guarded fetch per iteration, so a transient kubectl failure retries until the deadline instead of aborting under set -euo pipefail - full-seal-e2e job timeout 30m -> 45m: the script stacks a 300s PVC wait, two 300s rollouts and a 600s unfiltered helm test on create_cluster's 15m bound — GHA must not kill a slow-but-healthy run Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(e2e): local-path provisions RWO only — pin pvcAccessMode on the full-seal install (Bugbot) The chart's PVC default is ReadWriteMany; rancher.io/local-path never provisions it, so every claim sat Pending and both the Bound pre-wait and storage-assertions were guaranteed to fail once the job activates. The installer writes pvcAccessMode: ReadWriteOnce for this exact storage path — the harness now installs with the same value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…rap messages (#581) (#602) * refactor(installer): scrub remaining internal identifiers from bootstrap messages (#581) Follow-up from the #576/#579 review (saadqbal). The cosign verification-failure message was sanitized in #579; this sweeps the remaining lower-signal internal identifiers still in user-facing bootstrap messages — the `manifest.sha256` release-asset filename and the `RFC-0001 R8` internal spec code — to plain language, for consistency. No behaviour change: fail-closed paths, verification logic, and exit codes are untouched; only the wording of echo/throw/Warn strings changes. - install.sh: "manifest.sha256" -> "the installer's integrity checksums" / "signed checksum list"; ".sig/.cert not published" / "authenticate the manifest" -> "the installer's signature isn't published" / "confirm the download is authentic". - install.ps1: same wording, plus `(RFC-0001 R8)` removed from every throw (Resolve-InstallRef, Confirm-ManifestSignature, Confirm-ScriptIntegrity, the temp-dir guard). RFC-0001 stays in code COMMENTS (not user-facing) and the actual manifest.sha256 file paths/URLs are untouched. - Tests: updated the two pinned assertions (install-bootstrap.bats + install.Tests.ps1) from "no entry in manifest" to "isn't in the installer's signed checksum list". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#581): scrub the PS AllowUnverified warn too (Bugbot) The unverified-opt-in warn on the sig/cert path still said "manifest signature/cert not published" while the bash warn and the PS throw on the same path were already updated to "The installer's signature isn't published". My grep keyed on "manifest.sha256", so this "manifest signature/cert" variant slipped through. Aligned it with the rest. (The generic word "manifest" in the cosign-unavailable messages is descriptive supply-chain English, not the filename/spec-code #581 scopes, so it stays.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(installer): align cosign-unavailable messages to #576 vocabulary Address saadqbal's consistency nit on #602: the two cosign-unavailable strings still said "signed manifest" / "manifest signature" while the sibling sig-not-published messages just below now say "the installer's signature". Align both the WARN (AllowUnverified) and the fail-closed throw/echo in install.ps1 and install.sh, and update the matching install-bootstrap.bats assertion. No behaviour change: only wording of the echo/throw/Warn strings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…Registry (#604) * feat(#585): re-home all images onto a private mirror via global.imageRegistry Restricted-network / air-gapped installs can now point every image the chart pulls at a private registry mirror with a single knob. - Chart: global.imageRegistry (Bitnami convention) re-homes tracebloc/*, the spawned ingestor + training-job images (JOB_IMAGE_HOST), and the alpine/*, ubuntu/squid, busybox and curl helper images. Empty/unset renders byte-identical to before (docker.io / ghcr.io). An explicit images.ingestor.repository still wins; an explicit per-image registry is overridden by the global mirror. New tracebloc.mirrorPrefix helper for the registry-less utility images. - Installer one knob: TRACEBLOC_IMAGE_REGISTRY writes global.imageRegistry into the generated values; TRACEBLOC_REGISTRY_USERNAME / TRACEBLOC_REGISTRY_PASSWORD also mint the imagePullSecret (server derived as https://<host>). Bash (_image_mirror_yaml) and PowerShell (Get-ImageMirrorYaml) parity. - docs/INSTALL.md: rewrote the blocked-registry / air-gap section (the old per-image images.<name>.registry override was a no-op against this chart). - Tests: helm-unittest suite (15), bats (7), Pester (7). Contributes to #585. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(chart): bump to 1.9.14 (global.imageRegistry is chart content) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(installer): regenerate manifest.sha256 for #585 installer changes Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#585): always emit dockerRegistry.server (default Docker Hub) for creds-only (Bugbot) The chart schema requires dockerRegistry.server whenever create is true. When registry credentials were set without a mirror or an explicit server, the generated values omitted server and helm install failed with a schema error instead of authenticating to Docker Hub. Default the server to https://index.docker.io/v1/ in that case (bash + PowerShell), and always emit it. Adds bats + Pester coverage; regenerates manifest.sha256. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#585): don't let an empty global.imageRegistry drop the squid per-image registry (Bugbot) dig treats the chart-default empty global.imageRegistry as present, so the squid image never fell back to egressProxy.image.registry — an explicit per-image registry was silently dropped to docker.io on fresh installs and reset-then-reuse upgrades. Resolve the mirror with a "" fallback and | default through to the per-image registry (then docker.io); the global mirror still wins when set. Adds a helm-unittest regression case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
release-train: develop -> staging
…605) Environment selection is internal to tracebloc — a real install always resolves to prod (default) and a user never picks dev/stg. The commented `# CLIENT_ENV: prod` in the top-level env block advertised it as a knob, inviting misconfiguration during onboarding, so drop it. Environment selection stays fully functional via the (internal) values.schema.json contract and the ci/*-values.yaml files; only the user-facing template stops exposing it. Bump chart 1.9.14 -> 1.9.15: values.yaml is packaged chart content, so the edit only reaches installs via a new chart version (chart-version-guard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…emplate fix(chart): hide internal CLIENT_ENV from user-facing values.yaml (#605)
Contributor
Author
|
bugbot run |
There was a problem hiding this comment.
✅ 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 fcfe627. Configure here.
…tl/helm) (#607) (#608) * fix(installer): survive a proxy/AV-truncated tool download (k3d/kubectl/helm) (#607) On a filtered corporate network a proxy or antivirus can truncate or block a GitHub-release binary mid-transfer. install-k8s.ps1 had a single transport (Invoke-WebRequest) and a single after-the-fact signal (the checksum), so a blocked k3d download dead-ended at the cryptic "System tool checksum verification failed" -- the #578 field failure. The winget fallback was also dead (k3d has no winget manifest, so Rancher.k3d always returned "No package found"). PowerShell (install-k8s.ps1): - Get-VerifiedDownload tries three transports in turn -- Invoke-WebRequest, then curl.exe, then BITS. A different HTTP stack commonly succeeds where one is blocked/truncated. - Test-DownloadComplete validates each result BEFORE the checksum: present, at least a per-tool size floor, and the expected magic bytes (MZ for .exe, PK for the helm zip). A short/error-page/altered payload is caught as a TRANSFER failure (distinct from a checksum mismatch) and the next transport is tried. - kubectl / k3d / helm all route through it. - Removed the dead k3d winget branch (verified: k3d has no winget manifest). - Every-transport-failure throws one specific, actionable message (allowlist the hosts / exclude the tools dir from AV) instead of the cryptic checksum error. Bash parity (lib/common.sh, lib/setup-linux.sh): - _assert_download_size gates kubectl/k3d/helm downloads on a size floor before the checksum, so a truncated/blocked transfer reports the real reason. The Linux path already uses curl; TB_MIN_DOWNLOAD_BYTES lets the bats fetch mocks (tiny fixtures) relax the floor. Tests: Pester (Test-DownloadComplete, 7) + winget-removal guard; bats (_assert_download_size, 4); fetch mocks updated via the env hook. Manifest regenerated. Contributes to #578. Closes #607. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#607): exempt the PowerShell curl.exe fallback from the bare-curl style guard curl_secure() is a bash helper and cannot exist in PowerShell, so the resilient download's curl.exe fallback is a deliberate, flag-matched fetch. Mark both lines (the transport + the presence check) with '# style-guard: allow'. Regenerates the manifest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#607): TLS 1.2 floor on the curl.exe fallback + validation can't abort the fallback loop (Bugbot) - curl.exe fallback now passes --tlsv1.2, matching curl_secure's TLS floor, so it cannot negotiate below TLS 1.2 on the proxy networks this targets. - Get-VerifiedDownload wraps the Test-DownloadComplete call in try/catch: a post-download I/O error (e.g. AV locking/quarantining the just-written file) now records a problem and tries the next transport instead of aborting the whole download — the recovery path is the point of this change. - Pester source-guards for both; manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#607): clean up the tmp tree when the size guard fails (Bugbot) _assert_download_size calls error (which exits); it now removes the caller's mktemp -d tree first (passed as $4) so a truncated/blocked transfer no longer leaves a partial tool payload under /tmp — matching the checksum-mismatch branches in _fetch_kubectl / _fetch_k3d_release / _fetch_helm_release. Adds bats coverage for both the cleanup-on-failure and leave-intact-on-success paths; manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
bugbot run |
release-train: develop -> staging
Pin every actions/* ref to the full 40-char commit SHA it currently resolves to, with a trailing exact-version comment (D10, RFC-BACKEND-1405). Behaviour-preserving: no version changes, only removal of silent tag mutation. tracebloc/* refs stay on @main by design; third-party refs were pinned under backend#1490. Part of tracebloc/backend#1491. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
release-train: develop -> staging
Contributor
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
1 issue from previous review remains unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit da014f9. Configure here.
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.

Automated promotion by the release train (RFC-0008 D14). Head is the train-managed
release-train/to-mainbranch (a mirror ofstaging), so it never collides with a human PR. Merged only when the fr-gate is green.Note
High Risk
Large blast radius: default-off chart knobs still change image resolution and jobs-manager spawn env when mirrors are enabled, and installer/bootstrap changes affect privileged customer install paths plus supply-chain verification behavior.
Overview
This promotion moves client chart 1.9.12 → 1.9.15 and folds in a wide set of staging changes across the Helm chart, installers, CI, and docs.
Helm / runtime: Adds
global.imageRegistryso one mirror host re-homes tracebloc images, spawned ingestor/training jobs, and utility images (tracebloc.mirrorPrefix, updatedtracebloc.imageregistry args,JOB_IMAGE_HOST/ ingestor repo logic). Introduces opt-inperDatasetPvcs(PER_DATASET_PVCSon jobs-manager, cluster-scoped PV/PVC RBAC, render failure whenclusterScope: false). Fixes requests-proxy to use IfNotPresent when a digest is pinned. Helm unit tests cover mirror precedence and the new flags.Installers (bash / PowerShell): Wire
TRACEBLOC_IMAGE_REGISTRY(and optional pull-secret envs) into generated values; add corporate-CA trust for host tools, offline cosign--bundleverification (#584), curated PII-free logs, top-level fatal/interrupt handling, resilient multi-transport downloads with size checks, richer preflight (network profile, blocked-registry hints), and GPU plugin deploy/verify gating so failures stay CPU-mode non-fatal. Bash gainswire_ca_trust,_assert_download_size, and a pipefail-safe_extract_yaml_value.CI / release: GitHub Actions pins third-party actions to commit SHAs; helm-ci adds
full-seal-e2e(push/dispatch only, skips until e2e secrets), workflow_dispatch, bounded Docker Hub pulls in distro matrices, and kubeconform download timeouts. Release workflow publishes and attachesmanifest.sha256.bundlefor offline Sigstore verify.Docs / hygiene:
docs/INSTALL.mddocuments mirror/air-gap installs;docs/SEAL-CHECK.mddescribes CI seal coverage; Bugbot notes on secretless reusable workflows; style guard for lowercase tracebloc in user-facing copy.Reviewed by Cursor Bugbot for commit da014f9. Bugbot is set up for automated code reviews on this repo. Configure here.