Skip to content

Release develop → main#397

Open
shujaatTracebloc wants to merge 14 commits into
mainfrom
develop
Open

Release develop → main#397
shujaatTracebloc wants to merge 14 commits into
mainfrom
develop

Conversation

@shujaatTracebloc

@shujaatTracebloc shujaatTracebloc commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Promotion: develop → main

This release promotes 7 commits from develop to main (prod). No staging branch exists, so develop promotes straight to prod.

Commits being promoted

Highlights

  • New client status --seal verdict and tracebloc prepare-host / tracebloc upgrade commands
  • Install now prefers ~/bin when already on PATH; auto-update nudge added
  • Fixes to push progress rendering and delete-wipe verification

Note

Medium Risk
Touches offboarding data removal, privileged installer execution, and cluster helm tests used for security claims; changes are well-tested but affect production CLI behavior and messaging.

Overview
This promotion bundles CLI product work (F1 auto-update, RFC-0003 seal/offboard hygiene) with UX fixes on home, doctor, and resources.

New capabilities: tracebloc client status --seal runs the chart’s conformance helm tests and reports sealed / unsealed / unknown with documented exit codes; tracebloc upgrade and tracebloc prepare-host delegate to the cosign-verified installer (download-then-run, TLS-pinned). After each command, main may print a once-a-day “newer release” nudge (skipped for upgrade/delete and when offboard wiped config). The HTTP 426 message now points at tracebloc upgrade.

Trust & hygiene: tracebloc delete stats the host data dir after RemoveAll before claiming a clean wipe. Update-check cache writes do not recreate ~/.tracebloc after delete.

Diagnostics & UX: Home adopts a local cluster when the active-client pointer is empty (#401, Windows tb.cmd); machine capacity uses the largest Ready node instead of summing k3d nodes (#399). doctor compute remedies are OS-specific (#400). The resources wizard clamps defaults and flags budgets above what the machine can offer (#398).

Docs: Adds RFC-0003 (secure environment, storage, offboard, seal matrix) and updates troubleshooting exit codes for --seal. Golden copy catalogs cover seal verdicts, upgrade, and prepare-host.

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

saadqbal and others added 7 commits July 23, 2026 15:36
…389)

* fix(delete): verify the host-data wipe before printing ✔

removeHostDataDir did os.RemoveAll and returned nil without confirming the
tree was actually gone; the caller then printed "✔ Removed local tracebloc
data and config". A nil RemoveAll is not proof of absence (racing writer,
mount, masked partial failure), so offboard could claim a clean slate it
didn't achieve — the RFC-0003 offboard-hygiene gap.

Now removeHostDataDir stats the dir after RemoveAll and treats "still
present" (or an unexpected stat error) as a failure, so the caller prints
the warn + manual-rm hint instead of ✔. Adds an osStat seam + a test
proving delete does NOT claim success on an unverified wipe.

Closes #388. Refs tracebloc/client#367, backend#1151, #366.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(delete): regenerate copy-catalog golden for the two verify strings

The wipe-verify error messages are user-visible (surfaced via the "Couldn't
remove local data (%v)" warn), so zz-all-strings.golden picks them up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): auto-update — check-and-nudge + `tracebloc upgrade` (F1)

Customers run the installer once and then never again, so they silently sit on
an old CLI. Three layers, all safe (no silent binary swap):

- Update-check nudge: after any command, a quiet one-liner if a newer release
  exists. Throttled to once/day via a cache in ~/.tracebloc, network capped at
  2s, best-effort. Silent on dev builds, off a terminal, in CI, or with
  TRACEBLOC_NO_UPDATE_CHECK. Nudges from cache; refreshes when stale.
- `tracebloc upgrade`: the apply step. Re-runs the official installer
  (curl … | bash) so it reuses the existing cosign verification and upgrades
  the CLI + the secure environment together (no version skew) — no new download/
  verify surface in the CLI.
- The existing 426 "CLI too old" error now points at `tracebloc upgrade`.

Tests cover the semver compare (incl. v-prefix + pre-release), cache round-trip,
the GitHub fetch (httptest), fresh-vs-stale cache behavior, and the skip gates.
Catalog: adds 11-upgrade.golden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): resolve Bugbot findings on auto-update (F1)

