Uh oh!
There was an error while loading. Please reload this page.
feat(resources): tracebloc resources — show this machine's allowance (SHOW; set deferred) (#143) - #237
Conversation
…tracebloc may use (P1) Adds the top-level `tracebloc resources` command (cli#143): the one-knob, read-only view of how much of this machine tracebloc may use. No Kubernetes vocabulary in the output — one number for the machine, one for tracebloc's per-training-run share. P1 (SHOW) — built: - New pure `internal/resources` package: machine capacity from Ready-node allocatable (summed; single-node installer path is normally one node), per-run ceiling parsed from the jobs-manager RESOURCE_LIMITS env (the same source `cluster doctor`'s checkNodeFit reads, so the two never disagree), chart-default fallback, GPU surfacing, and user-language CPU/GiB formatting. - Release-scoped jobs-manager env reader that mirrors doctor.findDeployment's attribution rule on BOTH branches (release-known: prefixed name or instance-label-matched bare; release-unknown: unique suffix match, else nil) — never reads another release's component. - `internal/cli/resources.go`: bare `tracebloc resources` shows; resolves the cluster via the shared resolveClusterTarget seam (exit 3 kubeconfig / 4 no-release), --verbose adds the raw env + node/GPU breakdown. Wired into root.go's command tree and home screen. - Tests: resources pkg ~97%, internal/cli stays above its coverage floor. P2 (set --cpu/--memory, set max) and P3 (macOS Docker VM raise) — deferred: the shipped groundwork doesn't yet re-expose a safe persistence path to the CLI. `set` must write Helm values (a `kubectl set env` is reverted by the hourly auto-upgrade CronJob), and `helm upgrade` needs the chart reference the installer resolves via TRACEBLOC_HELM_REPO_NAME / a dev path — not recoverable from `helm list`. Rather than shell Helm blindly at a live training cluster, `set` is wired as a proper subcommand — `--cpu`/`--memory` flags and an optional `max` positional in the approved shape — whose RunE returns an honest exit-1 "not supported in this build yet" message that points back at `tracebloc resources`. Wiring the flags now means the designed invocation parses cleanly (no cobra "unknown flag") and P2 slots in behind it. Refs #143 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ed resources
Design sign-off for the status-aware `tracebloc` home screen (cli#244): fold
the LOCKED reference layout into the shipping renderer — byte-for-byte for the
signed-in/Online screen.
- ui: add Printer.MenuRow (dim · bullet, command padded to width, 4-space gap,
dimmed description) — the locked command-row style.
- home: rework renderHome to the locked layout — two-blank header, greeting by
first name (profile → clean email local-part → omit gracefully), a 30-col dim
rule, the two honest status axes (detection + no-false-Online invariant
unchanged), two command buckets ("Your data" + "Your secure environment",
`delete` folded in, "Manage" dropped), rows via MenuRow, and a dim
`love from tracebloc` sign-off. Not-signed-in + no-env restyled to match.
- doctor: promote `doctor` to a real top-level command; `cluster doctor` stays
as a hidden alias sharing one RunE (single diagnostic code path). The home
screen + env-status lines now read `<inv> doctor`.
- home: gate the `resources` row on the live command tree — absent until #237
wires `resources`, appears automatically once it does (never a hardcode).
- tests: byte-identical lock test against the reference render; name derivation;
resources gating (render + command-tree, both ways); top-level doctor shares
the cluster-doctor path; all existing state/honesty/timeout coverage kept.
make ci green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
aptracebloc
left a comment
There was a problem hiding this comment.
Approving. Clean, read-only, well-tested (96.7% on the pure internal/resources), and the resources set deferral is done right — it parses the approved shape (--cpu/--memory/set max, ValidArgs rejecting set bogus) and returns an honest exit-1 "not built yet" instead of a cobra flag error or a silent no-op, and it refuses to blindly helm upgrade a live training cluster with a chart ref it can't safely reconstruct. Good judgment. One non-blocking iteration note.
Non-blocking — shared logic worth extracting (suggest a follow-up)
internal/resources hand-copies a fair bit of internal/doctor: the chart-env parsers (parseResourceSpec, parseCPUMem, parseGPU, nodeReady) and the jobsManagerDeployment release-attribution rule. I see the in-code justification — keeping resources a pure leaf that doesn't import the cli command package — and that's a fair reason not to reach into doctor.
But this PR's own grounding ("a reader must mirror the writer", di#358) now applies twice over: there are two independent readers (doctor + resources) that must also stay mirrored to each other by hand, so a future change to the chart env format or the attribution rule has to land in both copies or they silently drift. The clean fix is to lift those helpers into internal/cluster — which both packages already import — rather than maintain two hand-synced copies. Could you open a follow-up issue for that extraction?
Two small things that'd fold into the same extraction:
parseGPUranges a map and returns the first match — nondeterministic ifGPU_LIMITSever carries more than one entry (same note as on #91). Fine for single-entry today.FormatGPU(name, q)never referencesname— the vendor prefix is "dropped" by simply not using it; either use it or drop the param.
None of this blocks — approving as-is; the extraction is a worthwhile iteration, not a fix.
— drafted with Claude (Opus 4.8), sent by @aptracebloc
Uh oh!
There was an error while loading. Please reload this page.
#237 put `resources show` on develop, so un-dash SHOW (keep `set`/#241 dashed), bump the basis commit to develop @27c5392, and reconcile the §4 heading + known-gaps note. The exit-6/--overwrite flow already matches current develop (data.go:236), so it's left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add the CLI navigation map (single source of truth) Four Mermaid flowcharts (top-level, the two gate chains, data ingest, resources) + exit-code legend + cross-links + known gaps. Diffable, renders on GitHub, kept current via PRs. Flags: two independent gate chains; stateless home today (status redesign proposed); delete exits 0 on partial offboard; resources unshipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: nav-map — resources show shipped (#237); rebase on develop #237 put `resources show` on develop, so un-dash SHOW (keep `set`/#241 dashed), bump the basis commit to develop @27c5392, and reconcile the §4 heading + known-gaps note. The exit-6/--overwrite flow already matches current develop (data.go:236), so it's left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Arturo Peroni <arturo@tracebloc.io>
Resolve internal/cli/root.go: keep the status-aware renderHomeScreen for bare `tracebloc` (the point of this PR) over develop's static banner. #237 (resources) is now merged, so the resources row renders — it stays gated on the live command tree, never hardcoded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…review #2/#3) Addresses @aptracebloc's review — both confirmed Medium, one shared root: the provisioned/namespace signal wasn't sourced uniformly across the home screen and doctor. - doctor ignored the active-client binding: runClusterDoctor now applies bindActiveClientNamespace before cluster.Load — the same seam `cluster info` / data / the home screen use — so with no --namespace/--context it targets the active client's cached namespace, not the kubeconfig default. Removes the home-screen <-> doctor contradiction. Binding-only: a local config read, no extra cluster dial, matching what the home screen does when provisioned (check the bound namespace, no cluster-wide scan). - provisioned-vs-no-environment keyed off env.name (ActiveClientName) while the probe's ownership gate keyed off the cached namespace, so a provisioned-but-unnamed profile misread as "no environment / run the installer". resolveHomeModel now takes a `provisioned` signal from the SAME field the gate uses (ActiveClientNamespace) and renders offline on `provisioned || env.name != ""`; the display name falls back to the client ID so it stays a *named* offline. Tests (mutation-proven): new TestResolveHomeModel_States case (provisioned, namespace only, no name -> offline) + TestClusterDoctor_BindsActiveClientNamespace. Updated TestHasTopLevelCommand (resources is wired now that #237 merged). make ci green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): status-aware `tracebloc` home screen
Rework the bare-`tracebloc` home screen from a stateless command list into
a status-aware landing screen that opens with where you actually stand —
signed in? is this machine's secure environment live? — then the commands.
Two separate, never-fused axes: sign-in (you) and the secure environment
(the machine), because the client heartbeats with its own credential. A
green "· Online" prints ONLY when the environment is live locally (chart
present + jobs-manager Ready) AND positively confirmed heartbeating to
tracebloc; a heartbeat we can't confirm degrades to "· running", never a
false green. States: not-signed-in / online / running-not-heard-from /
offline / no-environment.
Detection is best-effort and bounded so bare `tracebloc` (run constantly,
previously zero-I/O) stays snappy: probes run concurrently, each with its
own short timeout, all capped by a ~1.5s overall budget; any error/timeout
degrades to the softer state and the screen still renders. Logged out does
zero cluster/backend I/O. An unreachable cluster caps at the probe timeout
(~1.2s) instead of the OS default.
Reuses existing seams: config sign-in + cached email, the data commands'
namespace binding + release discovery, delete.go's `tb`-alias ownership
check, and node allocatable for the compute parenthetical (no `resources`
command on develop). `<inv>` echoes the invoked binary name (tb/tracebloc);
the doctor path shown is the real `cluster doctor`.
HEARTBEAT CREDENTIAL: read via the signed-in user token (ListClients),
the only path that exists — there's no login-free in-cluster-client
credential path, so the environment line is confirmable only while signed
in (documented in realHeartbeat).
Adds ui.CheckLine/CrossLine/WarnLine for the locked ✓/✗/⚠ status glyphs.
Tests are table-driven and cluster-free: every state, the honesty fallback
(can't-confirm → running, not Online), and the timeout/degrade path (a slow
probe still renders fast with the softer state).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(cli): address home-screen review — honest offline, named degrades, starting state
Code-review follow-ups on the status-aware home screen:
- A provisioned machine is never told "no environment / run the installer".
A reachable cluster that doesn't host this release (wrong kube-context, or
the client runs on a cluster this kubeconfig doesn't point at) now degrades
to a NAMED offline when a client name is cached — the "runs elsewhere" case
the sibling data commands explain via binding.explain — instead of the
no-environment lie. Only a machine that was never provisioned shows no-env.
- The budget-timeout degrade keeps the remembered name. resolveHomeModel now
reads the cached client name up front (new rememberedName seam) and fills it
whenever the probe surfaced none — including the bctx.Done() path — so a
context-ignoring kubeconfig exec-credential plugin (aws eks get-token, etc.)
that outlives the render degrades to a named offline, not no-env.
- The offline copy is honest for BOTH causes (stopped/unreachable AND
reachable-but-release-not-here): "· can't reach it from here — run
<inv> cluster doctor", not the bare "offline".
- Degraded workload gets its own state + line (homeStarting: "· starting up,
not ready yet"), distinct from the live-but-unconfirmed-heartbeat "running,
but tracebloc hasn't heard from it" (heartbeat is never consulted when the
workload isn't Ready). Both still point at cluster doctor.
- Header comment: the budget bounds the RENDER (wall-clock), not the probe
goroutines — a context-ignoring probe can outlive it; the buffered channels
just keep it from blocking.
Invariants held + mutation-proven: no green Online without localLive +
beatOnline (honesty), and detection never hangs (collector bails on the
budget without probe cooperation). New/changed tests below; make ci green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(home): drain just-finished probes at the budget + honest running copy (Bugbot)
Two review findings on the status-aware home screen:
- Budget expiry could drop completed probe results: when bctx.Done() and a
buffered result are ready in the same select, the pick is random — a probe
that finished just as the budget fired could be discarded for the softer
default (live release rendering as offline/no-env). The collector (now
collectProbes, extracted for a deterministic test) drains both buffers
non-blocking on Done, so only probes that truly haven't reported degrade.
- The running line said "tracebloc hasn't heard from it" for BOTH heartbeat
answers. That claim is only earned when the backend positively reported
not-online (offline/pending); a mere couldn't-confirm (backend unreachable,
timeout) now says "couldn't confirm it's connected to tracebloc" instead of
asserting a backend view we never obtained. homeModel carries
confirmedNotOnline so the render stays pure.
Both fixes mutation-proven (drain removal and flag collapse each fail their
new tests); make ci green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(home): never adopt a foreign release as "your secure environment" (Bugbot)
With no active client cached, realProbeEnv's discovery fell back to the
kubeconfig's default namespace and then the cluster-wide scan
(binding.allowScan() is true when the binding isn't applied) — so on a shared
cluster a colleague's release could render as YOUR live environment, full data
menu included. The data commands only run that scan behind a visible retarget
note and an explicit user action; the home probe passes p=nil, so even that
disclosure was silently dropped, and §7.5's rule (a miss must never silently
retarget to some other client) applies doubly to a status screen.
Ownership gate: no active-client binding ⇒ report localNoRelease before any
cluster I/O — resolveHomeModel renders the honest no-env screen (or a named
offline via the remembered-name fallback). Provisioned machines are untouched
(binding scopes discovery to the active client's namespace, scan already
disabled). Side effect: the common unprovisioned re-entry now does zero
cluster I/O.
Mutation-proven both ways (gate removed → the unprovisioned probe dials out
and lands unreachable; over-gated → the provisioned probe stops short);
make ci green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(home): fold the locked home-screen design; top-level doctor; gated resources
Design sign-off for the status-aware `tracebloc` home screen (cli#244): fold
the LOCKED reference layout into the shipping renderer — byte-for-byte for the
signed-in/Online screen.
- ui: add Printer.MenuRow (dim · bullet, command padded to width, 4-space gap,
dimmed description) — the locked command-row style.
- home: rework renderHome to the locked layout — two-blank header, greeting by
first name (profile → clean email local-part → omit gracefully), a 30-col dim
rule, the two honest status axes (detection + no-false-Online invariant
unchanged), two command buckets ("Your data" + "Your secure environment",
`delete` folded in, "Manage" dropped), rows via MenuRow, and a dim
`love from tracebloc` sign-off. Not-signed-in + no-env restyled to match.
- doctor: promote `doctor` to a real top-level command; `cluster doctor` stays
as a hidden alias sharing one RunE (single diagnostic code path). The home
screen + env-status lines now read `<inv> doctor`.
- home: gate the `resources` row on the live command tree — absent until #237
wires `resources`, appears automatically once it does (never a hardcode).
- tests: byte-identical lock test against the reference render; name derivation;
resources gating (render + command-tree, both ways); top-level doctor shares
the cluster-doctor path; all existing state/honesty/timeout coverage kept.
make ci green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(home): bound + sanitize greeting name; retarget cluster-doctor hints to `doctor`
Review follow-ups on the locked home screen (cli#244):
- home: greetingName now runs BOTH the profile first name and the email
local-part through one clean-token check (letters only, single word, no
interior whitespace/newline/control chars) and caps the result at
greetingNameMax = 14 runes — otherwise it omits the name. Stops a long
local-part from stretching, or a newline-bearing FirstName from SPLITTING,
the locked single-line header. The clean short demo name is unaffected, so
the byte-identical golden render is unchanged.
- doctor: retarget the now-hidden `cluster doctor` remediation hints to the
canonical `tracebloc doctor` (the alias still runs, so non-breaking) —
client.go x4 (create-fail hint, discovery-fail error, two connect-timeout
errors) + cluster/discover.go's ErrNoParentRelease tail. Kept doctor.Run's
suffix-strip in lockstep (it trims that exact tail so doctor never tells you
to run doctor) and updated the two tests that pinned the old text.
- tests: greeting-name bound/sanitize cases (over-long / interior-newline / tab
/ control-char / multi-word -> omit; cap boundary used); an end-to-end
one-line-header guard; a direct hidden-alias assertion (`doctor` visible
top-level, `cluster doctor` present but Hidden). Fixed the golden test's
stale cmd/hsdemo comment.
make ci green; golden render still byte-identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(home): two blank lines above every section title, not just the first
Lukas spacing tweak (cli#244): the status->first-section gap was 2 blanks (a
standalone pre-loop Newline + Section's own leading blank) while the inter-
section gaps were only 1. renderBuckets now emits a Newline before each Section,
and the standalone pre-loop Newline is dropped — so every section gets exactly
2 blanks above and the status->first-section gap stays 2 (not 3). Mirrors the
locked demo's new Newline+Section+Newline-per-bucket render.
Golden test updated (+1 blank before "Your secure environment"); make ci green,
render still byte-identical to the demo (850 bytes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(home,doctor): source the provisioned/namespace signal uniformly (review #2/#3)
Addresses @aptracebloc's review — both confirmed Medium, one shared root:
the provisioned/namespace signal wasn't sourced uniformly across the home
screen and doctor.
- doctor ignored the active-client binding: runClusterDoctor now applies
bindActiveClientNamespace before cluster.Load — the same seam `cluster
info` / data / the home screen use — so with no --namespace/--context it
targets the active client's cached namespace, not the kubeconfig default.
Removes the home-screen <-> doctor contradiction. Binding-only: a local
config read, no extra cluster dial, matching what the home screen does
when provisioned (check the bound namespace, no cluster-wide scan).
- provisioned-vs-no-environment keyed off env.name (ActiveClientName) while
the probe's ownership gate keyed off the cached namespace, so a
provisioned-but-unnamed profile misread as "no environment / run the
installer". resolveHomeModel now takes a `provisioned` signal from the
SAME field the gate uses (ActiveClientNamespace) and renders offline on
`provisioned || env.name != ""`; the display name falls back to the
client ID so it stays a *named* offline.
Tests (mutation-proven): new TestResolveHomeModel_States case (provisioned,
namespace only, no name -> offline) + TestClusterDoctor_BindsActiveClientNamespace.
Updated TestHasTopLevelCommand (resources is wired now that #237 merged).
make ci green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>#241 was stacked on #237 (resources SHOW), which squash-merged to develop; develop also gained the #244 status-aware home screen + #231 top-level doctor. Resolved: - internal/cli/resources.go, resources_test.go → #241's (the BUILT `set`). develop's copy is #237's SHOW + the deferred `resources set` stub, and #241 is exactly the PR that replaces that stub with the real command. Only #237 ever touched these files on develop, so no SHOW change is lost. - internal/cli/root.go → develop's (new home screen + top-level doctor); #241 adds nothing to root.go (`resources` is wired via resources.go).
What
Refs #143. Ships thetracebloc resourcescommand — "one knob for how much of this machine tracebloc may use" (design approved 2026-07-06) — SHOW only. SET and the macOS VM-raise are deferred on a real groundwork gap (below).Built —
tracebloc resources(SHOW / P1)Bare
tracebloc resourcesshows the machine's capacity + the per-training-run allowance in the user's language (cores + GiB, no Kubernetes vocabulary). New pureinternal/resourcespackage:MachineCapacity(sums Ready-node allocatable + GPU),ParseTraining(readsRESOURCE_LIMITS→RESOURCE_REQUESTS→chart default cpu=2/mem=8Gi, per client#308), user-language formatters, and a release-scoped jobs-manager env reader (mirrorsdoctor's attribution rule so another release's jobs-manager can't satisfy the read).--verboseadds namespace/client/raw-env/GPU;--plainhonored; wired top-level + into the home screen. Cluster-free tests via the fake clientset;internal/resourcesat 96.7%.Deferred —
resources set+ macOS VM raise (P2/P3)resources set --cpu … --memory …andresources set maxparse cleanly and return an honest "not supported in this build yet" (exit 1) — not a flag error. SET is deferred on a real groundwork gap: persisting a change needshelm upgradewith the chart reference, which the CLI can't safely reconstruct (the installer's repo-alias/chart-ref isn't recoverable fromhelm list), and blindly mutating a live training cluster with reconstructed args is unsafe. The unblock is installer-side — the installer must record the chart ref where the CLI can read it. Worth its own follow-up ticket.Tests / gates
make cigreen (vet +-racetests + errcheck/ineffassign/misspell + gofmt -s + schema-check);coverage-floor.shok (cli 74.6% ≥ 68%, submit 76.1% ≥ 72%).Open questions (for when P2 lands)
set, and whether overhead is a fixed constant or computed from chart requests.Refs #143(not Closes — P2/P3 outstanding).🤖 Generated with Claude Code
Note
Low Risk
Read-only cluster inspection and user-facing formatting;
setis explicitly non-mutating. Risk is mainly misreporting capacity/ceiling if attribution or parsing diverges from doctor—mitigated by mirrored rules and tests.Overview
Adds a top-level
tracebloc resourcescommand (cli#143) that read-only shows machine schedulable capacity vs the per-training-run ceiling, in plain CPU/GiB language without Kubernetes jargon.SHOW (P1): Resolves the cluster like other data commands, sums Ready node allocatable (GPUs included), and reads the training ceiling from the release-scoped jobs-manager env (
RESOURCE_LIMITS→ requests → chart default), aligned withcluster doctor.--verboseexposes namespace/client/raw env; node list failures still show the ceiling with a clear capacity-unavailable line.New
internal/resources: Pure helpers (MachineCapacity,ParseTraining, formatters) plusJobsManagerEnvwith the same deployment attribution rules as doctor (no cross-release reads; ambiguous multi-release → empty).resources set(deferred P2): Subcommand and flags (--cpu,--memory,max) parse the approved shapes but exit 1 with an honest “not supported yet” message—no cluster mutation.Root command tree and bare
traceblochome screen list the new command; CLI and package tests cover rendering, routing, exit codes, and deferral parsing.Reviewed by Cursor Bugbot for commit 899b0d7. Bugbot is set up for automated code reviews on this repo. Configure here.