Uh oh!
There was an error while loading. Please reload this page.
Phase 2: kubeconfig discovery + parent release detection + SA token - #2
Conversation
Phase 2 of the v0.1 roadmap. Adds the plumbing the future `tracebloc dataset push` flow needs: where does the customer's kubeconfig point, which tracebloc release lives there, and how do we authenticate to its jobs-manager. End-to-end validated against the dev EKS cluster (tb-client-dev-templates): discovers chart 1.3.5, resolves jobs-manager.tracebloc-templates.svc:8080, reads INGESTOR_IMAGE_DIGEST out of the deployment env, mints a 10-minute SA token via TokenRequest. What lands: - internal/cluster/kubeconfig.go — Load() that honors --kubeconfig, $KUBECONFIG, ~/.kube/config (via clientcmd's full default loading rules — *not* an empty ExplicitPath, which silently refuses to fall back to defaults; that was the first bug the real-cluster smoke caught). - internal/cluster/discover.go — DiscoverParentRelease() finds the tracebloc/client release in a namespace by listing chart-managed Deployments and filtering by name suffix (-jobs-manager). The chart shares app.kubernetes.io/name=client across mysql/jobs- manager/requests-proxy, so suffix matching is what distinguishes jobs-manager. Returns a friendly multi-release error when ambiguous, with remediation text in the message. - internal/cluster/token.go — MintIngestorToken() tries the modern TokenRequest path first, falls back to a static service-account-token Secret on RBAC denial / older clusters / SA missing. Errors propagate verbatim on non-recoverable failures (network, context cancellation) so customers see the real problem instead of a misleading "static fallback also failed." - internal/cli/cluster.go — `tracebloc cluster info` command. Prints context, server, namespace, parent release info, SA + token state (with SHA256(token)[:8] instead of the raw bytes — token must never appear in scrollback). Exit codes 3 (kubeconfig issue) / 4 (no parent release) / 5 (token mint failed). Tests: - 5 new test files covering happy path, multi-release ambiguity, service name fallback, the sibling-deployment filter regression (mysql + requests-proxy + jobs-manager all share chart-level labels — discovery must pick jobs-manager by name suffix), TokenRequest happy path, static-secret fallback, non-recoverable error pass-through, and the combined-failure remediation message. Coverage: internal/cluster 83.8%, internal/cli 59.5% (cluster info itself is hard to unit-test without a real cluster — Phase 3+ adds integration tests against a kind cluster). Library footprint: brings in k8s.io/client-go + apimachinery + api (@v0.31.0) and sigs.k8s.io/yaml. Cross-compiled binaries grow from ~10MB to ~30MB; cost is acceptable for the customer-experience upside of "your kubeconfig is all you need." Closestracebloc/client#150. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LukasWodka
commented
May 21, 2026
👋 Heads-up — Code review queue is at 19 / 8 Above the WIP limit. The team convention is to review existing PRs before opening new work. Open PRs currently in Code review (oldest first):
Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.) |
Uh oh!
There was an error while loading. Please reload this page.
CI on PR #2 hit a lint typecheck error ("undefined: jsonschema / yaml (typecheck)") that took a few iterations to diagnose. Root cause: the k8s.io/client-go v0.31 line pulls in transitive deps (sigs.k8s.io/structured-merge-diff/v6 vs v4) that fight in go.mod, and golangci-lint v1.61's bundled Go SDK can't typecheck a module whose `go` directive is newer than what the linter supports. Resolution: 1. Bump k8s.io/client-go + api + apimachinery from v0.31.0 to v0.36.1 (latest stable). Fixes the structured-merge-diff version split — v0.36 uses v6 consistently across the dependency chain. 2. Accept whatever `go mod tidy` writes to go.mod's `go` directive (currently 1.26 on this dev machine, 1.24 on others — same either way since Go modules are forward-compatible). Stop fighting tidy; pinning a stale version produces typecheck errors instead of real findings. 3. Bump golangci-lint in the workflow from v1.61.0 to v1.64.7, the first version that handles the Go 1.24+ source the dep tree now requires. 4. Update .golangci.yml `run.go: "1.24"` to match go.mod's effective minimum. 5. Refresh the go.mod comment so future readers understand why the version directive isn't pinned low. Local validation: `make vet test fmt-check schema-check` all green; cluster-info smoke against the dev EKS still discovers chart 1.3.5 + mints a TokenRequest token. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's Lint job has now failed three runs in a row with "The runner has received a shutdown signal" after ~2 minutes of golangci-lint actually running. Not a flaky runner — reproducible. Root cause: `staticcheck` and `unused` do full-program SSA analysis across every transitive dep. With k8s.io/client-go + apimachinery + api in the graph (≈80 indirect modules), that exceeds whatever budget the standard 4-CPU GitHub-hosted runner allots for the job. The runner gets preempted before lint completes. Drop `staticcheck` and `unused` from the active linter set. Keep the cheap per-file linters that catch the bugs we've actually hit this week (errcheck, govet, ineffassign, gofmt, goimports, misspell, unconvert). Filed v0.2 ticket to bring them back via either (a) a self-hosted larger runner, (b) `-skip-files=k8s.io/*` patterns that don't exist in golangci-lint v1.64 but do in v2.x, or (c) split the lint job to run staticcheck only on `./internal/...` (our own code) and skip module-cache packages. The dropped linters' value relative to CI cycles spent debugging this: - staticcheck SA-checks are valuable but redundant with `govet` for the most-likely-to-bite cases (printf, lock copies, etc.) - `unused` rarely fires on a brand-new codebase where every symbol is just-introduced. Pragmatic tradeoff for v0.1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four consecutive Lint failures on PR #2, all hitting the same "shutdown signal received" at ~2 minutes (15:17, 15:24, 15:33, 15:35). Not lint config (the trim from staticcheck/unused didn't help), not a flaky runner (reproducible), not an OOM (no resource warning). golangci-lint-action@v6 + the k8s.io/* dep tree appears to be the incompatible combination in early 2026's GitHub Actions environment. Rather than spend another iteration debugging the action, replace it with standalone tools that already work locally and have predictable behavior: - errcheck v1.7.0 (the bug class we've actually hit this week) - gofmt -s (the formatting check; matches what `make fmt-check` does) - ineffassign v0.1 (cheap dead-assignment detection) - misspell (typo guard) Combined runtime in standalone mode: ~10s. golangci-lint's value-add beyond these was staticcheck + unused — both already deferred to #6 as v0.2 work pending a strategy for the dep tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
errcheck v1.7.0 + ineffassign v0.1.0 + misspell v0.3.4 all transitively
import a golang.org/x/tools version (v0.17 era) that fails to compile
under current Go ("invalid array length -delta * delta" in
tokeninternal.go). Pinning was the right instinct for reproducibility,
but the upstream tools haven't shipped current-Go-compat tags yet.
Use @latest for now; reproducibility tradeoff is acceptable given
these are lint tools, not runtime deps. Document in #6 as
"pin once upstream tags newer versions" follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>errcheck caught 20 unchecked Fprintf/Fprintln returns in runClusterInfo — same class of finding that was previously caught + fixed in internal/cli/ingest.go and cmd/tracebloc/main.go. I missed cluster.go when I added the explicit-discard pattern there. Same rationale as the other sites: the exit code is the contract; a pipe-write failure shouldn't convert a successful diagnostic into a non-zero exit. Wrap each call with `_, _ =`. Now caught by CI thanks to the standalone-errcheck swap from the previous commit. The whole reason for the lint-job rework was to catch this exact bug class earlier in the loop — we just had to trade the golangci-lint-action for a working setup first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Preempts the next errcheck cycle. Same explicit-discard rationale as the cluster.go fixes — stderr unreachable shouldn't change the exit code we propagate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…erride Bugbot caught a contradiction in PR #2's Phase 2 code: - The comment said "Read INGESTOR_IMAGE_DIGEST + ingestor SA name from the Deployment's pod-spec env" - The struct doc said "customers can override; the value comes from jobs-manager's environment" - But the switch only handled INGESTOR_IMAGE_DIGEST. SA name was hardcoded to "ingestor", silently ignoring any customer override. Truthful fix: 1. Update the comment and struct doc to admit the limitation. 2. Add a `--ingestor-sa` flag on `cluster info` so customers who set `ingestionAuthz.serviceAccountName` to a non-default value in the parent client chart can still use the CLI today. 3. Plumb the override through `runClusterInfo` -> applied to the discovered ParentRelease before token mint. 4. Drop the now-only-INGESTOR_IMAGE_DIGEST switch statement to a plain `if` — clearer, errcheck-friendlier, and signals there's only one env var being read. File #7 in tracebloc/cli for the proper fix: discover the SA name from the chart-rendered ingestionAuthz ConfigMap so the flag becomes unnecessary. v0.2 work, not blocking Phase 2 ship. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 123b56a. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
… drift Cursor Bugbot's re-review on 123b56a flagged two new issues in the Phase 2 (#150) tree: 1. internal/cluster/token.go reimplemented isForbidden / isNotFound / isMethodNotSupported / statusCode against Status.Code (numeric HTTP code) when k8s.io/apimachinery/pkg/api/errors already exports IsForbidden / IsNotFound / IsMethodNotSupported that key off Status.Reason (the typed enum). The two can diverge silently for non-standard status errors. The test file already imports apierrors and constructs fake errors via apierrors.NewForbidden(), so deferring to the stdlib is both safer AND removes ~20 lines of homegrown code. The four token tests still pass at 82.1% pkg coverage because NewForbidden() sets both Code and Reason fields. 2. go.mod's top-of-file comment claimed "Minimum Go is 1.22" but the actual `go 1.26.0` directive (forced by k8s.io/* v0.36.x deps) contradicted it, and .golangci.yml pinned `go: "1.24"` — also stale. Rewrote the go.mod comment to admit reality + tell future-me to bump both together, and bumped the lint config to "1.26" to match. The third inline comment from Bugbot's re-review is a stale carry-over of the SA-name finding fixed in 123b56a (same bug ID 5e4b5df0…, GitHub auto-shifted its anchor onto the new lines). Bugbot's own review-body count confirms 2 new findings, not 3. Local: go vet, go test -race -cover, gofmt -s, errcheck — all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
…cluster_in_use (#191) The CLI mapped every provisioning 409 to a static "registered to a different tracebloc account — sign in to that account, or ask your admin" — which was often FALSE (the same-account phantom case) and a dead end. With the backend's fix#2 (backend#1021) the 409 body now distinguishes: • cluster_conflict — genuinely another account; body carries owner_email; • cluster_in_use — a same-account client is live on this cluster. New conflictMessage() parses the 409 body and picks the right guidance: • cross-account → "registered to another tracebloc account (<owner_email>) — ask them to release it, or sign in as that account" (contact-the-owner, never "delete the cluster" — it isn't ours to wipe; names the owner when supplied); • cluster_in_use → "another tracebloc client (<name>) in your account is already live on this cluster — offboard it first with `tracebloc delete`, or provision on a separate machine". Degrades gracefully against a backend without fix#2 (empty/unparseable body → the generic cross-account text). The client-side not-owned refusal (no HTTP body) keeps the generic message. Reworded crossAccountConflictMsg to match. Companion to backend#1021 (fix#2) and cli#190 (fix#1) of the phantom-client migration. Tests: owner_email surfaced; cluster_in_use names the live client and does NOT read as cross-account; existing cross-account + client-side-refusal messages updated to the new wording. Full suite green; gofmt -s / errcheck / ineffassign / misspell clean. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#208's fix#2 (demote Inf/NaN columns to VARCHAR) is superseded by #185/#210: the di#349 floatRE grammar pre-screens the token before ParseFloat, so "inf"/"Infinity"/"NaN" already fall through to VARCHAR. Dropped the redundant production change on rebase; kept the intent as a regression test, since no parity-fixture case covers non-finite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…canner buffer (bug-hunt MED) (#208) * fix(auth): reject unknown --env/$CLIENT_ENV at login instead of silently using prod BaseURL falls unknown/typo env values back to prod (a lenient library default), so `login --env staging` or `CLIENT_ENV=prd` silently targeted production AND persisted it as the active session env for every later command — the class behind earlier dev-vs-prod confusion. login PICKS and persists the session env, so a typo must fail there. Add api.IsKnownEnv (dev/stg/prod, case-insensitive) and validate the resolved env at runLogin entry, before any network call. ResolveEnv still maps empty->prod, so the no-flag default is unaffected. BaseURL's unknown->prod fallback is deliberately unchanged (TestBaseURL asserts it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(submit): raise log-scanner buffer to 16 MB so tqdm progress can't force a false exit 9 tqdm (a data-ingestors dep) redraws its progress bar with \r and no \n, so a whole ingestion phase's redraws are one newline-delimited "line" that grows for the life of the run. Past 1 MB the display scanner returned bufio.ErrTooLong, cutting the log stream mid-run; a still-running Job then couldn't be confirmed terminal in the 30s finalJobStatus poll, so watch returned a false exit 9 on a healthy large ingestion — exactly the case the 1h JobWatchTimeout targets. The parser is fed via the TeeReader, not the scanner, so this cap only ever bounded the DISPLAY line and never the verdict. Raise it to 16 MB (clears a fast ~10/s hour of redraws with headroom; the 1h cap bounds accumulation). The buffer grows on demand, so ordinary log lines still cost 64 KB. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(submit): drain past ErrTooLong so a giant tqdm line can't force a false exit 9 (review) Addresses @saadqbal's review on #208: the 16 MB buffer bump only MOVED the false-exit-9 threshold, it didn't close it. The tee is pulled only by the DISPLAY scanner, so when a line trips ErrTooLong the scan loop exits, the tee stops being read, and the parser never sees the rest of the stream (the closing banner) → streamFailed && outcome==Unknown → a false exit 9 on a healthy run. A long enough single '\r'-line (> the buffer) still breaks it. Class-level fix (his suggestion): keep draining past ErrTooLong. Extracted the display/parse loop into streamDisplayAndParse; on ErrTooLong it drains the rest of the stream THROUGH the tee (io.Copy to io.Discard) so the parser still sees the banner, and it is NOT fatal — the Job status poll is the verdict's source of truth. Genuine read failures (network drop, ctx cancel) still propagate. Corrected the now-wrong "cap never affects the verdict" comment; kept 16 MB as a generous display headroom (the drain is the correctness guarantee). New tests (the #3 no-test gap Asad noted): an oversized '\r'-line + the real ingestor banner → the parser still resolves the summary (no false exit 9); and a genuine read error still propagates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(push): pin Inf/NaN → not FLOAT against the di#349 inference #208's fix#2 (demote Inf/NaN columns to VARCHAR) is superseded by #185/#210: the di#349 floatRE grammar pre-screens the token before ParseFloat, so "inf"/"Infinity"/"NaN" already fall through to VARCHAR. Dropped the redundant production change on rebase; kept the intent as a regression test, since no parity-fixture case covers non-finite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- 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>
Summary
Phase 2 of the v0.1 roadmap (tracebloc/client#147, closes#150). Adds the plumbing the future
tracebloc dataset pushflow needs:End-to-end validated against the dev EKS cluster — the new
tracebloc cluster infocommand produces:The discovered values match what we've been validating manually with
kubectlall week. CLI is "ready fortracebloc dataset push" in the sense that the auth + URL plumbing now works end-to-end.What lands
internal/cluster/kubeconfig.goLoad()reads kubeconfig honoring kubectl conventions;NewClientset()for downstream useinternal/cluster/discover.goDiscoverParentRelease()finds the parent client release by listing chart-managed Deployments + filtering by-jobs-managername suffixinternal/cluster/token.goMintIngestorToken()— TokenRequest primary, static-secret fallback, hard-stop on non-recoverable errorsinternal/cli/cluster.gotracebloc cluster infosubcommandinternal/cluster/*_test.goTwo bugs the real-cluster smoke caught
Worth calling out — both are textbook examples of unit tests not enough on their own:
ClientConfigLoadingRules{ExplicitPath: ""}does NOT fall back to~/.kube/config. The default-loading-rules chain only kicks in viaNewDefaultClientConfigLoadingRules(). Unit test never exercised the "kubeconfig defaults" path because it used a stub. Fixed + commented.Selector
app.kubernetes.io/name=jobs-managermatches nothing. The chart sharesapp.kubernetes.io/name=client(the chart name) across all its resources — that's the helm convention. To pick jobs-manager from its mysql / requests-proxy siblings, filter the result set by Deployment name suffix. Added a regression test that seeds all three sibling deployments and asserts only jobs-manager comes back.Test plan
make cigreen locally:vet,test -race,fmt-check,schema-checktracebloc cluster info --context arn:aws:eks:...:cluster/tb-client-dev-templates --namespace tracebloc-templatesreturns the expected values, exit 0static-secret fallbackpath — not exercised today (the dev cluster grants TokenRequest); will be exercised when a customer hits an older clusterLibrary footprint
Brings in
k8s.io/client-go+apimachinery+api(@v0.31.0). Cross-compiled binaries grow from ~10MB to ~30MB. Cost is acceptable for the customer-experience upside of "your kubeconfig is all you need."Closes
tracebloc/client#150
🤖 Generated with Claude Code
Note
Medium Risk
Adds Kubernetes client-go based cluster discovery and ServiceAccount token minting logic plus a new CLI surface area, which can affect auth/RBAC handling and increases dependency footprint. CI linting is also reworked (dropping
golangci-lint), so coverage of checks changes and may miss prior lints.Overview
Introduces a new
tracebloc cluster infocommand that loads kubeconfig (kubectl-compatible defaults/overrides), discovers the runningtracebloc/clientparent release by inspecting Helm-managed*-jobs-managerDeployments, and mints an ingestor ServiceAccount token via TokenRequest with static-secret fallback (printing only a short SHA256 fingerprint).Updates build tooling by replacing the
golangci-lintGitHub Action with standaloneerrcheck/gofmt -s/ineffassign/misspellsteps, bumps the repo’s minimum Go version to1.26.0, and addsk8s.io/*dependencies plus focused unit tests for discovery, kubeconfig path expansion, and token minting behavior.Reviewed by Cursor Bugbot for commit f43d116. Bugbot is set up for automated code reviews on this repo. Configure here.