Skip to content

Release develop → main - #397

Merged
saadqbal merged 19 commits into
mainfrom
develop
Jul 27, 2026
Merged

Release develop → main#397
saadqbal merged 19 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 frozen exit-code semantics, offboard wipe honesty, and new cluster/installer shell-outs; behavior is heavily tested but scripts and field installs should re-verify seal and delete paths after upgrade.

Overview
This is a develop → main promotion bundling CLI features, hardening, and docs from the listed commits.

New commands and flows: tracebloc client status --seal runs the chart’s conformance (helm test) suite and reports sealed / unsealed / unknown with exit codes 0 / 2 (and cluster errors 3 / 4), without claiming sealed when nothing was verified. tracebloc upgrade and tracebloc prepare-host delegate to the cosign-verified installer via download-then-bash (TLS 1.2, fail-closed on curl errors, foreground TTY for prompts). main now calls MaybeNotifyUpdate after commands (skipped for upgrade and top-level delete via annotation).

Scripting and UX contract: Interactive Ctrl-C and “no” paths funnel through cleanCancel / mapPromptErr so users always see Cancelled — … on exit 0; in-flight interrupts stay exitInterrupted (130). delete **stat**s after RemoveAll before printing a successful wipe. 426 messages now point at tracebloc upgrade.

Home / doctor / resources: Local kubeconfig fallback when the active-client pointer is empty (loopback-only); home capacity uses the largest Ready node instead of summing k3d nodes; tb.cmd shim detection on Windows; OS-specific doctor compute remedies; resources wizard clamps defaults when the machine shrank under the configured ceiling.

Docs / review: Adds .cursor/BUGBOT.md, expands RFC-CLI-0003, PR/CONTRIBUTING cross-repo close guidance, troubleshooting exit-code table updates, and copy-catalog goldens for seal/upgrade/prepare-host.

Reviewed by Cursor Bugbot for commit 25db81d. 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>
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>
LukasWodka and others added 4 commits July 27, 2026 09:59
…, a visible note (#410)

Ctrl-C at `client create`'s "Provision this client?" confirm and at
`delete`'s typed-name confirmation exited 0 having printed nothing about
it: `mapClientErr` mapped errInteractiveCancelled straight to nil with no
Printer call. A script could not tell an aborted run from a completed one,
and the declined-answer branch sitting directly beside each one DID print.

Centralise the convention instead of duplicating it a fifth and sixth time:

- `cleanCancel(p, nothing, …)` is now the only place a cancellation is
  reported — it prints "Cancelled — <nothing>." and returns the clean exit.
- `mapClientErr` becomes `mapPromptErr(p, err, nothing, …)`: the Printer and
  the note are in the signature, so the silent-return shape is unreachable.
  Non-cancel errors still map to exit 1.
- All six prompt sites route their cancellation through it, including the
  declined-answer twins, so Ctrl-C and "no" cannot drift apart. Output is
  byte-identical at the four sites that were already correct; `client
  create`'s terse "Cancelled." becomes "Cancelled — nothing was
  provisioned."

Exit code stays 0, which is what all six sites already did and what
exitOK documents. exitInterrupted (130) is used only for a Ctrl-C that cuts
short work already in flight (sign-in wait, status --wait, the seal suite,
an installer re-run) — its comment claimed the prompt case too,
contradicting exitOK and every call site, so fix that and the matching row
in docs/troubleshooting.md.

Tests: cancel_test.go is a table over every prompt a user can back out of,
asserting the exit code, the user-visible line, and that the command did
not act anyway. The copy-catalog harvester learns about the new copy helper
so the assembled "Cancelled — …" lines stay in the catalog.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Cursor Bugbot reviews this repo with zero project context today — no repo
in the org has a BUGBOT.md. This repo draws the most findings of any
(232), and the recurring classes here are dishonest outcome reporting and
mishandled interrupts, not generic security nits. Writing the house rules
down stops Bugbot re-deriving them and lets its one pass land on the hard
findings.

Encodes the invariants with the reason and a real reference each: honest
outcome reporting via classifyPushOutcome (a Job exiting 0 with row
failures is "completed_with_failures", not "succeeded"), the FROZEN exit
code contract, visible feedback on every errInteractiveCancelled path —
including that mapClientErr swallows it silently today — HTTP 426 as a
hard stop never a warning, fail-closed cosign/SHA256 verification in
install.sh, per-call timeouts, empty/nil guards at boundaries, the
cross-repo pin + generated-artifact rules, and the STYLE.md output
contract.

Also records verified non-issues: .golangci.yml does NOT gate CI (pinned
standalone binaries do), staticcheck's deliberate -ST1005 exclusion, the
single documented nolint, and the deadcode allowlist. Deliberately omits
a SLSA/provenance claim — signing here is cosign keyless, and the term
appears nowhere in the repo.

Item 4 of tracebloc/backend#930.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…oc/backend#930) (#411)

GitHub auto-closes an issue in another repository only when the PR body names
it owner-qualified. A bare `repo#N` merely cross-references and closes nothing
-- and the template's own hint taught `Ref tracebloc/other-repo#456`, which is
not a closing keyword at all.

Eight code-complete issues stayed open for days-to-weeks this way
(tracebloc/backend#1171-#1176, tracebloc/client#376, #393),
dragging two epics to 0% and 14% when the true figures were 67% and 24%.
Someone had to notice and close all eight by hand.

Also corrects CONTRIBUTING.md, which asserted that a `Closes #N` body line
auto-closes on merge. This repo's default branch is `main` while PRs land on
`develop`, and GitHub fires closing keywords only on merges into the default
branch -- so that claim was wrong in both directions and helped propagate the
bug. cli#393 is one of the eight.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
RFC numbers are assigned per repo, so a bare "RFC 0001" names four different
documents across the org and "RFC 0002"/"RFC 0003" name two each.

Add the qualified ID (`RFC-CLI-0001` … `RFC-CLI-0003`) to each header, in the
blockquote style these documents already use. Two of the three also record
which document the existing bare in-code references actually mean: nearly all
`RFC-0001` comments across backend/cli/client point at RFC-CLI-0001, and the
`RFC-0003` references in client's chart templates and docs/SEAL-CHECK.md point
at RFC-CLI-0003 — not at backend's own 0001/0003.

Nothing is renumbered and no file is moved, so existing links still resolve.
The org-wide index lives in tracebloc/backend (private) at docs/rfcs/README.md.

Co-authored-by: Claude Fable 5 <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 25db81d. Configure here.

Comment thread internal/cli/update_check.go
…nt (Bugbot #397) (#414)

#404 made writeUpdateCache refuse to recreate a wiped ~/.tracebloc, but
latestReleaseVersion still fetched from GitHub whenever the cache couldn't be
read. On a fresh install / after offboard the dir is absent, so the throttle can
never be persisted and every TTY command re-hit the releases API, burning
updateCheckTimeout each time.

Reconcile both: introduce configDirExists as the single "can the throttle be
persisted?" gate, used by writeUpdateCache (skip write — #404) AND
latestReleaseVersion (skip the network check entirely — #397) when the dir is
absent. A dir-present-but-stale/unreadable cache still falls through to the
normal throttled fetch, so the everyday path is unchanged.

Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@shujaatTracebloc shujaatTracebloc added the skip-fr-gate Maintainer override: bypass the FR gate on a develop→main promote (audited) label Jul 27, 2026
@saadqbal
saadqbal merged commit 8043dfa into main Jul 27, 2026
46 of 48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-fr-gate Maintainer override: bypass the FR gate on a develop→main promote (audited)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants