Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f09078a
fix(delete): verify the host-data wipe before printing ✔ (RFC-0003) (…
saadqbal Jul 23, 2026
dfee738
feat(cli): auto-update — nudge + tracebloc upgrade (F1) (#390)
LukasWodka Jul 23, 2026
27fd92a
fix(push): defer progress-bar first render until bytes flow (#391)
LukasWodka Jul 23, 2026
5ed2f63
feat(install): prefer ~/bin when already on PATH (B2, RFC 0001) (#392)
LukasWodka Jul 23, 2026
71ed489
feat(cli): tracebloc prepare-host wrapper (#1178, cli) (#394)
LukasWodka Jul 23, 2026
234d8fe
feat(status): surface the environment's seal-check verdict (client st…
LukasWodka Jul 23, 2026
a242f46
docs(rfc): RFC-0003 — dataset storage & offboard hygiene (draft) (#366)
LukasWodka Jul 24, 2026
1856c90
fix: resolve Bugbot findings on promotion PR #397 (#402)
shujaatTracebloc Jul 24, 2026
4ecca9a
fix(resources): clamp wizard defaults into their own valid range (#39…
LukasWodka Jul 24, 2026
fcdefca
fix: Bugbot findings from promotion PR #397 (TLS floor + delete cache…
shujaatTracebloc Jul 24, 2026
6548218
fix(doctor): per-OS compute remedies + resources-set-max drift nudge …
LukasWodka Jul 24, 2026
327987b
fix(resources): 'equipped with' reports the largest node, not a cross…
LukasWodka Jul 24, 2026
a6baa17
fix: treat seal Ctrl-C as quiet interrupt, not false Unsealed (Bugbot…
shujaatTracebloc Jul 24, 2026
12bbe7e
fix(home): adopt a LOCAL cluster's release when the client pointer is…
LukasWodka Jul 24, 2026
550979a
fix(prompts): a cancelled prompt is never silent — one helper, exit 0…
LukasWodka Jul 27, 2026
b766f39
docs(bugbot): add .cursor/BUGBOT.md project context (backend#930) (#409)
LukasWodka Jul 27, 2026
292116e
docs(pr-template): owner-qualify cross-repo closing keywords (tracebl…
LukasWodka Jul 27, 2026
25db81d
docs(rfc): state qualified identifiers in the RFC headers (#412)
LukasWodka Jul 27, 2026
19ea73f
fix: skip update check (not just cache write) when config dir is abse…
shujaatTracebloc Jul 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions .cursor/BUGBOT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Bugbot guide — tracebloc/cli

## Context

Public Go CLI (Apache-2.0), shipped as **signed 8-platform releases** (cosign keyless,
verified by `scripts/install.sh`). Customers run it on their own machines against their own
Kubernetes to operate a self-hosted secure environment. It talks to a public HTTPS backend
(`internal/api`) and to an in-cluster jobs-manager (`internal/submit`), and shells out to
`kubectl`/`helm`/`docker`.

Two things make this repo unusual and should shape every finding:

1. **Its exit codes are a scripting contract** — customers branch on them
(`internal/cli/exitcodes.go`: "the numeric values are FROZEN").
2. **`make ci` mirrors CI exactly.** The `Makefile` header states it outright: "divergence
between local and CI is the bug this file exists to prevent." Tool versions are pinned in
lockstep with `.github/workflows/build.yml`.

## Always flag

- **A command that reports success it hasn't earned.** Exiting 0 is not the same as
succeeding. The reference pattern is `classifyPushOutcome`
(`internal/cli/data_ingest_output.go:25`): a Job that exits 0 but whose summary reports row
failures returns `"completed_with_failures"` + `exitIngestFailed`, *not* `"succeeded"` — its
comment cites this class explicitly. `internal/doctor` carries the same idea in
`StatusUnknown`: a check that ran but cannot back a green prints a neutral line rather than a
false ✔. Flag any new multi-step command where a partial failure collapses into success, and
any path where the `--output-json` status and the process exit code can disagree.

- **An exit code that isn't a named constant from `internal/cli/exitcodes.go`**, a repurposed
numeric value, or a new failure path returning generic `1` when a specific code already
exists. Every non-test `&exitError{}` names its code.

- **A prompt whose cancellation produces no visible feedback.** `errInteractiveCancelled`
(`internal/cli/interactive.go:26`) must print a `Cancelled — …` line via the Printer and then
return cleanly — see `resources_set.go:219`, `data_delete.go:233`,
`data_ingest_local.go:103`, `data_ingest_cluster.go:339`. Watch for
**`mapClientErr` (`internal/cli/client.go:858`), which maps it straight to `nil` with no
output at all** — a Ctrl-C routed through it exits 0 in total silence, right next to a
declined-answer branch that *does* print (`client.go:334`, `delete.go:196`). Check this at
every new or changed prompt site. Signals are wired centrally via `signal.NotifyContext`
(`cmd/tracebloc/main.go:58`) so deferred cleanup runs — a bare handler skips it and breaks
`push.Stage`'s cleanup contract. Interrupted-but-clean paths exit 130.

- **HTTP 426 treated as anything other than a hard stop.** It is detected centrally
(`internal/api/client.go`, `parseUpgradeRequired` → `*UpgradeRequiredError`) so every caller
degrades to the same actionable "run `tracebloc upgrade`" message — see `auth.go:336`,
`doctor.go:119`, `client_status.go:129`, `delete.go:151,218`, `client.go:253`. Flag a new API
consumer that retries through it, frames it as a transient outage, or folds it into a generic
error. A too-old CLI never recovers by waiting, so `--wait` loops must fail fast on it.

- **Verification that degrades to a warning.** In `scripts/install.sh` the SHA256 compare
aborts when no hashing tool is present, and `verify_cosign_signature()` bootstraps a pinned,
checksum-verified cosign (`COSIGN_VERSION=v2.4.1`) rather than skipping; the only bypass is an
explicit `TRACEBLOC_ALLOW_UNVERIFIED=1` with a loud warning. A previous "warn + continue +
still print ✓ matches" branch was caught as *both* a security regression and a dishonest log.
Also flag any `--version` / `RELEASE_VERSION` use that skips `validate_version_tag` before URL
interpolation. `tracebloc upgrade` and host prep must keep delegating to this verified script
instead of reimplementing verification in Go.

- **An external call with no ceiling.** Backend HTTP: `defaultTimeout = 30 * time.Second`
(`internal/api/client.go:31`). In-cluster submit: `SubmitTimeout`
(`internal/submit/client.go:21`). Doctor probes: `httpProbeTimeout = 8s`. Every shell-out uses
`exec.CommandContext`. Flag a bare `exec.Command` in non-test code, an `http.Client{}` with no
`Timeout`, or a watch/poll loop with no deadline.

- **A missing empty / nil / zero guard on anything crossing a boundary** (user input, API
response, cluster state). There is no shared validator — the convention is a colocated
`validate*` func: `internal/push/spec.go:100` (`ValidateTableName`),
`internal/cli/interactive.go:537-568`. Two specifics: a bare Enter yields `""` and must not be
treated as a real path (`validateDatasetPath` documents exactly this); and pagination must
fail loudly on an unparseable `next` link rather than silently truncating the list
(`internal/api/client.go`, `nextPath`). Where "empty" and "unknown" are different answers,
prefer a three-valued return (`internal/cluster/discover.go:302`).

- **A cross-repo contract change that only lands on one side.** `scripts/.data-ingestors-ref`,
`scripts/.client-ref` and `scripts/.backend-ref` pin upstream refs deliberately so an
unrelated upstream commit can't red every open PR. Flag a hand-edit to a generated artifact
(`internal/schema/*.json`, `internal/api/testdata/*.json`,
`internal/push/testdata/parity/goldens.json`, `internal/cli/testdata/golden/*.golden`) that
doesn't also bump and re-sync its pin, and any change to a chart assumption (discovery labels,
jobs-manager port, PVC mount path) that doesn't update `scripts/chart-invariants` — a chart
rename otherwise ships green in both repos and breaks discovery in the field.

- **Output that breaks the style contract** (`STYLE.md`): all colour goes through
`internal/ui`'s Printer — never inline an escape or brand hex outside `internal/ui`
(`scripts/check-style.sh` greps for it). Colour is never load-bearing: headings carry bold,
alerts carry a glyph, so the output still reads under `NO_COLOR`, in a pipe, and for a
colour-blind reader. User-facing copy follows the terminology table ("secure environment",
"ingest", "delete", "Online/Offline", "collaborators", "task"); only the workspace → secure
environment swap is grep-enforced, the rest is review judgement. A new user-facing string
almost always needs its golden regenerated:
`TB_UPDATE_GOLDEN=1 go test ./internal/cli/ -run TestCopyCatalog`.

- **Errors that lose their type.** `%w` wrapping is the house convention (~325 sites), with
typed errors for the cases callers branch on: `APIError`, `UpgradeRequiredError`,
`SubmitError`, `WatchError`, `exitError`, `noParentReleaseError`. Flag string-matching on an
error message where `errors.Is`/`errors.As` applies.

## Known non-issues — do not flag

- **`.golangci.yml` does not gate CI.** `golangci-lint` is never invoked in a workflow (its
`staticcheck`/`unused` are disabled there for runner OOM reasons); the blocking Lint job runs
pinned standalone binaries — `errcheck`, `gofmt -s`, `goimports`, `ineffassign`, `misspell`,
`staticcheck`, plus `deadcode-check.sh`, `file-budget.sh`, `check-style.sh`. Don't infer
coverage from that file.
- **`staticcheck` runs `-checks all,-ST1005` deliberately** — do not flag error-string
capitalisation or punctuation. It is a tracked, intentional exclusion (cli#279).
- `internal/submit/client.go:78` — `InsecureSkipVerify` is intentional for cluster-internal
traffic with no recognisable CA, documented in place and marked `//nolint:gosec`. It is the
only `nolint` in the repo.
- `scripts/deadcode-allowlist.txt` entries are verified false positives (Stringers reached only
through `fmt` reflection; test-only parity harnesses that must live in production source).
- `test/integration/*` uses 30s–5min timeouts because it drives a real cluster — not the
production timeout convention.
- `mutation.yml` and `head-drift-canary.yml` are advisory and never gate a merge.
- No `vendor/` directory — the module cache is used on purpose.
- `// style-guard: allow` is a defined escape hatch but is currently used nowhere; if one
appears, it is a novel exception worth scrutiny rather than an accepted pattern.

## Tone

Direct. Name the file and line. Give a concrete fix, not "consider". State the customer-visible
consequence — what they see, and which exit code they get — not just the code smell.

This repo is **public**: never put a customer name, internal hostname, or internal-only ticket
detail in a finding. A bare `tracebloc/backend#NNNN` reference is fine.
3 changes: 2 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!-- 1–3 sentences. What does this PR do and why? -->

## Related
<!-- Closes #123 / Ref tracebloc/other-repo#456 -->
<!-- Same repo: Closes #123 · Cross-repo: Fixes tracebloc/backend#456 (owner-qualified — a bare backend#456 closes nothing). PRs land on develop, not the default branch, so confirm the issue actually closed. -->

## Type of change
- [ ] Feature
Expand All @@ -19,3 +19,4 @@
- [ ] `go build ./...`, `go vet`, and the Lint job's checks pass locally
- [ ] Terminal output follows [STYLE.md](../STYLE.md) — Printer tones (no hardcoded colour/emoji), "secure environment" not "workspace"; `bash scripts/check-style.sh` passes
- [ ] No secrets / credentials in the diff
- [ ] Cross-repo issues use `Fixes tracebloc/<repo>#N` — a bare `repo#N` closes nothing
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ This repo follows the same conventions as the rest of the tracebloc org:
fix(#150): handle empty kubeconfig gracefully
```

- **PR body** should include a `Closes #N` line (on its own line) for any ticket the PR fully resolves. GitHub auto-closes the issue on merge. The `feat(#N):` convention in the title is for kanban tracking; `Closes #N` in the body is what triggers auto-close.
- **PR body** should include a `Closes #N` line (on its own line) for any ticket the PR fully resolves. The `feat(#N):` convention in the title is for kanban tracking; the body line is what links the issue. Owner-qualify anything in another repo — `Fixes tracebloc/backend#123` — because a bare `backend#123` only cross-references and closes nothing. And since GitHub fires closing keywords only on merges into the default branch (`main`), a PR merged to `develop` won't auto-close on its own: confirm the ticket, and close it by hand if needed.

- **One PR per ticket** when practical. Roll-up sync PRs (`Sync develop → main for vX.Y.Z release`) are an exception.

Expand Down
16 changes: 14 additions & 2 deletions cmd/tracebloc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,23 @@ func main() {
syscall.SIGINT, syscall.SIGTERM)
defer stop()

err := cli.NewRootCmd(cli.BuildInfo{
executed, err := cli.NewRootCmd(cli.BuildInfo{
Version: version,
GitSHA: gitSHA,
BuildDate: buildDate,
}).ExecuteContext(ctx)
}).ExecuteContextC(ctx)

// F1: after the command runs, a quiet once-a-day nudge if a newer release
// exists (best-effort; silent on dev builds, off a terminal, in CI, or with
// TRACEBLOC_NO_UPDATE_CHECK). Printed before the error handling so on success
// it's the last line, and on failure the error stays the most prominent one.
// Skipped right after `tracebloc upgrade`: that command swaps this very
// binary, so the still-running old process would otherwise nudge about the
// release it just installed.
if !cli.SkipUpdateNudge(executed) {
cli.MaybeNotifyUpdate(version, os.Stderr)
}
Comment thread
shujaatTracebloc marked this conversation as resolved.

if err == nil {
return
}
Expand Down
6 changes: 6 additions & 0 deletions docs/rfcs/0001-cli-auth-and-client-provisioning.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# RFC 0001 — Browser-based auth & one-command client provisioning

> **Qualified ID: `RFC-CLI-0001`.** Cite this document by its qualified ID, never as
> a bare "RFC 0001" — `backend` and `client` each have their own 0001. Note that
> most existing bare `RFC-0001` references in the `backend`, `cli` and `client`
> codebases mean **this** document. Org-wide index: `docs/rfcs/README.md` in
> `tracebloc/backend` (private).
>
> **Status: ACCEPTED — implemented.** The design in this RFC shipped in
> **CLI v0.4.0** ([cli#107](https://github.com/tracebloc/cli/pull/107)); the
> tracking epic ([cli#54](https://github.com/tracebloc/cli/issues/54)) is closed.
Expand Down
4 changes: 4 additions & 0 deletions docs/rfcs/0002-data-ingest-flow.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# RFC 0002 — `tracebloc data ingest`: flow, terminology & task taxonomy

> **Qualified ID: `RFC-CLI-0002`.** Cite this document by its qualified ID, never as
> a bare "RFC 0002" — `backend` also has a 0002 (platform cost & autoscaling).
> Org-wide index: `docs/rfcs/README.md` in `tracebloc/backend` (private).
>
> **Status: DRAFT — for discussion.** Owner: @LukasWodka. Last updated: 2026-07-07.
>
> This RFC captures the redesign of the `tracebloc data ingest` user
Expand Down
Loading
Loading