Three medium-severity issues from Cursor Bugbot:

1. Offline throttle broken (update_check.go): on a failed fetch,
   latestReleaseVersion fell back to the stale cache but never re-stamped
   CheckedAt, so the cache stayed expired and every command re-hit the
   network and ate the 2s timeout while offline. Now re-stamps CheckedAt so
   the once-per-interval throttle actually holds.

2. Nudge fired after a successful `tracebloc upgrade` (main.go): the nudge
   used the running process's compile-time version, which is stale by design
   once upgrade swaps the binary — so it claimed a newer release existed
   right after the user installed it. main now captures the executed command
   via ExecuteContextC and skips the nudge for `upgrade`.

3. `upgrade` broken on Windows (upgrade.go): it hardcoded `bash i.sh`, but
   Windows is a shipped platform (install.ps1, windows/* build matrix) with
   no bash — while the new 426 message tells users to run `upgrade`. Now
   branches on GOOS to run install.ps1 via PowerShell on Windows.

Adds regression tests: offline-fetch throttle refresh, per-OS upgrade
command, and the upgrade nudge-skip detector.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): resolve Bugbot HIGH findings on upgrade command

Round-2 Bugbot review of the auto-update work surfaced 3 real HIGH issues,
all verified against scripts/install.ps1:

1. Upgrade hid curl failures: `curl … | bash` ran under `bash -c` without
   pipefail, so a failed curl left the trailing `bash` exiting 0 on empty
   stdin and `upgrade` reported success having installed nothing. Now runs
   with `bash -o pipefail -c`.

2/3. Windows self-upgrade can't work: install.ps1 is CLI-only (no environment
   upgrade) and Move-Items the binary into place, which Windows blocks for a
   running .exe. Proper Windows self-update is a separate feature, so instead
   of pretending, `upgrade` on Windows now prints the documented manual
   command to run in a fresh shell (no tracebloc process holding the binary).
   Help/home/copy no longer promise CLI+environment parity on Windows.

Refactors the per-OS branch into upgradePlanFor(goos) (exec on Unix, guide on
Windows) so it's testable on any host. Regenerates copy-catalog goldens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
The staging progress bar was built with RenderBlankState(true), so
schollz painted a 0% bar at construction time — before Stage prints its
setup lines ("Opened a secure channel…", "Preparing the copy…") and
before the up-to-5-minute pod-ready wait. Two problems:

  1. The 0% bar sat frozen through the pod-ready wait, reading as
     "stuck".
  2. Those setup lines (plain \n writes) collided with the bar's \r
     redraw on the same terminal line, producing garbled output like
     "Copying x 0% |…| (0 B/53 kB) [0s:0s]Opened a secure channel…".

Flip to RenderBlankState(false): the bar first paints on the first
Add() inside StreamLayout, after every setup line has printed on its own
clean line. Better UX too — no misleading frozen bar during the
pod-ready wait. No copy strings change; catalog golden unaffected.

Backlog item: D3.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(install): prefer ~/bin when already on PATH, for same-terminal use (RFC 0001 B2)

When /usr/local/bin is not writable, the installer fell straight to
~/.local/bin, which is often not on the current shell PATH — so tracebloc was
not resolvable until a new terminal or an rc reload. Now, before that fallback,
prefer ~/bin when it already exists on $PATH and is writable: the binary is
usable in this shell and every new one with no rc edit at all. Restricted to the
conventional ~/bin (never a language-specific dir like ~/.cargo/bin that merely
happens to be on PATH); the ~/.local/bin fallback + rc persistence are unchanged.
POSIX sh; shellcheck --severity=warning + dash -n clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(install): ~/bin already on PATH => no rc edit, no "new terminal" nudge (Bugbot #392)

When the installer prefers ~/bin because it is ALREADY on $PATH, the binary is
usable immediately and in the user's shells (they configured ~/bin), so the
persist step must not rewrite their rc or tell them to open a new terminal — that
undercut the B2 goal. Flag that selection (PREFIX_PRESELECTED_ON_PATH) and skip
persist for it, same clean no-message outcome as an on-PATH /usr/local/bin. The
~/.local/bin fallback (created mid-session, needs the rc line for non-login
shells, #304) is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(install): ~/bin still persists to rc (session-only PATH), but message says "ready now" (Bugbot #392 r2)

My earlier skip-persist for ~/bin broke a SESSION-ONLY $PATH entry (direnv, a
one-off export): those are not in the rc, so new terminals lost tracebloc with no
guidance. Revert to always persisting a $HOME prefix (idempotent — a no-op when
the rc already has it), so new terminals are covered. To still honour B2 (do not
nag "open a new terminal" for a dir usable NOW), the message branches on whether
$PREFIX is on the current $PATH: "ready to use now" (+ note the rc was updated for
new terminals) instead of "open a new terminal".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(install): guard HOME=/ for ~/bin, and say ready-now on rc-write failure too (Bugbot #392 r3)

1) "${HOME%/}/bin" collapses to /bin when HOME is "/" or empty, so a root
   process could drop the CLI into /bin — require a real, non-root $HOME before
   preferring ~/bin.
2) The on_path "ready now" acknowledgement was applied to the added/present
   messages but not the rc-write-failure branch; a ~/bin (usable now) whose rc
   could not be written still nagged "open a new terminal". Add it there too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* install.sh: tolerate trailing-slash PATH entries when detecting ~/bin

The PATH membership checks matched only ":$dir:" and missed a
trailing-slash entry like "$HOME/bin/". A user with "~/bin/" on PATH would
be wrongly classified as not-on-PATH: the installer would skip the usable
~/bin prefix and fall back to ~/.local/bin, and would nag "open a new
terminal" for a dir that is in fact already on PATH (Bugbot #392).

Add a "|*":$dir/:"*" alternative to all three checks (home_bin detection,
persist decision, on_path message).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): tracebloc prepare-host — one-time admin step wrapper (RFC 0001 #1178)

Adds `tracebloc prepare-host`: a thin, discoverable wrapper that re-runs the
official installer's verified prepare-host step (curl … | bash -s -- prepare-host),
exactly like `tracebloc upgrade` delegates to the installer rather than
re-implementing privileged host prep in the CLI. Registered in root; copy catalog
gains 11-prepare-host.golden + the strings flow into zz-all-strings. build/vet/
staticcheck/deadcode clean; catalog no-drift; gofmt clean.

Pairs with the client installer prepare-host step (tracebloc/client#377).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): set -o pipefail in prepare-host so a curl failure is not swallowed (Bugbot #394)

Without pipefail, `curl … | bash -s -- prepare-host` under `bash -c` exits 0 when
curl fails (bash reads empty stdin), so the command reported success while
prepare-host never ran. Prepend `set -o pipefail;` so curl's non-zero propagates
and c.Run() surfaces the failure. Regression guard added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* prepare-host: run installer pipeline in its own process group

Cancelling the context (Ctrl-C / parent shutdown) previously killed only
the top-level `bash -c`, leaving the `curl` and the `bash -s` prepare-host
child -- which performs privileged host prep -- running detached after the
CLI had already reported failure and exited (Bugbot #394).

Extract prepareHostCmd(ctx): set SysProcAttr.Setpgid so the pipeline gets
its own process group, and a Cancel that group-signals (kill -PGID SIGINT)
so the whole pipeline stops when the user aborts. Add a test asserting
Setpgid + Cancel are wired.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* prepare-host: run installer from a temp file, not a pipe (keep stdin on TTY)

`curl | bash -s -- prepare-host` makes the inner bash read its *program*
from the pipe, so the installer's stdin is no longer the terminal — any
interactive prompt in prepare-host (e.g. which non-admin user gets runtime
access) gets EOF (Bugbot #394, second finding).

Switching to `bash <(curl …)` would fix stdin but reopen the FIRST #394
finding: process-substitution exit codes bypass pipefail, so a failed curl
would silently run nothing. Instead download to a temp file under `set -e`
(so a failed `curl -o` aborts) and run the file (stdin stays on the TTY).
Best of both: fail-closed on download error AND interactive-capable.

The manual-run hint shown on failure uses `bash <(curl …)` — the repo's
recommended idiom — since a human re-running it keeps their own TTY.

Add tests: fail-closed on download error (set -e + curl -o), and never pipe
the script into bash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* prepare-host: fix Windows build — move POSIX process-group logic behind build tags

The process-group cancel added for Bugbot #394 used syscall.SysProcAttr.Setpgid
and syscall.Kill(-pid, …), which are POSIX-only — the Windows build failed to
compile (unknown field Setpgid; undefined syscall.Kill).

Extract configureProcessGroup into build-tagged files:
- prepare_host_unix.go (//go:build !windows): sets Setpgid + the group-killing
  Cancel (unchanged behavior on linux/darwin).
- prepare_host_windows.go (//go:build windows): no-op — POSIX process groups
  don't exist there, and exec.CommandContext still kills the top-level process;
  prepare-host is a Linux host op regardless.
Move the Setpgid assertion test into prepare_host_unix_test.go (also
!windows-tagged) so `go test`/`go vet` compile on Windows too.

Verified: `GOOS=windows go build/vet ./...` now pass for amd64 + arm64, and the
native build/test/staticcheck stay green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* prepare-host: run in the foreground group, quiet interrupt on cancel (Bugbot #394)

The process-group approach was wrong for an interactive command and Bugbot
flagged five follow-ups:
- Setpgid put the installer in its own (background) process group while stdin
  was the TTY, so any prepare-host prompt got SIGTTIN and hung (High).
- the SIGINT-only Cancel with no WaitDelay could hang Wait forever if a
  privileged child ignored the signal, re-opening the orphaned-work risk.
- a user Ctrl-C was wrapped as "prepare-host didn't complete — retry" instead
  of a quiet interrupt.

Drop Setpgid and the custom Cancel entirely: keep the installer in the CLI's
foreground process group so prompts work and a terminal Ctrl-C signals the whole
pipeline (bash + curl + child) at once. Add WaitDelay=5s so a programmatic
cancel can't hang Wait. Treat ctx cancel as exitInterrupted (130), matching the
other cancellable paths. This also removes all syscall usage, so the Windows
build no longer needs the build-tagged split (files deleted).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* prepare-host: reuse installCmd for the hint + robust interrupt detection (Bugbot #394)

- prepareHostManualHint duplicated the bootstrap idiom owned by installCmd
  (doctor.go), so a URL/idiom change could leave the prepare-host fallback
  stale. Build it from installCmd + " prepare-host" (same value, one source).
- Interrupt detection only checked ctx.Err() after c.Run(); on a terminal
  Ctrl-C the child dies (bash exits 130) and Run can return before
  NotifyContext flips ctx.Err(), so an abort was mis-reported as a failed
  install. Add prepareHostInterrupted(ctx, err): interrupt if ctx cancelled OR
  the run exited 130 (128+SIGINT). Unit-tested (cancelled ctx, exit 130, exit 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* prepare-host: accept a researcher username to grant docker-group access (Divya #377)

prepare-host was cobra.NoArgs with no flag and no mention of TB_PREPARE_USER,
so an admin had no discoverable way to name the researcher — the installer's
docker-group grant (which reads TB_PREPARE_USER) was always skipped and the
feature couldn't deliver Tier-0 access end-to-end.

Add an optional positional arg: `tracebloc prepare-host <researcher-username>`
(MaximumNArgs(1)). The username is validated (Linux-username shape) and passed
to the installer via the TB_PREPARE_USER environment variable (not the command
string, so it can't be shell-interpreted; the installer quotes it for usermod).
Help text explains the arg and stresses it's the researcher, not the admin.
Without it, prepare-host installs prereqs only and prints how to grant access.

The client installer already grants ONLY TB_PREPARE_USER (never $SUDO_USER), so
this closes the loop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cli): guard prepare-host on Windows (Bugbot #394)

prepare-host shells out to bash/curl/mktemp and readies a Linux server / HPC
login node (container runtime, docker group) — a Unix-only concept. On Windows
it appeared in --help then failed with a cryptic missing-bash error and a
Unix-only retry hint. It now stops early with a clear explanation (a
no-op-with-message), mirroring upgrade's Windows handling. Adds a regression
test for the OS guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
…atus --seal) (#395)

* feat(status): surface the environment's seal-check verdict (client status --seal)

`tracebloc client status --seal` runs the chart's conformance checks (its
helm-test hooks — the RFC-0003 §8.2 seal check) against this machine's
secure environment and prints an honest verdict with per-check detail:

  sealed     every conformance check passed                    (exit 0)
  unsealed   a check failed — a protection is not enforced     (exit 2)
  unknown    the chart ships no checks; nothing was verified   (exit 2)

Chart contract (works against today's probes, picks up the growing
backend#1184 suite as it lands): test hooks labelled
`tracebloc.io/seal-check: "true"` form the suite; with no labelled hook
every helm test runs, with a visible fallback note; the optional
`tracebloc.io/seal-name` / `tracebloc.io/seal-hint` annotations refine a
check's display name and its failure hint. Checks run one
`helm test --filter name=<hook>` at a time so a first failure can't hide
the rest of the suite's state, and helm stays pinned to the resolved
kubeconfig/context — never the ambient one.

Honest-output rules (the #389 spirit): only a fully-passed suite exits 0;
"unknown" is never worded as sealed; a cancelled run (Ctrl-C) and a failed
hook enumeration yield no verdict at all.

Also: moves the status command into client_status.go (client.go sat at its
1050-line file-budget ceiling) and adds Printer.Spinner strings to the
copy-catalog harvest (they print as static lines on non-TTY runs and were
previously missed by the backstop).

Closes #393

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(troubleshooting): add client status --seal to the exit-code table

The table is the cross-command scripting contract; the new seal mode
produces 2 (unsealed / unknown via exitChecksFailed) and shares the
3 / 4 cluster-resolution codes with the data and resources commands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(seal): align to the chart's shipped label contract; carry aux hooks in every filter

Two alignments against the chart-side suite (client docs/SEAL-CHECK.md,
backend#1184) that landed in parallel:

- The per-check identifier is the tracebloc.io/seal-check-name LABEL
  (values: egress-enforcement, backend-reachability, storage-assertions),
  not a seal-name annotation — read it from labels. The hint stays an
  optional CLI-side annotation (labels cannot carry sentences); absent, the
  kubectl-logs fallback stands.

- The storage-assertions Job depends on a ServiceAccount/RBAC that are
  themselves test hooks at negative hook-weight, and helm's --filter
  excludes every unlisted test hook — plumbing included. Filtering to the
  check alone would strand the Job without its SA and report a false
  Unsealed. Every per-check run now lists the check PLUS the release's
  non-runnable test hooks (applied instantly; helm only waits on
  Jobs/Pods), while other checks stay excluded so the verdict remains
  per-check. Non-runnable hooks never count as checks: a chart whose only
  test hooks are plumbing is still honestly "unknown".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(seal): pin helm to the resolved kube-context; quiet exit on Ctrl-C during enumeration (Bugbot)

- helm.TestTarget.KubeContext now carries target.Resolved.Context, not the
  raw --context flag: with the flag omitted the raw value is empty and helm
  falls back to its own ambient resolution ($HELM_KUBECONTEXT included),
  which can diverge from the context client-go discovery just used. Same
  pinning `resources set` applies.

- A context cancellation during `helm get hooks` now exits 130 quietly
  (like the per-check loop and `status --wait`) instead of reporting a hard
  enumeration failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
* docs(rfc): add RFC-0003 — dataset storage & offboard hygiene (draft)

Commits the drafted RFC-0003 into cli/docs/rfcs/ alongside 0001/0002. Concept-only
design doc covering two user-visible problems that share one root cause: offboard
should leave a true clean slate, and ingested datasets are stored as world-readable
host files bind-mounted into the cluster (engineered to survive cluster deletion),
which sits awkwardly against "your data stays within the secure environment".
Storage options + open decisions (section 7) pending Lukas + Asad.

Companion to backend#1151.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(rfc): RFC-0003 v2 — record decisions, define the secure-environment boundary

- §7 decision menu -> §10 decision log (D1-D14): Option C node-local,
  leftover-guard, no data-copier, 777 dies with C, encryption phase 2
- New §1-2: secure-environment definition (3-in/1-out channel list) +
  threat model with honest ceilings (owner-root, data-derived weights)
- New §6: model IP protection — watermarking + audit now, envelope
  encryption + crypto-shredding for at-rest weights (keys in memory,
  not weight files), TEE as phase 2, HE/MPC rejected
- New §7: in-cluster walls — dataset-scoped mounts, per-experiment DB
  grants, spawned-pod hardening completion
- New §8: egress-lockdown flip dependency, seal check, per-substrate
  guarantee matrix, verify k3d enforcement
- §3/§13 evidence corrected + refreshed (777 scoping, cluster reuse,
  stale line numbers)
- Open items: O1 default-flip, O2 retention, O3 key custody, O4 watermark

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(rfc): v2.1 — correct weight-lifecycle facts against code; record D15

- §3.1/§6.4: verified against tracebloc-client + client-runtime +
  averaging-service — weights do NOT rest durably in the environment
  (per-cycle backend download -> pod-scoped scratch -> upload back);
  durable store is the platform-side averaging share
- D8 re-aimed: edge = lifecycle hygiene only; envelope encryption +
  crypto-shredding applies to the platform-side store (backend/averaging
  ticket, outside the environment boundary)
- O1 decided -> D15: node-local becomes the default for local installs
  after one green training run on node-local
- O3 dissolved by architecture; O2 re-scoped to platform-side retention
- Evidence appendix: added weight-lifecycle code anchors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(rfc): v2.2 — status DECIDED; cross-link execution tickets in §10/§12

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(rfc): v2.3 — per-dataset immutable tables + grant-scoped view isolation; split composition to data-spaces RFC

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(rfc): C1 pins single-node via AGENTS=0 AND SERVERS=1, not just AGENTS=0

§5 C1 and the appendix said node-local forces AGENTS=0; the prototype
(client#368) had to force SERVERS=1 too — k3s server nodes are
schedulable, so SERVERS>1 still yields multiple nodes a Job could land
on away from the local-path volume. Match the doc to the shipped code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(rfc): fix stale code citations in RFC-0003

- Repo rename: the training-client code is now in `tracebloc-engine`,
  not `tracebloc-client` (§7bis, O6, appendix). Verified the cited
  paths exist there: core/utils/database.py:243 (get_sql_query_and_params),
  core/utils/general.py:21-33 (get_experiment_path), core/weights/base.py.
- D19 line drift: the ingestor reuse/append branch spans 275-357, not
  309-357 — the old range started after the "return existing table if
  already created" reuse check (line 275) that D19 actually removes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Asad Iqbal <asad.dsoft@gmail.com>
Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
@shujaatTracebloc shujaatTracebloc self-assigned this Jul 24, 2026
Comment thread internal/cli/upgrade.go
Comment thread internal/cli/upgrade.go
- upgrade: reuse prepareHostInstallerCmd / installerURL (download-then-exec)
  instead of curl|bash, so stdin stays on the TTY and the URL has one source
- upgrade: treat cancelled context / bash exit 130 as a quiet interrupt (exit 130),
  matching prepare-host

Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread internal/cli/update_check.go
Comment thread internal/cli/prepare_host.go
… (#403)

On a machine that shrank under the configured ceiling (WSL2 field case:
node ~6.7 GiB under a lingering chart-default 8Gi RESOURCE_LIMITS), the
'Choose an amount' prompts offered the current ceiling as the default —
outside their own validator range — so pressing Enter on '(8)' answered
'must be between 2 and 3'. Defaults are now clamped into [floor, max],
and the header annotates an over-ceiling budget instead of printing a
bare '8 of 6.7 GiB'.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread cmd/tracebloc/main.go
shujaatTracebloc and others added 3 commits July 24, 2026 16:48
#404)

* fix: resolve Bugbot findings on promotion PR #397 (TLS floor + delete cache)

- installer: pin the TLS 1.2 floor (--tlsv1.2) on the shared installerRunScript
  download, matching scripts/install.sh, so `tracebloc upgrade` and
  `prepare-host` can never negotiate a weaker protocol
- update-check: skip the post-command nudge after `tracebloc delete` (reuse
  SkipUpdateNudge) AND make writeUpdateCache refuse to recreate a missing
  ~/.tracebloc, so a clean offboard is never resurrected by the throttle cache

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: drive update-nudge skip by annotation, not command name (Bugbot #404)

isDeleteCommand matched any leaf named "delete", so the nudge was wrongly
skipped after `data delete` too (which neither offboards nor wipes ~/.tracebloc).
Replace the name-based upgrade/delete predicates with a self-declared cobra
annotation (skipUpdateNudgeAnnotation) set on the top-level upgrade and delete
commands, so `data delete` is unaffected. Add a table-guard test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…400) (#405)

* fix(doctor): per-OS compute remedies + resources-set-max drift nudge (#400)

The 'raise the machine's allocation in Docker Desktop -> Resources' remedy
was emitted unconditionally -- that slider does not exist on Windows's
default WSL2 backend (field case: the user followed it into Docker Desktop
and found nothing to raise), and bare Linux has no Docker Desktop at all.

- computeRemedy(GOOS): windows names both levers (.wslconfig + wsl
  --shutdown / Hyper-V slider), darwin keeps the slider, linux drops the
  Docker Desktop reference; every variant ends with 'tracebloc resources
  set max' (the backend#1236 drift fix). Applied to both the node-capacity
  fail and the stuck-pending remedy.
- checkNodeFit OK path gains the grow-side drift nudge: when the budget
  uses no more than half of what the largest node could give one run,
  the detail points at resources set max.
- copy-catalog golden regenerated for the new strings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* remedies: the memory slider lives at Settings -> Resources -> Advanced

Verified against Docker's settings docs (review question by Lukas on the
client twin, tracebloc/client#392): 'Memory limit ... Mac, Linux, Windows
Hyper-V' sits under Resources > Advanced. Golden regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* drift nudge: CPU-major node selection, matching resources set max (Bugbot)

The nudge tracked the largest node memory-first while resources.
LargestReadyNode (what 'set max' applies) is CPU-major -- on heterogeneous
clusters the advertised ceiling could name numbers set max would not
apply. Same total order now; heterogeneous regression test added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…-node sum (#399) (#406)

The default k3d topology is server-0 + agent-0 -- two kubernetes nodes on
the SAME physical machine -- so summing allocatable across Ready nodes
double-counted it: 'equipped with 24 CPU · 13.4 GiB' on a 12-CPU node
exposing 6.7 GiB (field case). A pod schedules onto ONE node anyway.

- resources.MachineCapacity now delegates to LargestReadyNode; every
  surface (headline, wizard, doctor checkNodeFit) quotes the same machine.
- home.go's sumCapacity becomes the same largest-node selection (CPU-major,
  mirroring nodeLarger's total order); GPU count comes from the chosen node.
- addInto removed (orphaned by the delegation); regression tests on all
  three pinned paths.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 327987b. Configure here.

Comment thread internal/cli/seal.go
shujaatTracebloc and others added 2 commits July 24, 2026 17:15
…397) (#408)

The seal check's cancel detection looked only at ctx.Err(). On a terminal
Ctrl-C the helm child can exit 130 before NotifyContext flips the context, so
the check read as a real failure and rendered a false Unsealed (exit 2) instead
of a quiet interrupt (130) — breaking seal's "never claim a verdict you didn't
verify" rule. Reuse installerRunInterrupted (shared with upgrade/prepare-host)
at both seal cancel sites (mid-suite and enumeration) so the exit-130 race is
caught. Adds a regression test.

Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… missing (#401) (#407)

* fix(home): adopt a LOCAL cluster's release when the client pointer is missing (#401)

Home's env verdict hung entirely on ActiveClientNamespace -- written only
by 'client create', which the Windows installer never runs -- so a healthy
installed environment read as 'No secure environment on this machine yet'
while doctor said Ready (field case). The ownership gate stays intact:
the fallback adopts a discovered release ONLY when the kubeconfig server
is local (loopback / k3d wildcard / host.docker.internal) -- a cluster
that IS this machine, so no shared-cluster stranger can be greeted
(section 7.5 preserved); namespace-only discovery, no cluster scan; every
error degrades to the honest no-release.

Also: tb alias detection accepts the Windows tb.cmd shim (install.ps1
cannot symlink without admin), so remedies echo 'tb' on Windows too.
Both moved to home_local_fallback.go (home.go file budget).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* tb.cmd ownership: full-path match, not basename (Bugbot)

A shim mentioning 'tracebloc' anywhere -- a comment, or an invocation of
a DIFFERENT tracebloc install -- claimed ownership. Ours = the shim
contains THIS exe's full path (case-insensitive; install.ps1 writes an
absolute target, the same bar aliasStatus applies to symlinks). Tests
for both false-claim shapes added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to 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