Skip to content

feat(chart): perIngestionTables — RFC-0003 D16 enablement knob (backend#1205) - #472

Merged
LukasWodka merged 2 commits into
developfrom
feat/1205-enablement-knob
Jul 29, 2026
Merged

feat(chart): perIngestionTables — RFC-0003 D16 enablement knob (backend#1205)#472
LukasWodka merged 2 commits into
developfrom
feat/1205-enablement-knob

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

The enablement knob for RFC-0003 D16 per-ingestion tables — the one wire the merged D-series (tracebloc/data-ingestors#408 → tracebloc/backend#1300 → tracebloc/tracebloc-engine#543 → cli#424 + tracebloc/client-runtime#222) was still missing: nothing ever set PER_INGESTION_TABLES on an ingestion Job.

  • values.perIngestionTables (default false, schema-typed, heavily documented in values.yaml): renders PER_INGESTION_TABLES="1" onto the jobs-manager api container; the jobs-manager forwards it into every ingestion Job it spawns (companion: tracebloc/client-runtime#225).
  • Conditional block — default installs render byte-identically (helm-unittest pins both sides: absent by default, "1" when set; 298/298 green).

Enablement sequence (dev first)

  1. Merge this + the client-runtime companion; let dev images/deploys roll (dev pulls floating tags).
  2. Flip perIngestionTables: true on the dev fleet values, helm upgrade.
  3. Validate end-to-end with the ready-made tabular sample dataset: tb data ingest → confirm the summary carries the handle and the backend row has physical_table set → run a tabular training experiment (e2e harness) → confirm data_info.tables reaches the pod and training reads ds_<hex>tb data delete → confirm table + salt + journal rows are gone and the dataset tombstones.
  4. Staging → prod per fleet, same checklist. File-bearing categories stay refused under the flag until client-runtime#203 phase 2.

Epic: tracebloc/backend#1151 · Design: tracebloc/backend#1204

🤖 Generated with Claude Code


Note

Low Risk
Opt-in feature flag with default false and unittest guards; no behavior change until operators flip the value per environment.

Overview
Adds an opt-in Helm valueperIngestionTables (default false) to turn on RFC-0003 D16 per-ingestion physical tables. When set to true, the jobs-manager api container gets PER_INGESTION_TABLES=1, which jobs-manager is expected to forward into spawned ingestion Jobs so ingestors write immutable ds_<hex> tables instead of legacy shared label-named tables.

The env block is conditional so default installs stay unchanged. values.yaml, values.schema.json, and helm-unittest cases lock in absent-by-default vs PER_INGESTION_TABLES=1 when enabled.

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

…ackend#1204/#1205)
values.perIngestionTables (default false, schema-typed) renders
PER_INGESTION_TABLES=1 onto the jobs-manager, which forwards it into
every ingestion Job it spawns (client-runtime companion PR). Flip per
environment, dev first, only once that environment's backend + engine
images + jobs-manager carry the merged D-series. Default installs
render byte-identically (conditional block; unit tests pin both sides).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodkaLukasWodka self-assigned this Jul 29, 2026
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

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

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

Open PRs currently in Code review (oldest first):

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

@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

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

Reviewed by Cursor Bugbot for commit cdeb2e0. Configure here.

@saqlainsyed007

Copy link
Copy Markdown
Contributor

Review: perIngestionTables enablement knob

Overview

Small, well-scoped chart change adding an opt-in value perIngestionTables (default false). When enabled, it renders PER_INGESTION_TABLES="1" onto the jobs-manager api container, which forwards it into spawned ingestion Jobs (RFC-0003 D16). The env block is conditional, so default installs render byte-identically. Four files: template, unittest, schema, values.yaml docs.

Correctness ✅

  • The conditional {{- if .Values.perIngestionTables }} with trim markers is placed correctly between sibling env entries at 8-space indent — no stray blank lines, clean YAML output.
  • --reuse-values safe: a release predating this key resolves .Values.perIngestionTables to nil → falsy → env omitted. No nil-guard needed for a top-level scalar (unlike the (default dict ...) guards used for the nested images.ingestor reads nearby), so this is consistent, not a gap.
  • Default-off is enforced in three places (values.yaml default, schema type, the "off" test) — the right belt-and-suspenders for a cross-stack flag.
  • The notContains/contains assertions match the full {name, value} map element, so they can't be fooled by an unrelated env var — correct negative and positive coverage.

Conventions ✅

  • Env entry, quoting (value: "1"), and inline comment style all match the existing block.
  • Schema addition is a correct top-level sibling of ingestionAuthz. Since the schema sets no additionalProperties: false, it's documentation rather than a hard gate — consistent with the rest of the file.
  • Docs are thorough and honest about the ordering constraint (backend/engine/jobs-manager must carry the D-series first).

Suggestions / nits

  • values.yaml placement (minor readability).perIngestionTables: false is inserted directly under the # ==== Ingestion endpoint authorization (client-runtime#21) ==== banner, between that section's commented example and the real ingestionAuthz: key. It now visually reads as if it belongs to the authz section, which it doesn't — it's a separate D16 feature. Consider moving it above the authz banner, or giving it its own banner, so the authz doc block stays adjacent to the ingestionAuthz: key it documents.
  • String contract is cross-repo (can't verify here). The exact "1" value is the wire contract consumed by jobs-manager / client-runtime#225. Worth confirming the companion reads == "1" (not a generic truthy parse) before the coordinated flip. Already sequenced in your merge plan — checklist item, not a code issue.
  • Cosmetic: the test addition introduces a double blank line before the first new - it:. Harmless — drop one line if repushing anyway.

Risk

Low, and accurately self-described. No secrets, no new surface, no behavior change unless an operator explicitly flips the flag on a stack that already supports it. The byte-identical-default claim is backed by the passing unittest guard.

Verdict: Approve. Only the values.yaml placement is worth addressing before merge, and it's non-blocking.

🤖 Generated with Claude Code

…the authz section (review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

Both nits taken (0362930): perIngestionTables now sits above the authz section under its own ==== banner — the authz doc block is adjacent to its key again — and the double blank line in the test file is gone. On the string contract: confirmed on the companion (tracebloc/client-runtime#225) — jobs-manager forwards the raw value only when non-empty, and the ingestor's Config() parses truthiness (1/true/yes/on), so the contract is presence + truthy, with this chart emitting exactly "1" when enabled and omitting the var entirely when disabled (the conditional block — never a zeroed value). 298/298 helm tests green. Thanks for the approve!

bugbot run

@LukasWodka
LukasWodka merged commit f71faae into developJul 29, 2026
18 checks passed
LukasWodka added a commit that referenced this pull request Jul 29, 2026
Only conflict: manifest.sha256 -- regenerated via gen-manifest.sh.
Merged suite: 227 passed / 0 failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LukasWodka added a commit that referenced this pull request Jul 30, 2026
* chore(chart): close values-schema gaps + drop dead override/code/docs (#963) (#457)
* chore(chart): close values-schema gaps + drop dead override/code/docs (#963)
Contract fixes for the client Helm chart (re-verified against develop at
chart v1.9.6; the #963 audit was taken at v1.8.4):
- values.schema.json: add the six live-but-unvalidated keys so bad values
fail `helm lint` instead of silently passing —
egressReachabilityCheck.enabled, ingestionAuthz.{allowed,serviceAccountName},
networkPolicy.training.enforcementProbeTimeoutSeconds, podTokenSigningSecret,
podTokenTtlSeconds. Types/defaults/constraints taken from values.yaml and
the templates that consume them. helm lint passes.
- ingestor subchart: remove the dead `image.repository` key — no template
ever rendered it (jobs-manager spawns from the parent chart's
images.ingestor.repository). Kept image.digest (live). README's air-gapped
override rows now point at the authoritative parent-chart path.
- README: drop the hardcoded chart version (said v1.3.5 while Chart.yaml is
1.9.6) and point to Chart.yaml / the releases page, so it can't drift again.
- Delete the unwired check_docker_arch_mac function + its bats test (no call
sites) and the orphaned docs/eks.md (referenced nowhere).
Part of tracebloc/backend#963.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(installer): regenerate manifest after common.sh trim + develop merge
The #963 chart-contract cleanup dropped 48 dead lines from
scripts/lib/common.sh, changing its sha; the installer manifest wasn't
regenerated, so the Static analysis gate (gen-manifest.sh --check) failed.
Merging develop also refreshed preflight.sh/install-k8s.ps1 hashes.
Regenerate scripts/manifest.sha256 to match the working tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Syed Saqlain <syedsaqlain@MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(release): package charts as the tag version + pre-releases skip gh-pages (#467)
* fix(release): package charts AS the tag version + pre-releases skip gh-pages
Incident 2026-07-29: the v1.9.7-rc.1 pre-release packaged the client chart
from Chart.yaml's plain 1.9.7 and pushed it into the public helm index as
a STABLE version -- customers running helm upgrade would have received
staging content (removed from the index by hand, tgz deleted). Two layers
now prevent it: (1) helm package --version/--app-version from the release
tag, so rc charts carry the -rc.N suffix helm's pre-release rules key on;
(2) pre-releases never run the gh-pages index steps at all -- FR consumes
the release assets (stamped installer / chart tgz), the index is a
customer surface reserved for finals.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: gh-pages gates key on verify's tag-derived prerelease, not the frozen event (Bugbot)
github.event.release.prerelease is an event-time snapshot: after verify
demotes a mis-marked release, it still reads false, so the demoted rc
would have entered the public index anyway. verify now outputs effective
prerelease-ness derived from the tag shape (the same strict rule the
demotion uses) and all three gh-pages steps gate on that output.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: post-publish index-invariant job (manual leak catch -> CI)
After every release run: the public index must contain only stable-shaped
versions, and a prerelease run must not have indexed its own version.
Fails loudly; would have caught the 1.9.7 leak within a minute of it
happening instead of during manual FR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(install): rootless Docker core + Tier-1 routing (opt-in) (#1219) (#452)
* feat(install): rootless Docker core + Tier-1 routing (opt-in)
Add install_rootless_docker() and a Tier-1 early-branch in install_linux
so a modern-kernel host with no runtime and no root can install entirely
in user space (RFC 0001 Tier 1 — the RFC's primary path). Gated behind
opt-in TB_TIER1_ROOTLESS=1; with the flag unset a Tier-1 host falls
through to the legacy privileged flow unchanged (validated default).
- install_rootless_docker: uidmap-helper precondition (defers to
prepare-host #1178 when absent — never self-sudo), no-sudo install via
dockerd-rootless-setuptool.sh or get.docker.com/rootless, user-scoped
systemctl --user + loginctl enable-linger, DOCKER_HOST export with
XDG_RUNTIME_DIR fallback, single docker-info verify (no retry loop).
- Tier-1 branch mirrors the Tier-0 early-return. Tools still install via
sudo here (_set_tools_target keys no-sudo off Tier 0 only) — tightening
that for rootless Tier 1 is deferred to slice 3 (#1221).
- 6 bats cases; scripts/manifest.sha256 regenerated (R8).
Closestracebloc/backend#1219
Part of tracebloc/backend#1177 · Epic tracebloc/backend#1168
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address Bugbot review on rootless Docker core (#452)
- Prepend ~/bin to PATH after the rootless install so this run's docker
info verify + later k3d/docker calls resolve the CLI the
get.docker.com/rootless fallback installs there (High).
- Bound the rootless `docker info` verify with a new shared _bounded
helper (timeout/gtimeout, mirrors probe.sh) so a wedged user daemon
can't hang a headless install (Medium).
- Guard the user-systemd bring-up under set -e: `systemctl --user … ||
true` (the bounded verify is the real gate) and `loginctl
enable-linger … || warn` (optional; fails on polkit-locked hosts even
when the daemon is up) (Medium).
Adds 2 bats cases (~/bin on PATH; systemd/linger failure falls through
to the verify). Manifest regenerated (R8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: correct the uidmap remedy message (Bugbot #452)
The missing-uidmap error claimed prepare-host would install the uidmap
helpers, but run_prepare_host only sets up privileged Docker + the docker
group — it never installs uidmap. Point at the two honest remedies
instead: install the `uidmap` package directly (rootless then works), or
run prepare-host to set up Docker so the researcher installs at Tier 0
(no rootless needed). #1220 folds this into the shared subuid/subgid gate
and teaches prepare-host to install uidmap for real.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(install): TODO(#1221) — rootless daemon needs user-scoped proxy config
Bugbot on #452 flagged that install_rootless_docker never configures a
corporate proxy for the user-scoped dockerd (the #244 _configure_docker_proxy
is sudo/system-scoped and the Tier-1 early-return never reaches it), so k3d
pulls of rancher/k3s time out on proxy-only hosts. Deferred to #1221 (the
k3d-on-rootless-socket slice that owns the pulls); leaving a tracked TODO so
the follow-up adds the user-scoped drop-in.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address Asad review nits on #452 — chmod no-op + misleading comment
- Drop the chmod +x on the rootless installer script: it runs via
`sh "$rootless_script"`, which ignores the exec bit.
- Reword the Tier-1 _install_userspace_tools comment: tools still
sudo-install on Tier 1 (only _persist_tools_on_path is no-sudo);
the comment previously implied otherwise.
The underlying _set_tools_target sudo-crash on no-sudo hosts and the
post-install DOCKER_HOST shell persistence are tracked to #1221.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: resolve user via id -un in install_rootless_docker (Saqlain review, #452)
$USER can be empty in headless / su / cron contexts (a Tier-1 target),
which would break `loginctl enable-linger` and the success line. Resolve
the user once via `id -un` (fallback $USER) and use it for the linger
call, its hint, and the success message. Matches the id-based robustness
DOCKER_HOST already uses. Happy-path bats now mocks `id -un` cleanly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: add code-quality caller workflow (advisory) (#463)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Add pre-commit hooks (Layer 0, lint-only) (#465)
* Add pre-commit config (Layer 0, lint-only)
Lint-only on purpose: scripts/manifest.sha256 must keep matching the bytes
under scripts/, so no hook may rewrite files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Document pre-commit setup in README
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* docs: add Bugbot resolve-and-reply team norm to .cursor/BUGBOT.md (#464)
Part of tracebloc/backend#1308
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* ci: cover scripts/resolve-ingestor-digest.sh in CI shellcheck (#466)
* ci: lint scripts/resolve-ingestor-digest.sh in CI shellcheck (was never linted)
Both CI shellcheck invocations enumerate files explicitly and both omitted
this script. Verified clean against shellcheck --severity=error --shell=bash
0.11.0 before adding. The pre-commit hook from #465 already covers it
locally; this closes the same gap on the CI side.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: lint scripts/resolve-ingestor-digest.sh in CI shellcheck (was never linted)
Both CI shellcheck invocations enumerate files explicitly and both omitted
this script. Verified clean against shellcheck --severity=error --shell=bash
0.11.0 before adding. The pre-commit hook from #465 already covers it
locally; this closes the same gap on the CI side.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): honest cosign bootstrap download + translated DISM failures (#468) (#469)
The v1.9.7-rc.1 FR killed a healthy install: PS 5.1's progress overlay
throttled the 17 MB pinned-cosign fetch to ~4.5 min of dead silence and
the window read as frozen.
- silence the PS 5.1 progress overlay in Get-WithRetry/Get-Optional
(function-local, auto-reverts) - the classic 10-50x IWR speedup
- run the cosign fetch in a background job with a dim liveness tick
(Wait-JobWithTicks / Get-OptionalWithTicks; cwd pinned per #409,
TLS 1.2 re-applied in the fresh process), expectation lines before,
elapsed + checksum-verified confirmation after
- ASCII-only string literals in both installers: the release asset is
served without a charset so PS 5.1's irm decodes UTF-8 source as
Latin-1 before iex, and BOM-less -File reads are ANSI - literal
em-dashes/ellipses reached customers as mojibake. Locked in by a
tokenizer-based Pester test (which also caught the -Help here-string).
- Enable-OneVirtFeature: translate DISM's raw COMException (feature
package absent on Server SKUs vs enable failure) and stop demanding
a reboot for a feature that never enabled (old code sent Server
users into a reboot->re-run->same-error loop)
Pester: 212 passed / 0 failed locally (pwsh 7.5, Pester 5.7.1).
PSScriptAnalyzer: 0 errors. manifest.sha256 regenerated.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): trust the corporate MITM CA in the k3d nodes (#424) (#453)
* fix(installer): trust the corporate MITM CA in the k3d nodes (#424)
Proxy REACHABILITY reaches the nodes, but on a TLS-inspecting (break-and-
inspect) network the nodes still don't TRUST the corporate CA, so every in-node
containerd pull (rancher/k3s, ghcr.io, tracebloc images) fails x509 — then
masked (helm runs without --wait) into a root-cause-free "an image couldn't be
pulled." Enterprise/hospital archetype, all three OSes.
- Inject the CA at create time: when TRACEBLOC_CA_BUNDLE (or CURL_CA_BUNDLE) is
set, mount the bundle into every k3d node and write a registries.yaml pointing
containerd at it per-registry (docker.io, registry-1.docker.io, ghcr.io), via
the same --config/create path that already carries proxy env. Parity across
scripts/lib/cluster.sh (Linux/macOS) and install-k8s.ps1 (Windows). A CA var
set but unreadable fails loudly instead of silently skipping.
- Name the env var where the user hits the wall: the TLS-interception preflight
hint (both OSes), docs/INSTALL.md, and the PS -Help env-var list.
- CA-aware diagnosis: detect x509 / "certificate signed by unknown authority"
pull events and report a dedicated image_pull_ca state — "the cluster does not
trust your network's TLS-inspection CA" + the exact remedy — instead of the
generic pull error. Mirrored in summary.sh and Print-Summary.
- New check-drift.sh parity check (_drift_ca_trust) so neither installer can drop
the CA wiring for the other's OS.
Tests: +8 cluster.bats, +3 summary.bats, +2 check-drift.bats, +8 Pester.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): CA-trust hardening — no fail-open, bounded events, verify CA readable (Bugbot #424)
Three Bugbot findings on #424:
- _write_k3d_registries_config failed open: on mktemp failure it returned success
with no path, so create still mounted the CA and logged "nodes trust it" but
dropped --registry-config → containerd never got ca_file, x509 pulls still fail
while the operator thinks it's fixed. Now returns non-zero; the caller hard-errors
(CA was supplied, so we refuse to proceed without wiring it in).
- PS Get-NotReadyState `kubectl get events` had no --request-timeout (the bash path
does) — on a wedged/proxy-misrouted API, classification could hang. Added
--request-timeout=5s to match _diagnose_not_ready.
- PS Resolve-CaBundle only checked existence (Test-Path), not readability, so an
unreadable CA passed on Windows but bash (-r) hard-fails. Added an OpenRead probe
so both fail the same way, up front.
Tests: cluster.bats +mktemp-failure + unwritable-registries-hard-error;
install-k8s.Tests.ps1 +unreadable-CA (Unix) + events --request-timeout assertion.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): errexit-safe CA-resolve capture + drift check ignores comments (Bugbot #424 r2)
Two round-2 Bugbot findings:
- Under `set -euo pipefail`, `ca_bundle="$(_resolve_ca_bundle)"; ca_rc=$?` exited on
the rc-2 (unreadable/missing CA) BEFORE ca_rc/error ran — operators got a bare
exit instead of the "can't be read" guidance. Capture with `|| ca_rc=$?` so
errexit doesn't fire and the guidance prints.
- _drift_ca_trust whole-file grep matched tokens in comments (e.g. --registry-config
appears in a comment above the real line), so deleting the functional wiring could
still pass. Strip comment lines first (matches the execute-gate / preflight-host
checks), no grep -q under pipefail.
Tests: cluster.bats +errexit-safe-capture; check-drift.bats +comment-only-token drift.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): TLS-preflight hint names the right var per layer/OS (Bugbot #424 r3)
The hint claimed TRACEBLOC_CA_BUNDLE makes "the host AND the k3d nodes" trust the
CA, but the host connectivity checks use curl_secure / Invoke-WebRequest, which
read CURL_CA_BUNDLE / the system trust store — not TRACEBLOC_CA_BUNDLE (that var
only reaches the nodes via _resolve_ca_bundle). Following the hint literally left
host preflight TLS failures unchanged. Corrected, no behaviour change:
- bash: CURL_CA_BUNDLE fixes these host checks AND the nodes; TRACEBLOC_CA_BUNDLE
is nodes-only; or add the CA to the system trust store.
- Windows: import the CA into the cert store for the host checks (Invoke-WebRequest
uses the store, not an env var); TRACEBLOC_CA_BUNDLE/CURL_CA_BUNDLE cover the nodes.
(Reworded to avoid a bare lowercase `curl` that the curl_secure style guard flags.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): apply CA on cluster REUSE path — warn + recreate guidance (Bugbot #424 r4)
The image_pull_ca remedy said "set the CA and re-run", but CA trust is baked in
only at fresh create; a re-run reuses the existing cluster and never mounts the CA
or passes --registry-config, so the x509 pulls persisted. Mirror the existing
proxy handling (baked-at-create → warn on reuse):
- bash _check_existing_cluster_ca (called from _handle_existing_cluster): warns when
a CA bundle is set but the reused server container lacks the CA mount.
- ps1 New-K3dCluster reuse block: same check via docker inspect mounts.
- both image_pull_ca remedies now say to `k3d cluster delete <name>` first, then
re-run with the CA (CA, like proxy, can't be added to a running cluster).
Tests: cluster.bats +3 (no-CA no-op / CA-but-missing-mount warns / mount-present silent).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): add auth.docker.io to the CA registries config (Bugbot #424 r5)
The registries.yaml ca_file entries covered docker.io / registry-1.docker.io /
ghcr.io, but Docker Hub pulls also TLS-handshake with auth.docker.io for bearer
tokens — so on a break-and-inspect network containerd still rejected the
intercepted cert there even with the CA mounted. #416 already probes auth.docker.io
at preflight; the CA registries list now matches. Added to TB_CA_REGISTRIES and
$TbCaRegistries; registries.yaml test counts 3 -> 4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#424): _resolve_ca_bundle rejects a directory, not just unreadable paths
A directory of PEMs is readable (-r) but would bind-mount over the single
node ca_file path and containerd can't read it — the silent 'looks applied
but still x509' case. Require a regular file (-f), mirroring the PS
Resolve-CaBundle -PathType Leaf check. Adds a directory-reject bats case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#424): exact whole-line match for CA mount detection (Bugbot)
_check_existing_cluster_ca used a substring test on docker mount
destinations, so a longer path embedding /etc/ssl/certs/tracebloc-mitm-ca.crt
(e.g. …crt.bak) would be treated as the CA mount and skip the recreate
warning while containerd still x509-fails. Switch to grep -qxF (exact
whole-line), matching the PS anchored regex. Adds a substring-embed test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#424): scope x509 classification to the pull-failure event (Asad)
_diagnose_not_ready / Get-NotReadyState flagged image_pull_ca on ANY x509
event in the namespace, so a stale/unrelated x509 event (e.g. a FailedMount)
could misdirect the user into a needless delete+recreate. Filter events to
the image-pull failure lines (failed to pull / ErrImagePull) before testing
x509, in both bash and PS. Adds an unrelated-x509 test to each side.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(chart): perIngestionTables — RFC-0003 D16 enablement knob (backend#1205) (#472)
* feat(chart): perIngestionTables — the RFC-0003 D16 enablement knob (backend#1204/#1205)
values.perIngestionTables (default false, schema-typed) renders
PER_INGESTION_TABLES=1 onto the jobs-manager, which forwards it into
every ingestion Job it spawns (client-runtime companion PR). Flip per
environment, dev first, only once that environment's backend + engine
images + jobs-manager carry the merged D-series. Default installs
render byte-identically (conditional block; unit tests pin both sides).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(values): own banner for perIngestionTables — it is not part of the authz section (review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* chore: clear house-rules findings (#470)
Fix every finding the shared org checker (tracebloc/.github
scripts/house-rules.sh) reports at develop HEAD: missing curl
timeouts/TLS floors, plus (cli) a missing pipefail. Waivers only where
the finding is a documented false positive. Part of tracebloc/backend#1303.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(install): Tier-1 subuid/subgid gate + prepare-host remediation (#1220) (#458)
* feat(install): Tier-1 subuid/subgid gate + prepare-host remediation
RFC 0001 #1220. Detect the one privileged residue a modern rootless host may
still need — a subordinate UID/GID range + the setuid uidmap helpers — and
either proceed (present), hand off to prepare-host (unprivileged), or perform
one announced touch (sudo available). Never blanket sudo, never an opaque
mid-install crash inside dockerd-rootless-setuptool.sh.
- probe.sh: _probe_subid_ranges (PROBE_SUBID) + _probe_uidmap_helpers
(PROBE_UIDMAP), set in run_host_probes (Linux only), plus audit rows on
the Tier-1 path.
- common.sh: shared pure parsers _subid_has_entry + _next_subid_start, used
by both the probe and the remediation (no duplication).
- setup-linux.sh: _ensure_subid_ranges gate (present / hand-off / one
announced sudo touch) called before install_rootless_docker;
_provision_subid_ranges (idempotent, non-overlapping block, usermod
--add-subuids with file-append fallback, uidmap install) shared by the
installer and run_prepare_host. Folds in slice-1's minimal uidmap check.
- Tests: probe.bats + setup-linux.bats. Manifest regenerated (R8).
Closestracebloc/backend#1220
Part of tracebloc/backend#1177 · Epic tracebloc/backend#1168
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): address #458 review — subid gate/probe/hand-off robustness
Bugbot + @saadqbal + a self code-review on client#458, all in slice-2's code:
- id -un everywhere (gate, _provision default, probe): $USER diverges from the
rootless daemon's user under su/cron, which wedged detection/provisioning (#1).
- Re-verify the uidmap helpers are usable (present AND setuid|cap_setuid) after
install, and return non-zero + warn (NOT error/exit) so run_prepare_host stays
best-effort while the installer sudo-path hard-fails via `|| error` (#2 + self-review).
- _idmap_helper_ok (common.sh): accept the setuid bit OR a cap_setuid filecap, so
Arch's `shadow`/pacman path isn't false-rejected (#3).
- Hand-off + run_prepare_host fallback compute a non-overlapping start via
_next_subid_start (honoring TB_SUBUID_FILE/TB_SUBGID_FILE), not hardcoded 100000
(#4 + self-review path-override).
- Hand-off command names the researcher (TB_PREPARE_USER=) — bare prepare-host
provisions nothing, so it would have looped back to the same hand-off (#5).
- Capture `usermod --help` before grepping — pipefail-safe (#6).
bats: id -un mocks, filecaps accept/reject, gate hand-off (names user + computed
start), _provision re-verify best-effort, run_prepare_host best-effort. R8 regen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(install): stub the subid gate in the Tier-1 rootless routing test
install_linux's Tier-1 branch now calls _ensure_subid_ranges (slice 2)
before install_rootless_docker; the routing test left it un-stubbed, so the
real gate hit the no-sudo hand-off and error()'d → install_linux returned
non-zero. Stub _ensure_subid_ranges (its own behavior is covered by the
dedicated gate tests) and assert it runs before daemon setup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(install): scope set -o pipefail to a subshell (bats harness footgun)
Setting `set -o pipefail` in the @test body can leak into bats' own
post-test pipelines and fail the whole run with exit 1 even when every
test reports ok (no 'not ok'). Confine it to a subshell around the call
so the pipefail-safety assertion still holds without touching the harness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): style guard — no bare curl in the prepare-host hint
The hand-off piped 'curl … | TB_PREPARE_USER=… bash', which breaks
check-style.sh's exemption for the canonical 'curl … | bash' one-liner
(the env var sits between the pipe and bash). Split into an 'export
TB_PREPARE_USER=…' line + the canonical piped one-liner — still names the
researcher, and passes the guard. Verified with scripts/check-style.sh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): fix the #458 red bats + 2 Bugbot bugs (newgidmap cap, write-failure)
Root cause of the "540 ok but exit 1" bats red: the probe.bats uidmap tests set
PATH="$bin" in the test body to hide system helpers, which also hides `rm` — so
bats-core 1.10+ can't run its own per-test cleanup ("rm: command not found") and
fails the whole run even though every test passes. Scope the hermetic PATH to a
subshell so it can't leak into bats' machinery. (Why develop was green + this was
so hard to see: these tests are new in slice 2, and the symptom is a clean pass
list with a non-zero exit.)
Two real Bugbot findings in the slice's own code:
- _idmap_helper_ok checked cap_setuid for BOTH helpers; newgidmap carries
cap_setgid (Arch filecaps) -> false-rejected. Map name->cap; fix the test mock
that masked it + add a wrong-cap regression test.
- _provision_subid_ranges printed success/returned 0 even when the usermod/tee
write failed (callers run it with set -e off) -> installer proceeds with no
range. Guard every write; warn + return 1 on failure. + a test.
Verified: probe.bats + setup-linux.bats EXIT 0 (0 not-ok, 0 rm-not-found) in a
faithful ubuntu 24.04 + bats 1.10 + non-root container. Rebased onto develop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): 2 more Bugbot findings on #458 (apt hang, false zero-root message)
- _install_uidmap_pkg ran a bare `sudo apt-get install -y uidmap` under the
spinner — no needrestart/DEBIAN_FRONTEND env, no DPkg::Lock::Timeout, no
apt_wait_for_lock — so a headless Tier-1 install can hang on Ubuntu needrestart
or an apt-daily lock (#210 class). Reuse the repo's hardened PM_INSTALL
(populate via setup_pm, which Tier 1 skips) + apt_wait_for_lock.
- install_rootless_docker always printed "no administrator rights were used",
even after _ensure_subid_ranges performed an announced sudo touch on the
root/sudo_nopw path. The gate now sets TB_ROOTLESS_ADMIN_TOUCH and the summary
is honest on both the zero-root and one-admin-touch paths.
Tests: hardened-install assertion (NEEDRESTART_MODE + DPkg::Lock::Timeout) + a
success-message honesty test. Verified EXIT 0 (0 not-ok, 0 rm-errors) in the
faithful ubuntu 24.04 + bats 1.10 + non-root container.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): refresh the package index in _install_uidmap_pkg (Bugbot #458)
Completing the prior apt-hardening: _install_uidmap_pkg populated PM_INSTALL and
waited for the dpkg lock but never ran PM_UPDATE. On the Tier-1 path this is the
first package op, so an empty/stale index can't locate uidmap/shadow and the
install hard-stops. Run $PM_UPDATE (best-effort) first, matching the repo's other
install paths (setup-linux.sh:335/543). Test asserts the index refresh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(installer): silence the PS 5.1 progress throttle in install-k8s.ps1 downloads (#468 follow-up) (#471)
Same class as the bootstrap fix in #469: PS 5.1's progress overlay
throttles Invoke-WebRequest 10-50x and reads like a hang. One
function-local $ProgressPreference in Invoke-WithRetry covers every
fetch scriptblock it drives (dynamic scoping) - winget msixbundle,
Docker Desktop fallback, kubectl, k3d, helm, GPU plugin yaml, and the
version resolvers.
Honest-progress expectation lines (sizes measured today via HEAD):
Docker Desktop ~600 MB, winget ~200 MB, kubectl ~60 MB, k3d ~25 MB,
helm ~20 MB - all cold-path only, silent on warm re-runs.
Pester: 205 passed / 0 failed locally. PSSA: 0 errors.
manifest.sha256 regenerated.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: shujaat hasan <shujaat@tracebloc.io>
* fix(installer): trust the corporate CA in the Docker daemon for k3d runtime image pulls (#474) (#475)
* fix(#474): detect host Docker daemon x509 at cluster-create + document daemon CA trust
k3d pulls its own runtime images (rancher/k3s, k3d-tools, k3d-proxy) with the
HOST Docker daemon, which doesn't use the in-node CA trust from #424. On a
TLS-inspecting network that pull can x509-fail during 'k3d cluster create',
before any node boots — so the post-create diagnosis never classifies it.
- bash: _host_ca_create_hint() detects x509 in the k3d create output and prints
a platform-aware remedy (Linux system trust store vs Docker Desktop VM);
wired into _create_new_cluster's failure path.
- PS: Write-HostCaCreateHint() mirrors it (Windows Trusted Root store), wired
before the generic create failure.
- docs/INSTALL.md: document trusting the CA in the daemon itself (Linux /
Docker Desktop).
- check-drift.sh: enforce both installers keep the host-CA hint (parity).
- Tests: bats (Linux/macOS branches + silent-on-no-x509) + Pester + drift.
Closes#474
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#474): distro-aware Linux CA remedy + actionable Docker Desktop for Linux (Bugbot)
- Linux native-Docker remedy now covers both Debian/Ubuntu (update-ca-certificates)
and RHEL/Fedora (update-ca-trust), not just the Debian path — the installer
supports RHEL hosts where the Debian commands fail.
- Docker Desktop for Linux now has an actionable step (trust in the system store,
restart Docker Desktop) instead of a dangling reference to a step only printed
on the macOS branch.
- docs/INSTALL.md updated to match. bats Linux test asserts both distro paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#474): cover Colima runtime in the macOS host-CA remedy (Bugbot)
Headless macOS installs use Colima (_install_docker_colima), a Lima VM that
does not read the macOS keychain — so the 'trust it in the keychain + restart
Docker Desktop' remedy was wrong for those hosts. The macOS branch now also
gives the Colima path (add the CA inside the VM via 'colima ssh', then
'colima restart'). docs/INSTALL.md + macOS bats test updated to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#474): isolate drift negative fixtures so a missing new token can't mask them (Bugbot)
Adding _host_ca_create_hint / Write-HostCaCreateHint as required _drift_ca_trust
tokens meant the older negative fixtures (missing registry-config, comment-only
registry-config) could pass just because the new token was also absent — so the
comment-strip case no longer uniquely proved comment-stripping still works. Each
negative fixture now carries ALL other required tokens and omits/comments only
the one under test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#474): surface host-CA hint on the PS create-timeout path too (Bugbot parity)
The PowerShell create-timeout branch exited via Err without calling
Write-HostCaCreateHint (and deleted the k3d logs first), so a TLS-inspected
host pull that logs x509 then hangs to the deadline gave Windows operators a
raw timeout with no certlm.msc CA guidance — while bash runs _host_ca_create_hint
on its timeout fall-through. Capture the full create output before deleting the
logs and call the hint before the timeout Err. Adds a parity regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#474): use a herestring in _host_ca_create_hint to survive pipefail (Asad)
printf '%s' "$out" | grep -qiE ... could swallow the hint under set -o pipefail:
grep -q closes the pipe on first match, so for output past the ~64KB pipe buffer
(reachable on the timeout path, which passes the full logs) printf takes SIGPIPE,
the pipeline exits non-zero, and `|| return 0` bails even though x509 matched.
Feed grep via a herestring (no pipe, no SIGPIPE). Adds a >64KB-under-pipefail
regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(install): Tier-1 k3d-on-rootless + cgroup delegation + autostart (#1221) (#478)
* feat(install): Tier-1 k3d-on-rootless + cgroup delegation + autostart (#1221)
Slice #1221 (RFC 0001 / #1177): make a rootless Tier-1 cluster actually usable,
all behind the opt-in TB_TIER1_ROOTLESS flag (default off until the spike's §5
host validation). With the flag unset every path below is a no-op and current
behavior is byte-for-byte unchanged.
- Shared _rootless_active predicate (common.sh) so cluster.sh + setup-linux.sh
can't drift on the flag pair.
- create_cluster targets the rootless socket (DOCKER_HOST); ensure_cluster_
autostart gets a user-scope branch (systemctl --user enable + loginctl
enable-linger, never `sudo systemctl enable docker`), and promises
reboot-survival only when BOTH succeed (honesty rule, #375/#458).
- cgroup v2 controller delegation drop-in (Delegate=cpu cpuset io memory pids):
privileged write + daemon-reload on root/sudo, or hand off to prepare-host
with the exact path+content when unprivileged. run_prepare_host writes it too
(system-wide -> covers the researcher).
- Carry-ins from #452/#458: scope-aware _configure_docker_proxy (user scope, no
sudo) so a proxy-only host's rootless daemon can pull rancher/k3s;
_set_tools_target installs user-space on rootless Tier 1 (no sudo-mv crash on
a true no-sudo host); persist DOCKER_HOST to the shell rc for new terminals.
14 new bats tests incl. flag-off regressions; shellcheck --severity=error clean;
manifest.sha256 regenerated (R8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): address Bugbot on #478 — rootless autostart seed + admin-touch msg
- ensure_cluster_autostart: don't seed TB_DOCKER_AUTOSTART from the SYSTEM
docker.service is-enabled check on the rootless path. The cluster runs on the
per-user rootless socket, so a system unit that happens to be enabled would seed
a false reboot promise the rootless branch then can't honestly retract. On
rootless the user-scope enable+linger are now the sole authority (Bugbot medium).
- install_rootless_docker: the TB_ROOTLESS_ADMIN_TOUCH success line no longer
hardcodes "subuid/subgid range" — _ensure_cgroup_delegation can set that flag
too, so it now names "host prerequisites (subuid/subgid range and/or cgroup
delegation)" (Bugbot low).
- Test: rootless + system docker.service enabled + user-enable fails => flag stays
0 (pins the seed-guard). manifest regenerated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): address Asad + Saqlain review on #478
- _persist_docker_host: key idempotency off our own marker, not a bare
'DOCKER_HOST=' probe. The old probe also matched a user's own DOCKER_HOST
(remote/TCP), so we silently skipped persisting the rootless socket and new
shells kept hitting the wrong daemon. Now: our own line -> idempotent; a foreign
DOCKER_HOST -> left untouched + a warn to repoint it (Asad #2 + Bugbot #478, Medium).
- ensure_cluster_autostart: reset TB_DOCKER_AUTOSTART=0 in the rootless else-branch
(defensive; the is-enabled seed is already guarded off the rootless path) so the
honesty guarantee is local to the branch (Asad #1).
- install_rootless_docker: success line now reads "one or more one-time admin steps"
so it doesn't undercount when both the subuid and cgroup touches happen (Saqlain #1).
- Test: foreign DOCKER_HOST -> warns, no clobber, no double-write. manifest regen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(installer): failures surface the real error + log path + support-bundle hint (#423) (#476)
* fix(#423): failures surface the real error + log path + support-bundle hint
Fatal errors printed a generic red line while the actionable detail (k3d/helm
stderr) went only to the transcript, and the log path itself was never shown on
screen. Now:
- Err gains an optional $Detail param; Get-ErrDetailLines (pure, unit-tested)
renders the last ~5 non-empty output lines + the log path + a '-Diagnose'
next-step hint, appended to EVERY fatal error.
- Cluster-create failure passes k3d's stdout/stderr so the real reason (image
pull / proxy / port / WSL) shows on screen — the motivating case.
- Helm repo-add / reconcile / install failures pass helm's output via $Detail
instead of embedding it (no more duplicated log-path text).
- Install log path is announced up front in the banner (was log-only before).
Closes#423
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#423): force array enumeration of Err detail lines (Bugbot, defensive)
Bugbot flagged that a single-line Get-ErrDetailLines return (no detail + no
LOG_FILE, e.g. a Confirm-Config failure before Start-InstallLog) unwraps to a
scalar string. The foreach statement already iterates a scalar once (verified:
it prints the whole line, not per-character), so the reported char-splitting
does not reproduce -- but wrap the enumeration in @(...) to make that
unambiguous and future-proof. Adds a regression test asserting the single-line
case stays one intact line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#423): strip PS 5.1 ErrorRecord chrome from the failure excerpt (Bugbot)
helm failures arrive as `native 2>&1 | Out-String`; on Windows PowerShell 5.1
that wraps stderr in ErrorRecord chrome (the `At <file>:<n> char:<n>` position
line plus the `+ ...` / `+ CategoryInfo` / `+ FullyQualifiedErrorId` block).
Get-ErrDetailLines kept only the last 5 non-empty lines, so the excerpt was all
chrome and the real `Error:` line dropped out -- a regression from the previous
full-message dump. Filter those chrome lines before taking the window so the
actual error survives. Adds a regression test simulating the 5.1 rendering.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#423): drop duplicate inline log-path hints (Bugbot)
Err now always prints the log path via Get-ErrDetailLines, so the k3d
spawn-failure and create-timeout paths that still Hint "Full log:" right before
Err printed it twice. Remove those inline hints; Err is the single source. Adds
a guard test asserting no inline 'Full log:' hints remain in the installer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#423): put stderr last in the create-failure Err detail (Asad)
Get-ErrDetailLines keeps the LAST 5 non-empty lines, so with detail ordered
stderr-then-stdout any k3d stdout tail could crowd the real stderr reason
(FATA/x509/port) out of the excerpt. Order it stdout-then-stderr so the stderr
tail survives the window; also matches the Write-HostCaCreateHint order just
above.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Syed Is Saqlain <saqlain.syed007@gmail.com>
Co-authored-by: Syed Saqlain <syedsaqlain@MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Arturo Peroni <arturo@tracebloc.io>
Co-authored-by: shujaat_tracebloc <153823837+shujaatTracebloc@users.noreply.github.com>
Co-authored-by: shujaat hasan <shujaat@tracebloc.io>
Co-authored-by: tracebloc-release-train[bot] <309815517+tracebloc-release-train[bot]@users.noreply.github.com>
LukasWodka added a commit that referenced this pull request Jul 30, 2026
…491)
A Helm chart repo publishes only on a version change, so a template/values
edit without a Chart.yaml bump reaches no installs. That is exactly how the
perIngestionTables flag block shipped to staging yet never rendered (PR #472
changed the template but not the version, so the published 1.9.7 stayed
stale). This blocking gate makes the bump non-optional.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
LukasWodka added a commit that referenced this pull request Jul 31, 2026
… channel parser
Two things, both caught by CI/review rather than by me:
- The 'chart content ⇒ Chart.yaml version bump' guard was red: this PR
edits client/templates and client/values.yaml, and a Helm repo publishes
only on a version change, so without a bump the whole change would reach
no install -- exactly how the perIngestionTables block shipped dark in
#472. Chart 1.9.8 -> 1.9.9.
- read_ingestor_prod_channel stripped quotes BEFORE removing an inline
comment, the reverse of read_ingestor_tag. A channelTags.prod line with a
trailing comment therefore parsed as 0.7" -- a stray quote -- and the
no-arg/--write resolver would look up a nonexistent ref. Reordered to
match: key, comment, trim, quotes. Verified across all four forms
(double/single quoted with and without a trailing comment, and bare).
LukasWodka added a commit that referenced this pull request Jul 31, 2026
…#494)
* feat(ingestor): resolve the spawned tag per environment (backend#1360)
dev and staging edges now spawn ingestion Jobs from the internal channels
published by the matching data-ingestors branch (:dev / :stg) instead of
the 0.7 release float, so an ingestor change can be validated on a real
edge without a production release. Before this, the ingestor image
existed only as a byproduct of a prod release -- on 2026-07-30 testing
one change cost a prod PyPI publish plus an FR-gate override.
- images.ingestor.tag becomes an explicit override, EMPTY by default.
- images.ingestor.channelTags carries the per-environment floats
(dev/stg/prod), keyed on the resolved CLIENT_ENV.
- New tracebloc.ingestorTag helper mirrors tracebloc.ingestorDigest's
precedence: explicit tag > channel for CLIENT_ENV > literal 0.7 (so a
release predating these keys still renders under --reuse-values).
- prod deliberately stays a semver float, NOT a :prod channel -- no such
tag is published, and prod normally runs prodDigest anyway.
- The ingestor-multiarch CI guard previously hard-failed on an empty tag;
it now validates the explicit override when set plus every channelTags
entry, since an edge resolves exactly one of them.
Chart defaults propagate through the fleet auto-upgrade
(--reset-then-reuse-values), and the installer does not pin the tag, so
existing dev/staging edges pick up their channel on the next upgrade.
An operator who set images.ingestor.tag explicitly keeps it.
Verified: 307/307 helm unittest across 27 suites (9 new cases covering
each environment, the override, an unknown CLIENT_ENV, and a
channelTags-less replay); helm lint --strict clean on all four platform
values files; rendered INGESTOR_IMAGE_TAG confirmed as dev/stg/0.7/0.7
for dev/stg/prod/unset; schema still rejects 'latest' in channelTags and
now accepts the empty override.
* fix(review): normalize CLIENT_ENV aliases before the channel lookup
Asad's first note landed on a real defect, not just a stale description.
The schema documents CLIENT_ENV as (dev, staging, prod) while the channel
keys are dev|stg|prod, so CLIENT_ENV=staging -- the documented value --
missed channelTags entirely and fell back to the prod float. Meanwhile
client-runtime normalizes staging->stg at runtime, so that edge would
have talked to the stg backend while spawning the 0.7 release ingestor:
exactly the split-brain client-runtime#227 was filed for, reintroduced
one layer up.
tracebloc.ingestorTag now normalizes development/staging/production
before the lookup, mirroring proxy_config.ENV_ALIASES, and the schema
description states which values are canonical and that it is load-bearing
for channel selection.
Also addresses the second note: values.schema.json is edited surgically
in the file's own style instead of being reformatted by a json round-trip
(576-line diff -> 29).
Verified: 310/310 helm unittest (3 new alias cases); lint --strict clean
on all four platform files; rendered tag is dev/dev/stg/stg/0.7/0.7 for
dev/development/stg/staging/prod/production, and an unknown value still
falls back to the float rather than rendering empty.
* fix(review): one CLIENT_ENV normalizer, and unbreak the digest resolver
Both from Bugbot on #494, and both caused by my own half-applied alias fix.
1. The alias normalization went into tracebloc.ingestorTag only, while
tracebloc.ingestorDigest still compared the RAW CLIENT_ENV to "prod".
So CLIENT_ENV=production got the prod float tag but an EMPTY digest --
silently dropping the reproducibility pin (backend#1028/#1245) on an
edge that looked correctly configured, which is worse than the bug the
alias fix was for. Extracted tracebloc.clientEnv as the single
normalizer and pointed both helpers at it, so they cannot drift again
(the reason ENV_ALIASES lives once in client-runtime proxy_config).
2. scripts/resolve-ingestor-digest.sh read images.ingestor.tag, now empty
by default, so the documented no-arg / --write path exited on an empty
tag -- the exact command the chart comments and the ingestor-multiarch
CI error tell operators to run. It now falls back to
images.ingestor.channelTags.prod on both the yq and the yq-free path,
with a matching sibling awk reader scoped the same way.
Verified: 311/311 helm unittest (a new case pins the production-alias
digest); lint --strict clean on all four platform files; rendered
tag+digest correct for prod/production/stg/staging/dev/development; the
script's no-arg path resolves 0.7 -> the multi-arch digest again, and the
portable reader returns 0.7 with tag empty.
* style(script): keep each yq-free reader with its own header comment
The new read_ingestor_prod_channel landed between read_ingestor_tag's
header comment and read_ingestor_tag itself, so that header described the
wrong function and read_ingestor_tag sat comment-less ~30 lines below.
Moved the new function below the original; each header is now directly
above the function it documents. No behaviour change -- both readers
verified still returning 0.7 / empty, and the no-arg resolver path still
resolves the multi-arch digest.
* fix: bump the chart to 1.9.9, and strip comments before quotes in the channel parser
Two things, both caught by CI/review rather than by me:
- The 'chart content ⇒ Chart.yaml version bump' guard was red: this PR
edits client/templates and client/values.yaml, and a Helm repo publishes
only on a version change, so without a bump the whole change would reach
no install -- exactly how the perIngestionTables block shipped dark in
#472. Chart 1.9.8 -> 1.9.9.
- read_ingestor_prod_channel stripped quotes BEFORE removing an inline
comment, the reverse of read_ingestor_tag. A channelTags.prod line with a
trailing comment therefore parsed as 0.7" -- a stray quote -- and the
no-arg/--write resolver would look up a nonexistent ref. Reordered to
match: key, comment, trim, quotes. Verified across all four forms
(double/single quoted with and without a trailing comment, and bare).
LukasWodka added a commit that referenced this pull request Jul 31, 2026
…ange (#522)
Three `producer | early-exiting-consumer` pipelines ran under `set -o pipefail`.
When the consumer exits on its first match/line it closes the pipe, and once the
producer has more than the ~64KB pipe buffer left to write it takes SIGPIPE and
the pipeline exits 141 — a *success* case reported as failure. Measured on
ubuntu-24.04 (bash 5.2.21 / GNU grep 3.11 / coreutils 9.4): 65,622 bytes is
already enough.
WHY this matters most in the chart guard: there the 141 lands on the `if !`
branch, so a genuine `client/templates/**` edit is reported as "guard N/A" and
the version-bump check is SKIPPED — the guard fails OPEN and waves through
exactly the dark ship it was added to stop (PR #472 / the perIngestionTables
flag block). Demonstrated end-to-end on a real repo: at 1,301 changed paths
(58,723 bytes) with a template edit and NO version bump, the old body exits 0
"guard N/A"; the new one exits 1 and blocks. The mirror direction is broken too:
a SIGPIPE on the `grep -qx client/Chart.yaml` MATCH short-circuits the `&&`, so
a PR that DID bump the version is failed with a message that says it did not.
Restructured rather than papered over with `|| true`, which would only convert a
fail-open into a different fail-open:
- chart-version-guard.yml — classify the changed-file list with a bash `read`
loop + `case` (no pipe, no subprocess, so neither SIGPIPE nor a grep rc=2 can
be mistaken for "no match"), and fail CLOSED with ::error:: on a missing base
SHA or a failed `git diff`: "don't know" must never read as "nothing changed".
- _pull_failure_detail — `head -n 3 <<< "$bad"`. With errexit live the old
pipeline aborted the function AT that line, dropping the scoped pull event
underneath it, i.e. the one actionable reason (x509 / blocked registry / auth).
- _gpu_stack_signature — capture whole, take the first line with `%%$'\n'*`. This
site was NOT reachable in practice (the trailing `|| true` already absorbed the
141), but that `|| true` swallowed every real failure code alike; `|| ..._out=""`
states the actual contract (absent tool / timeout ⇒ empty ⇒ don't cache).
Behaviour is unchanged below the buffer threshold: 669/669 bats tests pass, and
_gpu_stack_signature is byte-identical across normal / multi-line / absent /
timeout / 20k-line-chatty probe output. Verified on bash 3.2 (macOS floor) too.
scripts/manifest.sha256 regenerated via scripts/gen-manifest.sh (R8).
Co-authored-by: Claude <noreply@anthropic.com>
LukasWodka added a commit that referenced this pull request Jul 31, 2026
The guard classified only `client/templates/**` and `client/values.yaml`, but
release-helm-chart.yaml packages BOTH `./client` and `./ingestor` (lines
131-132) into one shared index.yaml (line 193). So an unbumped `ingestor/**`
edit merged green — the exact dark ship the guard exists to stop (PR #472).
Raised by Bugbot on the develop->staging promotion PR #519.
Measured on develop: 6 of 9 commits touching ingestor chart content never
bumped ingestor/Chart.yaml, whose version has been 0.2.0 since 2026-05-20.
Because `helm package ./ingestor` passes no --version override, ingestor's
published version IS that file, so those edits did not go nowhere — they
overwrote an already-published version. ingestor-0.2.0.tgz was replaced 5x
between 2026-05-20 and 2026-07-29 and `helm repo index --merge` refreshed the
digest in place, so two installs of "0.2.0" months apart are not the same
chart. Helm also caches by version, so an existing client may never pick the
change up at all.
- derive the guarded chart list from the release workflow's `helm package`
lines instead of hardcoding it, so a third chart is guarded on day one
- require a bump of the chart's OWN Chart.yaml (bumping client no longer
satisfies an ingestor change)
- add client/values.schema.json (packaged, and Helm validates user values
against it at install time), plus charts/** and crds/** pre-emptively
- report every unbumped chart in one run rather than one per push
- fail closed when the chart list cannot be read, or when a packaged chart
has no Chart.yaml
Move the logic into scripts/chart-version-guard.sh so it can be tested: the
new scripts/tests/chart-version-guard.bats (23 cases, real throwaway git repo,
no stubbed git) runs under the required `Unit tests` check, which this
workflow is not. 13 of the 23 fail against the previous inline logic; the 10
that pass on both are the client-side semantics, deliberately unchanged.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
LukasWodka added a commit that referenced this pull request Aug 6, 2026
My earlier merge resolution took develop's 1.9.20 verbatim, reasoning from the
release train's rule: v1.9.20 is untagged, and the train's version_preflight
only refuses when the version is ALREADY released, so one bump covers a whole
release cycle. That reasoning is correct for the train and wrong for this repo.
client/scripts/chart-version-guard.sh enforces a stricter rule for a
repo-specific reason: chart content reaches installs only via a NEW chart
version, because a Helm repo publishes on version change. An unbumped
template/values edit therefore either reaches nobody or overwrites an
already-published version. Both have happened here - the perIngestionTables
block shipped dark in PR #472, and ingestor-0.2.0.tgz was overwritten 5x
between 2026-05-20 and 2026-07-29.
This PR changes client/templates/** and client/values.yaml, so it needs its
own version rather than riding develop's. v1.9.21 is untagged.
LukasWodka added a commit that referenced this pull request Aug 6, 2026
…r format guard (backend#723 PR-2) (#597)
* feat(mysql): A2 engine split — 8.4 opt-in for fresh installs + datadir format guard (backend#723 PR-2)
Chart: mysql-format-guard init container fails fast (with an actionable
message) when the engine major and datadir format disagree — 8.4 over a
5.7-format datadir and 5.7 over an 8.x one are both refused before mysqld
CrashLoops; the 8.0 transit hop and custom digest pins stand down.
tracebloc.mysqlEngineMajor derives the expected engine (digest-wins,
mirroring tracebloc.image); the 5.7 digest literal is CI-pinned to the
values default. Default render changes by exactly the guard.
Installer (A2, decision 2026-08-05): _resolve_mysql_engine picks the engine
for the generated values — explicit TB_MYSQL_ENGINE wins; a previous 8.4
opt-in is sticky; any existing release or real datadir content pins 5.7;
only a fresh arm64 install auto-selects 8.4 (native multi-arch instead of
amd64 emulation). amd64 fresh installs stay 5.7 for now (soak first).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): fail CLOSED on an unlistable mysql datadir in the engine resolver (Bugbot)
The auto branch treated a failed ls -A as an empty datadir — on arm64,
--reuse-data commonly leaves a uid-999 mysql dir the host user cannot
list, so the resolver opted the reuse into 8.4 and the format guard then
(correctly) refused the 5.7 datadir: the reuse path never came up. An
unlistable dir now counts as content (mirrors _leftover_data_dirs' fail-
closed stance for the same ownership case), with a chmod-000 regression
test. Rebased over #593/#527/#525 (manifest regenerated; my bats
negations now carry the #527 '|| return 1' enforcement idiom).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(bats): harden the 11 new engine-resolver assertions per the #527 hygiene guard
bats-hygiene's scanner requires every standalone bracket assertion in an
@test body to end in '|| return 1'; the resolver tests added on this
branch predated rebasing onto that guard.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tests): restore the brace the #604 merge seam ate (last mirror test)
git hoisted the shared closing brace out of the conflict region during the
rebase onto #604; the file then died at parse (1 of 88 tests ran).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(chart): the format guard honors global.imageRegistry (Bugbot)
Semantic rebase conflict with #604: every other image include gained the
mirror dig while the guard (written pre-#604, merged clean textually)
kept a hardcoded docker.io — on mirrored/air-gapped edges the always-on
guard alone would ImagePullBackOff and block mysql on exactly the fleets
#604 serves. Same dig expression now + a mirror re-home pin test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(chart): bump 1.9.20 -> 1.9.21, as this PR changes chart content
My earlier merge resolution took develop's 1.9.20 verbatim, reasoning from the
release train's rule: v1.9.20 is untagged, and the train's version_preflight
only refuses when the version is ALREADY released, so one bump covers a whole
release cycle. That reasoning is correct for the train and wrong for this repo.
client/scripts/chart-version-guard.sh enforces a stricter rule for a
repo-specific reason: chart content reaches installs only via a NEW chart
version, because a Helm repo publishes on version change. An unbumped
template/values edit therefore either reaches nobody or overwrites an
already-published version. Both have happened here - the perIngestionTables
block shipped dark in PR #472, and ingestor-0.2.0.tgz was overwritten 5x
between 2026-05-20 and 2026-07-29.
This PR changes client/templates/** and client/values.yaml, so it needs its
own version rather than riding develop's. v1.9.21 is untagged.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka
LukasWodka deleted the feat/1205-enablement-knob branch August 14, 2026 13:53
LukasWodka added a commit that referenced this pull request Aug 15, 2026
…pying it (#723)
* release-train: staging -> main (#495)
* chore(chart): close values-schema gaps + drop dead override/code/docs (#963) (#457)
* chore(chart): close values-schema gaps + drop dead override/code/docs (#963)
Contract fixes for the client Helm chart (re-verified against develop at
chart v1.9.6; the #963 audit was taken at v1.8.4):
- values.schema.json: add the six live-but-unvalidated keys so bad values
fail `helm lint` instead of silently passing —
egressReachabilityCheck.enabled, ingestionAuthz.{allowed,serviceAccountName},
networkPolicy.training.enforcementProbeTimeoutSeconds, podTokenSigningSecret,
podTokenTtlSeconds. Types/defaults/constraints taken from values.yaml and
the templates that consume them. helm lint passes.
- ingestor subchart: remove the dead `image.repository` key — no template
ever rendered it (jobs-manager spawns from the parent chart's
images.ingestor.repository). Kept image.digest (live). README's air-gapped
override rows now point at the authoritative parent-chart path.
- README: drop the hardcoded chart version (said v1.3.5 while Chart.yaml is
1.9.6) and point to Chart.yaml / the releases page, so it can't drift again.
- Delete the unwired check_docker_arch_mac function + its bats test (no call
sites) and the orphaned docs/eks.md (referenced nowhere).
Part of tracebloc/backend#963.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(installer): regenerate manifest after common.sh trim + develop merge
The #963 chart-contract cleanup dropped 48 dead lines from
scripts/lib/common.sh, changing its sha; the installer manifest wasn't
regenerated, so the Static analysis gate (gen-manifest.sh --check) failed.
Merging develop also refreshed preflight.sh/install-k8s.ps1 hashes.
Regenerate scripts/manifest.sha256 to match the working tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Syed Saqlain <syedsaqlain@MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(release): package charts as the tag version + pre-releases skip gh-pages (#467)
* fix(release): package charts AS the tag version + pre-releases skip gh-pages
Incident 2026-07-29: the v1.9.7-rc.1 pre-release packaged the client chart
from Chart.yaml's plain 1.9.7 and pushed it into the public helm index as
a STABLE version -- customers running helm upgrade would have received
staging content (removed from the index by hand, tgz deleted). Two layers
now prevent it: (1) helm package --version/--app-version from the release
tag, so rc charts carry the -rc.N suffix helm's pre-release rules key on;
(2) pre-releases never run the gh-pages index steps at all -- FR consumes
the release assets (stamped installer / chart tgz), the index is a
customer surface reserved for finals.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: gh-pages gates key on verify's tag-derived prerelease, not the frozen event (Bugbot)
github.event.release.prerelease is an event-time snapshot: after verify
demotes a mis-marked release, it still reads false, so the demoted rc
would have entered the public index anyway. verify now outputs effective
prerelease-ness derived from the tag shape (the same strict rule the
demotion uses) and all three gh-pages steps gate on that output.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: post-publish index-invariant job (manual leak catch -> CI)
After every release run: the public index must contain only stable-shaped
versions, and a prerelease run must not have indexed its own version.
Fails loudly; would have caught the 1.9.7 leak within a minute of it
happening instead of during manual FR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(install): rootless Docker core + Tier-1 routing (opt-in) (#1219) (#452)
* feat(install): rootless Docker core + Tier-1 routing (opt-in)
Add install_rootless_docker() and a Tier-1 early-branch in install_linux
so a modern-kernel host with no runtime and no root can install entirely
in user space (RFC 0001 Tier 1 — the RFC's primary path). Gated behind
opt-in TB_TIER1_ROOTLESS=1; with the flag unset a Tier-1 host falls
through to the legacy privileged flow unchanged (validated default).
- install_rootless_docker: uidmap-helper precondition (defers to
prepare-host #1178 when absent — never self-sudo), no-sudo install via
dockerd-rootless-setuptool.sh or get.docker.com/rootless, user-scoped
systemctl --user + loginctl enable-linger, DOCKER_HOST export with
XDG_RUNTIME_DIR fallback, single docker-info verify (no retry loop).
- Tier-1 branch mirrors the Tier-0 early-return. Tools still install via
sudo here (_set_tools_target keys no-sudo off Tier 0 only) — tightening
that for rootless Tier 1 is deferred to slice 3 (#1221).
- 6 bats cases; scripts/manifest.sha256 regenerated (R8).
Closestracebloc/backend#1219
Part of tracebloc/backend#1177 · Epic tracebloc/backend#1168
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address Bugbot review on rootless Docker core (#452)
- Prepend ~/bin to PATH after the rootless install so this run's docker
info verify + later k3d/docker calls resolve the CLI the
get.docker.com/rootless fallback installs there (High).
- Bound the rootless `docker info` verify with a new shared _bounded
helper (timeout/gtimeout, mirrors probe.sh) so a wedged user daemon
can't hang a headless install (Medium).
- Guard the user-systemd bring-up under set -e: `systemctl --user … ||
true` (the bounded verify is the real gate) and `loginctl
enable-linger … || warn` (optional; fails on polkit-locked hosts even
when the daemon is up) (Medium).
Adds 2 bats cases (~/bin on PATH; systemd/linger failure falls through
to the verify). Manifest regenerated (R8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: correct the uidmap remedy message (Bugbot #452)
The missing-uidmap error claimed prepare-host would install the uidmap
helpers, but run_prepare_host only sets up privileged Docker + the docker
group — it never installs uidmap. Point at the two honest remedies
instead: install the `uidmap` package directly (rootless then works), or
run prepare-host to set up Docker so the researcher installs at Tier 0
(no rootless needed). #1220 folds this into the shared subuid/subgid gate
and teaches prepare-host to install uidmap for real.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(install): TODO(#1221) — rootless daemon needs user-scoped proxy config
Bugbot on #452 flagged that install_rootless_docker never configures a
corporate proxy for the user-scoped dockerd (the #244 _configure_docker_proxy
is sudo/system-scoped and the Tier-1 early-return never reaches it), so k3d
pulls of rancher/k3s time out on proxy-only hosts. Deferred to #1221 (the
k3d-on-rootless-socket slice that owns the pulls); leaving a tracked TODO so
the follow-up adds the user-scoped drop-in.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address Asad review nits on #452 — chmod no-op + misleading comment
- Drop the chmod +x on the rootless installer script: it runs via
`sh "$rootless_script"`, which ignores the exec bit.
- Reword the Tier-1 _install_userspace_tools comment: tools still
sudo-install on Tier 1 (only _persist_tools_on_path is no-sudo);
the comment previously implied otherwise.
The underlying _set_tools_target sudo-crash on no-sudo hosts and the
post-install DOCKER_HOST shell persistence are tracked to #1221.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: resolve user via id -un in install_rootless_docker (Saqlain review, #452)
$USER can be empty in headless / su / cron contexts (a Tier-1 target),
which would break `loginctl enable-linger` and the success line. Resolve
the user once via `id -un` (fallback $USER) and use it for the linger
call, its hint, and the success message. Matches the id-based robustness
DOCKER_HOST already uses. Happy-path bats now mocks `id -un` cleanly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: add code-quality caller workflow (advisory) (#463)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Add pre-commit hooks (Layer 0, lint-only) (#465)
* Add pre-commit config (Layer 0, lint-only)
Lint-only on purpose: scripts/manifest.sha256 must keep matching the bytes
under scripts/, so no hook may rewrite files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Document pre-commit setup in README
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* docs: add Bugbot resolve-and-reply team norm to .cursor/BUGBOT.md (#464)
Part of tracebloc/backend#1308
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* ci: cover scripts/resolve-ingestor-digest.sh in CI shellcheck (#466)
* ci: lint scripts/resolve-ingestor-digest.sh in CI shellcheck (was never linted)
Both CI shellcheck invocations enumerate files explicitly and both omitted
this script. Verified clean against shellcheck --severity=error --shell=bash
0.11.0 before adding. The pre-commit hook from #465 already covers it
locally; this closes the same gap on the CI side.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: lint scripts/resolve-ingestor-digest.sh in CI shellcheck (was never linted)
Both CI shellcheck invocations enumerate files explicitly and both omitted
this script. Verified clean against shellcheck --severity=error --shell=bash
0.11.0 before adding. The pre-commit hook from #465 already covers it
locally; this closes the same gap on the CI side.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): honest cosign bootstrap download + translated DISM failures (#468) (#469)
The v1.9.7-rc.1 FR killed a healthy install: PS 5.1's progress overlay
throttled the 17 MB pinned-cosign fetch to ~4.5 min of dead silence and
the window read as frozen.
- silence the PS 5.1 progress overlay in Get-WithRetry/Get-Optional
(function-local, auto-reverts) - the classic 10-50x IWR speedup
- run the cosign fetch in a background job with a dim liveness tick
(Wait-JobWithTicks / Get-OptionalWithTicks; cwd pinned per #409,
TLS 1.2 re-applied in the fresh process), expectation lines before,
elapsed + checksum-verified confirmation after
- ASCII-only string literals in both installers: the release asset is
served without a charset so PS 5.1's irm decodes UTF-8 source as
Latin-1 before iex, and BOM-less -File reads are ANSI - literal
em-dashes/ellipses reached customers as mojibake. Locked in by a
tokenizer-based Pester test (which also caught the -Help here-string).
- Enable-OneVirtFeature: translate DISM's raw COMException (feature
package absent on Server SKUs vs enable failure) and stop demanding
a reboot for a feature that never enabled (old code sent Server
users into a reboot->re-run->same-error loop)
Pester: 212 passed / 0 failed locally (pwsh 7.5, Pester 5.7.1).
PSScriptAnalyzer: 0 errors. manifest.sha256 regenerated.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): trust the corporate MITM CA in the k3d nodes (#424) (#453)
* fix(installer): trust the corporate MITM CA in the k3d nodes (#424)
Proxy REACHABILITY reaches the nodes, but on a TLS-inspecting (break-and-
inspect) network the nodes still don't TRUST the corporate CA, so every in-node
containerd pull (rancher/k3s, ghcr.io, tracebloc images) fails x509 — then
masked (helm runs without --wait) into a root-cause-free "an image couldn't be
pulled." Enterprise/hospital archetype, all three OSes.
- Inject the CA at create time: when TRACEBLOC_CA_BUNDLE (or CURL_CA_BUNDLE) is
set, mount the bundle into every k3d node and write a registries.yaml pointing
containerd at it per-registry (docker.io, registry-1.docker.io, ghcr.io), via
the same --config/create path that already carries proxy env. Parity across
scripts/lib/cluster.sh (Linux/macOS) and install-k8s.ps1 (Windows). A CA var
set but unreadable fails loudly instead of silently skipping.
- Name the env var where the user hits the wall: the TLS-interception preflight
hint (both OSes), docs/INSTALL.md, and the PS -Help env-var list.
- CA-aware diagnosis: detect x509 / "certificate signed by unknown authority"
pull events and report a dedicated image_pull_ca state — "the cluster does not
trust your network's TLS-inspection CA" + the exact remedy — instead of the
generic pull error. Mirrored in summary.sh and Print-Summary.
- New check-drift.sh parity check (_drift_ca_trust) so neither installer can drop
the CA wiring for the other's OS.
Tests: +8 cluster.bats, +3 summary.bats, +2 check-drift.bats, +8 Pester.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): CA-trust hardening — no fail-open, bounded events, verify CA readable (Bugbot #424)
Three Bugbot findings on #424:
- _write_k3d_registries_config failed open: on mktemp failure it returned success
with no path, so create still mounted the CA and logged "nodes trust it" but
dropped --registry-config → containerd never got ca_file, x509 pulls still fail
while the operator thinks it's fixed. Now returns non-zero; the caller hard-errors
(CA was supplied, so we refuse to proceed without wiring it in).
- PS Get-NotReadyState `kubectl get events` had no --request-timeout (the bash path
does) — on a wedged/proxy-misrouted API, classification could hang. Added
--request-timeout=5s to match _diagnose_not_ready.
- PS Resolve-CaBundle only checked existence (Test-Path), not readability, so an
unreadable CA passed on Windows but bash (-r) hard-fails. Added an OpenRead probe
so both fail the same way, up front.
Tests: cluster.bats +mktemp-failure + unwritable-registries-hard-error;
install-k8s.Tests.ps1 +unreadable-CA (Unix) + events --request-timeout assertion.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): errexit-safe CA-resolve capture + drift check ignores comments (Bugbot #424 r2)
Two round-2 Bugbot findings:
- Under `set -euo pipefail`, `ca_bundle="$(_resolve_ca_bundle)"; ca_rc=$?` exited on
the rc-2 (unreadable/missing CA) BEFORE ca_rc/error ran — operators got a bare
exit instead of the "can't be read" guidance. Capture with `|| ca_rc=$?` so
errexit doesn't fire and the guidance prints.
- _drift_ca_trust whole-file grep matched tokens in comments (e.g. --registry-config
appears in a comment above the real line), so deleting the functional wiring could
still pass. Strip comment lines first (matches the execute-gate / preflight-host
checks), no grep -q under pipefail.
Tests: cluster.bats +errexit-safe-capture; check-drift.bats +comment-only-token drift.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): TLS-preflight hint names the right var per layer/OS (Bugbot #424 r3)
The hint claimed TRACEBLOC_CA_BUNDLE makes "the host AND the k3d nodes" trust the
CA, but the host connectivity checks use curl_secure / Invoke-WebRequest, which
read CURL_CA_BUNDLE / the system trust store — not TRACEBLOC_CA_BUNDLE (that var
only reaches the nodes via _resolve_ca_bundle). Following the hint literally left
host preflight TLS failures unchanged. Corrected, no behaviour change:
- bash: CURL_CA_BUNDLE fixes these host checks AND the nodes; TRACEBLOC_CA_BUNDLE
is nodes-only; or add the CA to the system trust store.
- Windows: import the CA into the cert store for the host checks (Invoke-WebRequest
uses the store, not an env var); TRACEBLOC_CA_BUNDLE/CURL_CA_BUNDLE cover the nodes.
(Reworded to avoid a bare lowercase `curl` that the curl_secure style guard flags.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): apply CA on cluster REUSE path — warn + recreate guidance (Bugbot #424 r4)
The image_pull_ca remedy said "set the CA and re-run", but CA trust is baked in
only at fresh create; a re-run reuses the existing cluster and never mounts the CA
or passes --registry-config, so the x509 pulls persisted. Mirror the existing
proxy handling (baked-at-create → warn on reuse):
- bash _check_existing_cluster_ca (called from _handle_existing_cluster): warns when
a CA bundle is set but the reused server container lacks the CA mount.
- ps1 New-K3dCluster reuse block: same check via docker inspect mounts.
- both image_pull_ca remedies now say to `k3d cluster delete <name>` first, then
re-run with the CA (CA, like proxy, can't be added to a running cluster).
Tests: cluster.bats +3 (no-CA no-op / CA-but-missing-mount warns / mount-present silent).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(installer): add auth.docker.io to the CA registries config (Bugbot #424 r5)
The registries.yaml ca_file entries covered docker.io / registry-1.docker.io /
ghcr.io, but Docker Hub pulls also TLS-handshake with auth.docker.io for bearer
tokens — so on a break-and-inspect network containerd still rejected the
intercepted cert there even with the CA mounted. #416 already probes auth.docker.io
at preflight; the CA registries list now matches. Added to TB_CA_REGISTRIES and
$TbCaRegistries; registries.yaml test counts 3 -> 4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#424): _resolve_ca_bundle rejects a directory, not just unreadable paths
A directory of PEMs is readable (-r) but would bind-mount over the single
node ca_file path and containerd can't read it — the silent 'looks applied
but still x509' case. Require a regular file (-f), mirroring the PS
Resolve-CaBundle -PathType Leaf check. Adds a directory-reject bats case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#424): exact whole-line match for CA mount detection (Bugbot)
_check_existing_cluster_ca used a substring test on docker mount
destinations, so a longer path embedding /etc/ssl/certs/tracebloc-mitm-ca.crt
(e.g. …crt.bak) would be treated as the CA mount and skip the recreate
warning while containerd still x509-fails. Switch to grep -qxF (exact
whole-line), matching the PS anchored regex. Adds a substring-embed test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#424): scope x509 classification to the pull-failure event (Asad)
_diagnose_not_ready / Get-NotReadyState flagged image_pull_ca on ANY x509
event in the namespace, so a stale/unrelated x509 event (e.g. a FailedMount)
could misdirect the user into a needless delete+recreate. Filter events to
the image-pull failure lines (failed to pull / ErrImagePull) before testing
x509, in both bash and PS. Adds an unrelated-x509 test to each side.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(chart): perIngestionTables — RFC-0003 D16 enablement knob (backend#1205) (#472)
* feat(chart): perIngestionTables — the RFC-0003 D16 enablement knob (backend#1204/#1205)
values.perIngestionTables (default false, schema-typed) renders
PER_INGESTION_TABLES=1 onto the jobs-manager, which forwards it into
every ingestion Job it spawns (client-runtime companion PR). Flip per
environment, dev first, only once that environment's backend + engine
images + jobs-manager carry the merged D-series. Default installs
render byte-identically (conditional block; unit tests pin both sides).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(values): own banner for perIngestionTables — it is not part of the authz section (review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* chore: clear house-rules findings (#470)
Fix every finding the shared org checker (tracebloc/.github
scripts/house-rules.sh) reports at develop HEAD: missing curl
timeouts/TLS floors, plus (cli) a missing pipefail. Waivers only where
the finding is a documented false positive. Part of tracebloc/backend#1303.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(install): Tier-1 subuid/subgid gate + prepare-host remediation (#1220) (#458)
* feat(install): Tier-1 subuid/subgid gate + prepare-host remediation
RFC 0001 #1220. Detect the one privileged residue a modern rootless host may
still need — a subordinate UID/GID range + the setuid uidmap helpers — and
either proceed (present), hand off to prepare-host (unprivileged), or perform
one announced touch (sudo available). Never blanket sudo, never an opaque
mid-install crash inside dockerd-rootless-setuptool.sh.
- probe.sh: _probe_subid_ranges (PROBE_SUBID) + _probe_uidmap_helpers
(PROBE_UIDMAP), set in run_host_probes (Linux only), plus audit rows on
the Tier-1 path.
- common.sh: shared pure parsers _subid_has_entry + _next_subid_start, used
by both the probe and the remediation (no duplication).
- setup-linux.sh: _ensure_subid_ranges gate (present / hand-off / one
announced sudo touch) called before install_rootless_docker;
_provision_subid_ranges (idempotent, non-overlapping block, usermod
--add-subuids with file-append fallback, uidmap install) shared by the
installer and run_prepare_host. Folds in slice-1's minimal uidmap check.
- Tests: probe.bats + setup-linux.bats. Manifest regenerated (R8).
Closestracebloc/backend#1220
Part of tracebloc/backend#1177 · Epic tracebloc/backend#1168
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): address #458 review — subid gate/probe/hand-off robustness
Bugbot + @saadqbal + a self code-review on client#458, all in slice-2's code:
- id -un everywhere (gate, _provision default, probe): $USER diverges from the
rootless daemon's user under su/cron, which wedged detection/provisioning (#1).
- Re-verify the uidmap helpers are usable (present AND setuid|cap_setuid) after
install, and return non-zero + warn (NOT error/exit) so run_prepare_host stays
best-effort while the installer sudo-path hard-fails via `|| error` (#2 + self-review).
- _idmap_helper_ok (common.sh): accept the setuid bit OR a cap_setuid filecap, so
Arch's `shadow`/pacman path isn't false-rejected (#3).
- Hand-off + run_prepare_host fallback compute a non-overlapping start via
_next_subid_start (honoring TB_SUBUID_FILE/TB_SUBGID_FILE), not hardcoded 100000
(#4 + self-review path-override).
- Hand-off command names the researcher (TB_PREPARE_USER=) — bare prepare-host
provisions nothing, so it would have looped back to the same hand-off (#5).
- Capture `usermod --help` before grepping — pipefail-safe (#6).
bats: id -un mocks, filecaps accept/reject, gate hand-off (names user + computed
start), _provision re-verify best-effort, run_prepare_host best-effort. R8 regen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(install): stub the subid gate in the Tier-1 rootless routing test
install_linux's Tier-1 branch now calls _ensure_subid_ranges (slice 2)
before install_rootless_docker; the routing test left it un-stubbed, so the
real gate hit the no-sudo hand-off and error()'d → install_linux returned
non-zero. Stub _ensure_subid_ranges (its own behavior is covered by the
dedicated gate tests) and assert it runs before daemon setup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(install): scope set -o pipefail to a subshell (bats harness footgun)
Setting `set -o pipefail` in the @test body can leak into bats' own
post-test pipelines and fail the whole run with exit 1 even when every
test reports ok (no 'not ok'). Confine it to a subshell around the call
so the pipefail-safety assertion still holds without touching the harness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): style guard — no bare curl in the prepare-host hint
The hand-off piped 'curl … | TB_PREPARE_USER=… bash', which breaks
check-style.sh's exemption for the canonical 'curl … | bash' one-liner
(the env var sits between the pipe and bash). Split into an 'export
TB_PREPARE_USER=…' line + the canonical piped one-liner — still names the
researcher, and passes the guard. Verified with scripts/check-style.sh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): fix the #458 red bats + 2 Bugbot bugs (newgidmap cap, write-failure)
Root cause of the "540 ok but exit 1" bats red: the probe.bats uidmap tests set
PATH="$bin" in the test body to hide system helpers, which also hides `rm` — so
bats-core 1.10+ can't run its own per-test cleanup ("rm: command not found") and
fails the whole run even though every test passes. Scope the hermetic PATH to a
subshell so it can't leak into bats' machinery. (Why develop was green + this was
so hard to see: these tests are new in slice 2, and the symptom is a clean pass
list with a non-zero exit.)
Two real Bugbot findings in the slice's own code:
- _idmap_helper_ok checked cap_setuid for BOTH helpers; newgidmap carries
cap_setgid (Arch filecaps) -> false-rejected. Map name->cap; fix the test mock
that masked it + add a wrong-cap regression test.
- _provision_subid_ranges printed success/returned 0 even when the usermod/tee
write failed (callers run it with set -e off) -> installer proceeds with no
range. Guard every write; warn + return 1 on failure. + a test.
Verified: probe.bats + setup-linux.bats EXIT 0 (0 not-ok, 0 rm-not-found) in a
faithful ubuntu 24.04 + bats 1.10 + non-root container. Rebased onto develop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): 2 more Bugbot findings on #458 (apt hang, false zero-root message)
- _install_uidmap_pkg ran a bare `sudo apt-get install -y uidmap` under the
spinner — no needrestart/DEBIAN_FRONTEND env, no DPkg::Lock::Timeout, no
apt_wait_for_lock — so a headless Tier-1 install can hang on Ubuntu needrestart
or an apt-daily lock (#210 class). Reuse the repo's hardened PM_INSTALL
(populate via setup_pm, which Tier 1 skips) + apt_wait_for_lock.
- install_rootless_docker always printed "no administrator rights were used",
even after _ensure_subid_ranges performed an announced sudo touch on the
root/sudo_nopw path. The gate now sets TB_ROOTLESS_ADMIN_TOUCH and the summary
is honest on both the zero-root and one-admin-touch paths.
Tests: hardened-install assertion (NEEDRESTART_MODE + DPkg::Lock::Timeout) + a
success-message honesty test. Verified EXIT 0 (0 not-ok, 0 rm-errors) in the
faithful ubuntu 24.04 + bats 1.10 + non-root container.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): refresh the package index in _install_uidmap_pkg (Bugbot #458)
Completing the prior apt-hardening: _install_uidmap_pkg populated PM_INSTALL and
waited for the dpkg lock but never ran PM_UPDATE. On the Tier-1 path this is the
first package op, so an empty/stale index can't locate uidmap/shadow and the
install hard-stops. Run $PM_UPDATE (best-effort) first, matching the repo's other
install paths (setup-linux.sh:335/543). Test asserts the index refresh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(installer): silence the PS 5.1 progress throttle in install-k8s.ps1 downloads (#468 follow-up) (#471)
Same class as the bootstrap fix in #469: PS 5.1's progress overlay
throttles Invoke-WebRequest 10-50x and reads like a hang. One
function-local $ProgressPreference in Invoke-WithRetry covers every
fetch scriptblock it drives (dynamic scoping) - winget msixbundle,
Docker Desktop fallback, kubectl, k3d, helm, GPU plugin yaml, and the
version resolvers.
Honest-progress expectation lines (sizes measured today via HEAD):
Docker Desktop ~600 MB, winget ~200 MB, kubectl ~60 MB, k3d ~25 MB,
helm ~20 MB - all cold-path only, silent on warm re-runs.
Pester: 205 passed / 0 failed locally. PSSA: 0 errors.
manifest.sha256 regenerated.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: shujaat hasan <shujaat@tracebloc.io>
* fix(installer): trust the corporate CA in the Docker daemon for k3d runtime image pulls (#474) (#475)
* fix(#474): detect host Docker daemon x509 at cluster-create + document daemon CA trust
k3d pulls its own runtime images (rancher/k3s, k3d-tools, k3d-proxy) with the
HOST Docker daemon, which doesn't use the in-node CA trust from #424. On a
TLS-inspecting network that pull can x509-fail during 'k3d cluster create',
before any node boots — so the post-create diagnosis never classifies it.
- bash: _host_ca_create_hint() detects x509 in the k3d create output and prints
a platform-aware remedy (Linux system trust store vs Docker Desktop VM);
wired into _create_new_cluster's failure path.
- PS: Write-HostCaCreateHint() mirrors it (Windows Trusted Root store), wired
before the generic create failure.
- docs/INSTALL.md: document trusting the CA in the daemon itself (Linux /
Docker Desktop).
- check-drift.sh: enforce both installers keep the host-CA hint (parity).
- Tests: bats (Linux/macOS branches + silent-on-no-x509) + Pester + drift.
Closes#474
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#474): distro-aware Linux CA remedy + actionable Docker Desktop for Linux (Bugbot)
- Linux native-Docker remedy now covers both Debian/Ubuntu (update-ca-certificates)
and RHEL/Fedora (update-ca-trust), not just the Debian path — the installer
supports RHEL hosts where the Debian commands fail.
- Docker Desktop for Linux now has an actionable step (trust in the system store,
restart Docker Desktop) instead of a dangling reference to a step only printed
on the macOS branch.
- docs/INSTALL.md updated to match. bats Linux test asserts both distro paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#474): cover Colima runtime in the macOS host-CA remedy (Bugbot)
Headless macOS installs use Colima (_install_docker_colima), a Lima VM that
does not read the macOS keychain — so the 'trust it in the keychain + restart
Docker Desktop' remedy was wrong for those hosts. The macOS branch now also
gives the Colima path (add the CA inside the VM via 'colima ssh', then
'colima restart'). docs/INSTALL.md + macOS bats test updated to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#474): isolate drift negative fixtures so a missing new token can't mask them (Bugbot)
Adding _host_ca_create_hint / Write-HostCaCreateHint as required _drift_ca_trust
tokens meant the older negative fixtures (missing registry-config, comment-only
registry-config) could pass just because the new token was also absent — so the
comment-strip case no longer uniquely proved comment-stripping still works. Each
negative fixture now carries ALL other required tokens and omits/comments only
the one under test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#474): surface host-CA hint on the PS create-timeout path too (Bugbot parity)
The PowerShell create-timeout branch exited via Err without calling
Write-HostCaCreateHint (and deleted the k3d logs first), so a TLS-inspected
host pull that logs x509 then hangs to the deadline gave Windows operators a
raw timeout with no certlm.msc CA guidance — while bash runs _host_ca_create_hint
on its timeout fall-through. Capture the full create output before deleting the
logs and call the hint before the timeout Err. Adds a parity regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#474): use a herestring in _host_ca_create_hint to survive pipefail (Asad)
printf '%s' "$out" | grep -qiE ... could swallow the hint under set -o pipefail:
grep -q closes the pipe on first match, so for output past the ~64KB pipe buffer
(reachable on the timeout path, which passes the full logs) printf takes SIGPIPE,
the pipeline exits non-zero, and `|| return 0` bails even though x509 matched.
Feed grep via a herestring (no pipe, no SIGPIPE). Adds a >64KB-under-pipefail
regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(install): Tier-1 k3d-on-rootless + cgroup delegation + autostart (#1221) (#478)
* feat(install): Tier-1 k3d-on-rootless + cgroup delegation + autostart (#1221)
Slice #1221 (RFC 0001 / #1177): make a rootless Tier-1 cluster actually usable,
all behind the opt-in TB_TIER1_ROOTLESS flag (default off until the spike's §5
host validation). With the flag unset every path below is a no-op and current
behavior is byte-for-byte unchanged.
- Shared _rootless_active predicate (common.sh) so cluster.sh + setup-linux.sh
can't drift on the flag pair.
- create_cluster targets the rootless socket (DOCKER_HOST); ensure_cluster_
autostart gets a user-scope branch (systemctl --user enable + loginctl
enable-linger, never `sudo systemctl enable docker`), and promises
reboot-survival only when BOTH succeed (honesty rule, #375/#458).
- cgroup v2 controller delegation drop-in (Delegate=cpu cpuset io memory pids):
privileged write + daemon-reload on root/sudo, or hand off to prepare-host
with the exact path+content when unprivileged. run_prepare_host writes it too
(system-wide -> covers the researcher).
- Carry-ins from #452/#458: scope-aware _configure_docker_proxy (user scope, no
sudo) so a proxy-only host's rootless daemon can pull rancher/k3s;
_set_tools_target installs user-space on rootless Tier 1 (no sudo-mv crash on
a true no-sudo host); persist DOCKER_HOST to the shell rc for new terminals.
14 new bats tests incl. flag-off regressions; shellcheck --severity=error clean;
manifest.sha256 regenerated (R8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): address Bugbot on #478 — rootless autostart seed + admin-touch msg
- ensure_cluster_autostart: don't seed TB_DOCKER_AUTOSTART from the SYSTEM
docker.service is-enabled check on the rootless path. The cluster runs on the
per-user rootless socket, so a system unit that happens to be enabled would seed
a false reboot promise the rootless branch then can't honestly retract. On
rootless the user-scope enable+linger are now the sole authority (Bugbot medium).
- install_rootless_docker: the TB_ROOTLESS_ADMIN_TOUCH success line no longer
hardcodes "subuid/subgid range" — _ensure_cgroup_delegation can set that flag
too, so it now names "host prerequisites (subuid/subgid range and/or cgroup
delegation)" (Bugbot low).
- Test: rootless + system docker.service enabled + user-enable fails => flag stays
0 (pins the seed-guard). manifest regenerated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(install): address Asad + Saqlain review on #478
- _persist_docker_host: key idempotency off our own marker, not a bare
'DOCKER_HOST=' probe. The old probe also matched a user's own DOCKER_HOST
(remote/TCP), so we silently skipped persisting the rootless socket and new
shells kept hitting the wrong daemon. Now: our own line -> idempotent; a foreign
DOCKER_HOST -> left untouched + a warn to repoint it (Asad #2 + Bugbot #478, Medium).
- ensure_cluster_autostart: reset TB_DOCKER_AUTOSTART=0 in the rootless else-branch
(defensive; the is-enabled seed is already guarded off the rootless path) so the
honesty guarantee is local to the branch (Asad #1).
- install_rootless_docker: success line now reads "one or more one-time admin steps"
so it doesn't undercount when both the subuid and cgroup touches happen (Saqlain #1).
- Test: foreign DOCKER_HOST -> warns, no clobber, no double-write. manifest regen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(installer): failures surface the real error + log path + support-bundle hint (#423) (#476)
* fix(#423): failures surface the real error + log path + support-bundle hint
Fatal errors printed a generic red line while the actionable detail (k3d/helm
stderr) went only to the transcript, and the log path itself was never shown on
screen. Now:
- Err gains an optional $Detail param; Get-ErrDetailLines (pure, unit-tested)
renders the last ~5 non-empty output lines + the log path + a '-Diagnose'
next-step hint, appended to EVERY fatal error.
- Cluster-create failure passes k3d's stdout/stderr so the real reason (image
pull / proxy / port / WSL) shows on screen — the motivating case.
- Helm repo-add / reconcile / install failures pass helm's output via $Detail
instead of embedding it (no more duplicated log-path text).
- Install log path is announced up front in the banner (was log-only before).
Closes#423
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#423): force array enumeration of Err detail lines (Bugbot, defensive)
Bugbot flagged that a single-line Get-ErrDetailLines return (no detail + no
LOG_FILE, e.g. a Confirm-Config failure before Start-InstallLog) unwraps to a
scalar string. The foreach statement already iterates a scalar once (verified:
it prints the whole line, not per-character), so the reported char-splitting
does not reproduce -- but wrap the enumeration in @(...) to make that
unambiguous and future-proof. Adds a regression test asserting the single-line
case stays one intact line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#423): strip PS 5.1 ErrorRecord chrome from the failure excerpt (Bugbot)
helm failures arrive as `native 2>&1 | Out-String`; on Windows PowerShell 5.1
that wraps stderr in ErrorRecord chrome (the `At <file>:<n> char:<n>` position
line plus the `+ ...` / `+ CategoryInfo` / `+ FullyQualifiedErrorId` block).
Get-ErrDetailLines kept only the last 5 non-empty lines, so the excerpt was all
chrome and the real `Error:` line dropped out -- a regression from the previous
full-message dump. Filter those chrome lines before taking the window so the
actual error survives. Adds a regression test simulating the 5.1 rendering.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#423): drop duplicate inline log-path hints (Bugbot)
Err now always prints the log path via Get-ErrDetailLines, so the k3d
spawn-failure and create-timeout paths that still Hint "Full log:" right before
Err printed it twice. Remove those inline hints; Err is the single source. Adds
a guard test asserting no inline 'Full log:' hints remain in the installer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#423): put stderr last in the create-failure Err detail (Asad)
Get-ErrDetailLines keeps the LAST 5 non-empty lines, so with detail ordered
stderr-then-stdout any k3d stdout tail could crowd the real stderr reason
(FATA/x509/port) out of the excerpt. Order it stdout-then-stderr so the stderr
tail survives the window; also matches the Write-HostCaCreateHint order just
above.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Syed Is Saqlain <saqlain.syed007@gmail.com>
Co-authored-by: Syed Saqlain <syedsaqlain@MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Arturo Peroni <arturo@tracebloc.io>
Co-authored-by: shujaat_tracebloc <153823837+shujaatTracebloc@users.noreply.github.com>
Co-authored-by: shujaat hasan <shujaat@tracebloc.io>
Co-authored-by: tracebloc-release-train[bot] <309815517+tracebloc-release-train[bot]@users.noreply.github.com>
* ci(1606): standard-checks and helm-ci call the Makefile instead of copying it
CI parity. Two jobs restated targets the Makefile already declares -- and one
of them had ALREADY DRIFTED, in the direction nobody notices.
LINT. This job spelled out the shellcheck file list inline while the Makefile
kept the same list in SHELLCHECK_FILES. Measured: the Makefile carries 19
entries, this file carried 9. Ten scripts were shellchecked on a contributor
machine and NOT at the merge gate:
gen-manifest.sh check-facts.sh check-style.sh lib/*.sh
tests/check-drift.sh tests/e2e-full-seal.sh tests/e2e-journey.sh
tests/path-persist.sh tests/chart-env-vocabulary.sh
tests/env-vocabulary-agreement.sh
gen-manifest.sh is the installer integrity-manifest generator, so a shell
defect there could not be caught by this gate. `make lint` is green across all
19 on this tree, so arming the full list imports no backlog.
HELM LINT. The values-file loop, `helm lint --strict ./ingestor` and both
vocabulary scripts were verbatim copies. `env-vocabulary-agreement.sh` exists
to prove the four CLIENT_ENV declarations agree with each other (backend#1729
sweep 5) -- a check about "these declarations must not drift" being itself
declared twice is the joke version, and a third vocabulary script added to the
Makefile alone would leave this gate silently not running it.
The apt install of shellcheck stays: it bootstraps the runner, it is not a
duplicated command.
Job names untouched. `Lint` and `Unit tests` are required status checks on
main, matched by name.
Verified: make lint, make helm-lint and make helm-vocab all exit 0 on this
tree; actionlint clean on both files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Asad Iqbal (Saadi) <asad.dsoft@gmail.com>
Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: shujaat_tracebloc <153823837+shujaatTracebloc@users.noreply.github.com>
Co-authored-by: Arturo Peroni <arturo@tracebloc.io>
Co-authored-by: tracebloc-release-train[bot] <309815517+tracebloc-release-train[bot]@users.noreply.github.com>
Co-authored-by: Syed Is Saqlain <saqlain.syed007@gmail.com>
Co-authored-by: Syed Saqlain <syedsaqlain@MacBook-Pro.local>
Co-authored-by: shujaat hasan <shujaat@tracebloc.io>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@LukasWodka@saqlainsyed007@divyasinghds