chore: back-merge main → develop (reset promote ancestry after v0.6.0)#161
Merged
Conversation
Sync develop → main for v0.1.0-alpha
* feat(#26): installer-style terminal UX package (internal/ui) (#32) * feat(#26): add internal/ui installer-style terminal UX package Printer renders colored step/✔/⚠/· output matching the tracebloc/client installer (scripts/lib/common.sh). TTY+NO_COLOR auto-detect with a WithColor override (functional-options); fatih/color forced per-instance so it's testable on a buffer. Helpers: Banner, Step, Successf, Warnf, Infof, Errorf, Hintf, PromptHeader. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#26): render dataset push pre-flight via internal/ui + add --plain Adds a persistent --plain root flag and a printerFor(cmd) helper, plus ui.Section/ui.Field. dataset push pre-flight now renders through the ui.Printer (colored Section/Field rows, ⚠ for the RWO warning) instead of ad-hoc fmt.Fprintf; behavior unchanged (the pinned RendersKeyFacts test still passes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#26): render cluster info via internal/ui cluster info now prints through the ui.Printer (Section/Field + a green ✔ ready line) instead of raw fmt.Fprintf, matching the dataset push pre-flight. Test updated for the *ui.Printer signature (still asserts exit-3 on a bad kubeconfig). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#27): installer-style ingestion summary via internal/ui (#33) Replaces the box-drawing RenderPanel with RenderSummary(p *ui.Printer, s): an outcome-colored headline (green clean / yellow skips / red failures) + Section/Field counts + a 'what's next' block. Dropping the Unicode box resolves the v0.2 plain-ASCII TODO. submit.Options gains Printer (nil → ui.New(Out)); dataset push threads its --plain-aware printer through. Parser untouched. RenderPanel tests → RenderSummary (basic shape, nil, table-driven outcome). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#28): interactive guided dataset push — engine + core prompts (PR-a) (#34) On a TTY (unless --no-input), dataset push now prompts for any missing core inputs (path, category, table, intent, label) before validation; flags already passed win. Introduces a prompter interface (survey-backed in prod, fake in tests) so the prompt-mapping is unit-testable without a pseudo-terminal. Args ExactArgs(1)->MaximumNArgs(1) so a bare command can prompt for the path; explicit path-required error otherwise. MLM skips the label prompt; the table prompt reuses push.ValidateTableName. PR-b adds category-specific prompts + a confirm screen. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#28): category-specific prompts + confirm screen (PR-b) (#35) Completes the guided dataset push. After the core prompts, asks only what each category needs: image resolution (blank=auto-detect), required keypoint count, tabular schema (blank=infer) + regression label-policy + time-column. Then a Review screen + 'Proceed?' confirm — shown only when something was actually prompted, so full-flag pushes aren't nagged. Declining or Ctrl-C (survey terminal.InterruptErr, translated to errInteractiveCancelled at the prompter boundary) exits cleanly (0). Prompter interface gains Confirm; fake-prompter tests cover keypoint, tabular-regression, and cancel. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#29): branded home screen + cluster info banner (PR-a) (#36) Bare 'tracebloc' (no subcommand) now renders a branded home screen — ui.Banner + a 'get started' command list — via root.RunE, instead of cobra's raw usage dump; subcommands and --help are unaffected. cluster info gains a Banner header. PR-a of #29; --output-json follows in PR-b (it needs runDatasetPush to return a result struct + JSON tags on Summary, so it's its own change). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#29): --output-json for dataset push (PR-b) (#38) * feat(#29): --output-json for dataset push (PR-b) --output-json emits a machine-readable JSON result on stdout and routes all human output (preflight, logs, summary) to stderr, so 'push ... --output-json | jq' is clean. Implies non-interactive. JSON is emitted at the dry-run stop (status=dry-run) and after submit (status=succeeded/failed/detached/unknown + summary counts). The wire shape (pushJSONResult/pushJSONSummary) lives in the CLI layer so submit.Summary stays json-tag-free. Adds printerForWriter for the stderr routing; unit-tested via TestWritePushJSON. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#29): align --output-json status with exit code + emit JSON on errors classifyPushOutcome maps submit.Run's result to a (status, exitError) pair in lockstep: JobOutcomeSucceeded + summary.HasFailures() now yields status "completed_with_failures" + exit 9 (was "succeeded" while exiting 9). --output-json now emits exactly one result object on EVERY path — including auth/submit/watch errors (with job namespace+name when available) — instead of nothing on error. Adds TestClassifyPushOutcome. Addresses Bugbot on #38. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#37): expand ~ in dataset path (interactive prompt + literal arg) (#41) A leading ~ / ~/… in the dataset path now resolves under $HOME. The shell only expands an *unquoted* ~ on the command line; a path typed at the interactive prompt (#28) or a quoted/literal ~ positional arg reaches the CLI literally, and filepath.Abs would just prepend the CWD (.../cwd/~/...). Adds expandHome (mirrors cluster.expandPath) and applies it in runDatasetPush right after a.LocalPath is resolved — before any push.Discover* call — so both entry points are covered in one place. Relative/absolute/empty paths pass through untouched. Deferred the optional in-prompt existence validator: it would couple the prompt-mapping unit tests to real directories on disk. Filed nothing — noted in the PR. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#30): dataset rm — in-cluster teardown (table + PVC) (#40) Adds 'tracebloc dataset rm <table>': the in-cluster teardown of a pushed dataset, packaging the manual kubectl-exec cleanup. Discovers the cluster (parent release + shared PVC), shows the teardown plan, confirms (reuses the #28 prompter; --yes to skip, --dry-run to preview), then DROPs the MySQL table (exec into the mysql pod, using its own $MYSQL_ROOT_PASSWORD so no credential transits the CLI) and rm -rf's the PVC dirs (exec into the jobs-manager pod). Exit codes mirror push (2 bad name, 3 kubeconfig/refused, 4 no release/PVC, 7 teardown failed). Does NOT remove the central backend catalog entry — the CLI has no direct line to that backend; tracked as the cross-repo follow-up #39. The exec-into-pods mechanism (CLI-direct) vs a server-side jobs-manager delete endpoint is the open architecture question — see the DESIGN NOTE on push.Teardown. Hence: draft, pending team-lead review. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(release): multi-arch v0.1.0 — add linux/386, linux/arm, windows/arm64 (#43) Widen the release build matrix from 5 to 8 targets for the first stable v0.1.0: add linux/386, linux/arm (GOARM=6, covers armv6 + armv7), and windows/arm64. macOS amd64/arm64 and linux/windows amd64/arm64 unchanged. - install.sh detect_arch: learn i386|i686 -> 386 and armv6l|armv7l|armv8l|armhf -> arm (else auto-install breaks on them). - install.ps1 Get-Arch: return arm64 (was a hard fail) and prefer PROCESSOR_ARCHITEW6432 so x64-emulated PowerShell on a Windows-ARM host still picks the native arm64 build. - README + RELEASE_CHECKLIST: reflect the 8-platform stable release; the releases/latest/download/install.sh one-liner now resolves (a plain v0.1.0 tag is non-prerelease per release.yml's gate, so it is `latest`). Verified: all 8 targets cross-compile (CGO_ENABLED=0) to the right arch; release.yml YAML + install.ps1 parse clean. No Go source changed. Refs #42 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(#31): friendlier dataset push — prose intro, guided steps, per-prompt examples (#44) * feat(#31): friendlier dataset push — prose intro + guided 4-step flow Replaces the terse bullet header with a plain-English explainer (grounded in docs.tracebloc.io: 'your workspace', 'the cluster your workspace was installed on', 'your data stays on that cluster'), via a new ui.Printer.Para for prose. Restructures the push into a clearly-numbered, narrated 4-step flow — Check your dataset / Connect to your cluster / Stage your files / Run the ingestion — each with a one-line how/why for medium-technical users (steps 1-2 visible in --dry-run, 3-4 live). Splits printPushPreflight into printLocalSummary + printClusterSummary so each sits under its step; the dry-run stop now names the live-only steps it skipped. dataset rm: drop the backend-catalog clause from the runtime warning (now just 'Destructive and cannot be undone.'); --help NOTE + #39 tracking unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#31): per-prompt guidance + examples in interactive push Each interactive input prompt now shows a visible hint with an example above it — previously this guidance was hidden behind survey's '?' key. Covers the core prompts (path, table, intent, label column) and the category-specific ones (keypoint count, image resolution, tabular schema, regression label-policy, time column). Rendered via a new ui.PromptHint: a leading blank line for separation + cyan text, so per-field guidance stands out and reads distinctly from the dim generic hints (e.g. the 'Press Enter' meta-line). Verified by TestRunInteractive_ShowsExampleHints, which drives runInteractive with a buffer-backed Printer — no TTY needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(build): compile-check all 8 release targets in build.yml (#46) release.yml builds 8 targets but build.yml (PR CI) only compiled 5, so a break in linux/386, linux/arm, or windows/arm64 wouldn't surface until a release tag. Bring build.yml's matrix in lock-step: add linux/386, linux/arm (GOARM=6), windows/arm64. Compile-only (the native linux/amd64 smoke step is unchanged). Closes #45 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(#31): add dataset rm to home screen + in-run guidance (#47) The bare-'tracebloc' home screen now lists 'dataset rm' alongside push/cluster-info/ingest-validate. The rm command itself gains the same guidance treatment as push: a plain-English Para intro after the banner (what it removes — table + files — and that it can't be undone), and a cyan PromptHint above the confirmation. Home-screen test asserts 'dataset rm' appears. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: prep README for v0.2.0 (#50) - Status → v0.2.0 (latest stable; builds on v0.1.0 with the friendlier guided dataset push + dataset rm on the home screen). - Reconcile the Roadmap: "Next (v0.2)" framed v0.2 as the cloud-sources milestone, but v0.2.0 ships UX polish — so cloud sources / segmentation / `list` move to a label-free "Next", and `rm` drops from the future verbs (it shipped in v0.1.0). - Phase-5 table link pinned to the v0.1.0 tag (text said v0.1.0 but linked to /latest, which now resolves to v0.2.0). Refs #48 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * fix: address Bugbot review on #49 (#51) - dataset push --output-json: emit a JSON error object on early-failure paths (validation, discovery, staging, token, port-forward), not just on dry-run/post-submit. runDatasetPush now uses a named return + a jsonEmitted flag + a defer, so '… --output-json | jq' always gets JSON instead of empty stdout on a failure. Adds Error/ExitCode to the result shape + writePushErrorJSON; covered by TestRunDatasetPush_OutputJSONEarlyFailureEmitsJSON. - dataset rm: on a partial teardown (DROP TABLE succeeded but the PVC file removal failed), the error now says so and points the user to re-run — both teardown ops are idempotent (DROP TABLE IF EXISTS / rm -rf), so a re-run completes cleanup rather than reporting a flat 'teardown failed'. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Asad Iqbal (Saadi) <asad.dsoft@gmail.com>
Sync develop → main — prepare v0.3.0
docs: sync v0.3.0 README to main
…#64) * fix(install.sh): persist PATH to the shell rc (parity with install.ps1) (#61) * fix(install.sh): persist PATH to the shell rc (parity with install.ps1) When the binary lands in the ~/.local/bin fallback (the normal unprivileged `curl | sh` case), the installer now appends the PATH entry to the rc file the user's shell actually reads — instead of only printing advice. Fixes the recurring "CLI still not in PATH after restarting the shell" ticket. install.ps1 already persists user PATH on Windows; this brings Unix to parity. The print-only behavior silently failed on Ubuntu: ~/.profile adds ~/.local/bin only at login and only if it already existed, but the installer creates it mid-session, so a new (non-login) terminal reading ~/.bashrc never saw it. - Detects the rc per shell + OS: zsh -> .zshrc, bash -> .bashrc (Linux) / .bash_profile (macOS login shell), fish -> config.fish, else .profile. - Idempotent (skips if the rc already references the dir); falls back to printing the line if the rc isn't writable. Validated: sh -n + bash -n; functional + idempotency + per-shell routing tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: add install troubleshooting guide + harden rc-write fallback Completes the install-PATH work tracked in #60. The installer change already on this branch fixes the Ubuntu "not on PATH after restart" report; this adds the troubleshooting doc + README link that #60 also asked for, plus a small hardening of the rc-write fallback. - docs/troubleshooting.md: command-not-found / PATH guide covering the ~/.local/bin fallback, login vs non-login shells (Linux + macOS), the /usr/local/bin alternative, the Windows new-window case, and SSH login shells. - README: link the guide from the install section. - install.sh: group the rc append as `{ …; } 2>/dev/null` so a read-only or unwritable rc no longer leaks a raw "Permission denied" line before the clean fallback message. Verified leak-free under dash + bash; all shell/OS routing and idempotency unchanged. Refs #60. 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> * feat(push): infer entirely-empty columns as nullable FLOAT + warn (#66) When InferSchema saw a column with no non-empty value in the sample it fell back to VARCHAR(255). An entirely-empty column then fails ingest: the data-ingestors string validator rejects the all-NULL column ("N non-string values"). Sparse panels (e.g. a proteomics matrix where an analyte is measured in zero samples) hit this on every empty column. Type such columns as a nullable FLOAT instead — the FLOAT validator accepts NULLs, so an all-empty column ingests cleanly — and return them in a new `empty` slice so `dataset push` warns which columns were empty (the user can --schema-override if a column is really text). Updates the renamed test + the InferSchema callers for the new return. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * fix(install.sh): tighten PATH idempotency + persist user-local PREFIX (Bugbot on #61) (#65) * fix(install.sh): persist user-local PREFIX even when on PATH; tighten rc idempotency Two Cursor Bugbot findings on #61's PATH-persistence block, both of which let the installer print a false "Added ... to your PATH" while a new shell still couldn't find the binary — the exact failure #61 set out to kill. 1. Loose idempotency: `grep -F "$PREFIX"` matched the directory anywhere in the rc, including a bare comment, so a stray mention set added=1 with no export written. Now strip comment lines, keep only real PATH=/fish_add_path lines, then fixed-match the prefix. 2. Session PATH skipped the rc: the outer `case ":$PATH:"` skipped rc persistence whenever $PREFIX was on PATH for the current shell, so a one-off `export` (which won't survive a new terminal) left the rc untouched. Branch on the install location instead — a user-local prefix (under $HOME) always persists; a system prefix (e.g. /usr/local/bin) is already on PATH for every shell and only gets a nudge if it isn't. Validated: sh -n / bash -n / dash -n, shellcheck clean, and a functional matrix over the extracted block (fresh, idempotent re-run, comment-only, unrelated PYTHONPATH= line, system-prefix nudge, session-on-PATH persist, plus zsh/fish/macOS-bash routing) — 9/9. Refs #61 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install.sh): tolerate a trailing slash in $HOME for the user-local match Bugbot on #65: `case "$PREFIX" in "$HOME"/*)` misclassifies a user-local prefix as a system one when $HOME ends in a slash (e.g. HOME=/home/u/ with --prefix /home/u/.local/bin -> pattern /home/u//* never matches the single- slash path), so rc persistence is skipped. Normalize with home_dir="${HOME%/}" (POSIX suffix strip) before matching. Functional matrix extended with a trailing-slash-HOME case — 10/10; sh/bash/ dash -n + shellcheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install.sh): accurate present/added/failed messaging; recognize PATH+= and zsh path+=() Two more Bugbot findings on #65: - The user-local branch's failure message claimed "$PREFIX is not on $PATH", which is wrong now that the branch runs regardless of session PATH (a one-off export could already include it). Replaced the added/else pair with explicit present/added/failed states so each message is accurate; the failed case no longer asserts PATH state. - The idempotency filter only matched PATH=/fish_add_path, missing the PATH+= append idiom (and zsh's path+=() array), so a manual entry of that form wasn't recognized and a re-run could append a duplicate block. Broadened to case-insensitive (^|[^A-Za-z_])path[+]?= (still excludes PYTHONPATH=/MYPATH=). Functional matrix now 13/13 (adds PATH+=, zsh path+=(), persist-failure message); sh/bash/dash -n + shellcheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(install.sh): persist any off-PATH prefix to rc, not just $HOME-local ones Bugbot on #65: keying rc persistence on `case "$PREFIX" in "$home_dir"/*)` dropped writable non-$HOME targets (e.g. `--prefix /opt/tracebloc`) into the system branch, which only nudges — so a custom off-PATH install no longer persisted and a new terminal couldn't find tracebloc. cli#61's original code persisted ANY off-PATH prefix. Compute a `persist` decision instead: yes when the prefix is under $HOME (always — survives a new terminal even if a session export already has it) or when it's a non-$HOME prefix not on the current $PATH (e.g. /opt/...); no for a non-$HOME prefix already on PATH (the default /usr/local/bin needs nothing). One rc-writing block then handles every persist case. Functional matrix 14/14 (adds off-PATH /opt prefix persists; default system prefix on PATH stays silent); sh/bash/dash -n + shellcheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: lukasWuttke <54042461+LukasWodka@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
release: promote develop → main (v0.4.0 — auth + client provisioning, cluster doctor) (#106)
* Merge pull request #111 from tracebloc/sec/rfc-0001-r8-mandatory-verify fix(security): mandatory / fail-closed cosign verification in the CLI installer (RFC-0001 R8) * fix: validate COSIGN_VERSION and enforce TLS 1.2 on installer downloads (#114) Bugbot (promotion PR #113), two findings in scripts/install.sh: - Unvalidated cosign version in URL: COSIGN_VERSION (env-overridable) was interpolated into the Sigstore download URL without the semver/path-traversal gate applied to RELEASE_VERSION. Generalized validate_tag into validate_version_tag and apply it to COSIGN_VERSION before building the URL. (scripts/install.sh:45) - Signature download curls omit TLS: the .sig/.cert fetches (and the binary, SHA256SUMS, and latest-redirect curls) lacked --tlsv1.2, unlike the new cosign bootstrap fetches. Add --tlsv1.2 to every security-sensitive download. (scripts/install.sh:335) Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * chore(schema): re-vendor ingest.v1.json — register seq2seq as recognize-but-unsupported (#118) The CI "Schema drift check" (scripts/sync-schema.sh --check) was failing on develop itself and therefore every PR — the vendored internal/schema/ingest.v1.json had drifted from data-ingestors master, which added the `seq2seq` task category (enum entry + an if/then requiring `texts`, membership in the self-supervised text group, and an updated `texts` description). The drift is purely additive — it does not change validation for any already-supported category. - Re-vendored the schema (sync-schema.sh) → --check now passes. - Registered seq2seq in internal/push/category.go as recognize-but-not-yet- CLI-supported (CLISupported:false + UnsupportedNote), Family text: the CLI's discover/build for its raw-.txt / source\ttarget `texts` layout isn't implemented, so push reports it as pending rather than leaving a schema<->registry gap (the cli#74 drift class that TestRegistryCoversSchema- Categories pins). Updated the parity tests. Mirrors #103, which did exactly this for causal_language_modeling. Full seq2seq push support (discover/build, flip CLISupported) is a follow-up feature — the sibling of cli#105. go build / vet / test ./... green; drift check green. Closes #117 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(auth): send User-Agent version header + handle 426 Upgrade Required (cli#98) (#115) RFC-0001 §13 / §14 R11 / Appendix C.1 — the CLI half of the minimum-supported-CLI-version handshake (server half: backend#888, shipped in backend#916). - Every backend request now carries `User-Agent: tracebloc-cli/<ver> (<os>/<arch>)`, injected by a transport wrapper on the shared internal/api client so login + provisioning are both covered without threading the version through each command. The version is the ldflags build info, recorded once via api.SetUserAgent in NewRootCmd; a build with no version reports "dev" (unparseable server-side → fails open). - A 426 from any endpoint is detected centrally in post/get and surfaced as a typed *UpgradeRequiredError carrying min_version, so every command degrades to the same actionable "your CLI is too old — upgrade to >= X" message and a clean non-zero exit (no stack trace), in both interactive and --plain modes. Tests: UA on the wire, dev fallback, 426 → UpgradeRequiredError (GET + POST), unparseable-426 body, message actionability. Closes #98. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(auth): logout revokes the token server-side (cli#112) (#116) * feat(auth): logout revokes the token server-side via POST /auth/revoke (cli#112) RFC-0001 §7.5 / R2 — the CLI half of the logout-revoke gap (server endpoint: backend#887, shipped in backend#903). Until now `logout` was local-only: it cleared ~/.tracebloc but a copied/leaked token kept authenticating (confirmed in the 2026-06-25 connect-flow FR). - New api.Client.RevokeToken → POST /auth/revoke (Bearer in, 204 out, idempotent); a non-2xx surfaces as *APIError. - logout now revokes server-side before clearing local state. Best-effort by contract: on failure (offline / already-revoked / 5xx) it logs a hint and still clears local state — the user must always be able to log out locally. Tests: RevokeToken 204→nil (Bearer + POST on the wire) and 5xx→APIError; logout calls revoke (now routed at a stub — it was about to hit real prod) and still clears local state when the revoke fails. Closes #112. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(auth): address Bugbot on logout revoke — env resolution + save ordering (cli#112) Two Medium findings on the revoke wiring: - Resolve the revoke env consistently with authedClient via a new shared sessionEnv(cfg) helper (saved env → $CLIENT_ENV → prod), instead of hardcoding prod for an empty cfg.Env — which could revoke against the wrong host and leave the real session token valid after sign-out. - Clear + persist local state BEFORE the revoke call, so a failed Save can't leave a token that's already been revoked server-side on disk as a broken "signed in" state. Revoke stays best-effort, after the local clear. Test: TestLogout_RevokesAgainstSessionEnv pins empty cfg.Env → $CLIENT_ENV. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): rename dataset → data (ingest/list/delete; keep aliases) (cli#99) (#119) Rename the `dataset` command group to `data` and its verbs: - `push` → `ingest` (canonical: `tracebloc data ingest <path>`) - `rm` → `delete` (canonical: `tracebloc data delete <table>`) - `list` stays `list` Deprecated aliases retained for one cycle via cobra's Aliases field: - `data` has `Aliases: []string{"dataset"}` - `ingest` has `Aliases: []string{"push"}` - `delete` has `Aliases: []string{"rm"}` All user-facing text updated (Short, Long, Banner, home screen, examples). Internal Go symbols renamed accordingly (newDataCmd, newDataIngestCmd, newDataDeleteCmd, runDataIngestArgs, runDataIngest, runDataDelete, etc.). Tests updated to use canonical names; alias-resolution tests added. No behaviour change beyond naming; ingestion logic and cli#67–#77 fixes are untouched. The separate top-level `ingest validate` command is unmodified. Closes #99 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): env-scoped config v2 — per-env profiles fix the cross-env clobber (cli#100) (#120) RFC-0001 §7.5 / §14 R10 / Appendix C.8. The v1 ~/.tracebloc/config.json held a single flat {env,token,active_client_id}, so `login --env X` overwrote env+token but stranded the previous env's active_client_id — a dev/stg/prod user could silently target the wrong client. - Config is now v2: { version, current_env, profiles: { <env>: {email, token, expires_at, active_client_id} } }. One active_client_id PER env. - `login --env X` switches current_env and writes X's profile via Profile(env) (which returns X's existing profile), so a re-login preserves its active_client_id instead of clobbering it; other envs are untouched. - v1 files auto-migrate to v2 on first read (the single record wrapped under profiles[env]); the first Save rewrites the file as v2. No data loss. - `logout` clears only the current env's profile; other envs untouched. (Still revokes server-side, best-effort — from cli#112.) - File stays 0600. Call sites (login / logout / auth status / client create·list·use) now read and write via cfg.Current() / Profile(env) / CurrentEnv. expires_at is persisted per profile and shown by `auth status` when present (login doesn't capture it yet). Tests: v1→v2 migration (+ empty-env→prod), the dev→prod→dev no-clobber (R10) guarantee, SignedIn semantics, 0600, and every login/logout/auth/client path on v2. Closes #100. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): --verbose + cluster doctor auth checks + resume hint & install log (cli#101) (#121) * feat(cli): --verbose + cluster doctor auth checks + resume hint & install log (cli#101) RFC-0001 §8.5 — make the zero-prompt connect flow's FAILURE path stand on its own (the installer-side streaming stays with backend#838, per the ticket). - `--verbose` / $TRACEBLOC_LOG_LEVEL: a root persistent flag that streams the device-flow → provision detail via a new verbose-gated ui.Detailf. Default output stays quiet (~the usual handful of ✔ lines). - `cluster doctor` now runs an "Auth & config" section FIRST — before the cluster checks, so it works even when no cluster is reachable (the failed-provision case): signed in? which env + account? active client set? plus a live token check (WhoAmI) — 401 → ✖ re-login, network error → ⚠. Its status folds into the overall verdict. - `client create` failure prints the exact, idempotent resume command (§7.2) + a `tracebloc cluster doctor` pointer, so a broken headless connect isn't a dead end. - Every `client create` run writes ~/.tracebloc/install-<ts>.log (0600) — a full trace on disk even when the terminal stayed quiet. Tests: ui Detailf gating; doctor auth (not-signed-in / valid / 401 / no-active-client); verbose-streams vs quiet-default; provision failure → resume command + install log. Closes #101. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): don't log a cancelled provision as "done" (cli#101, Bugbot) The success-path defer blanket-logged "done" on err==nil, so declining the confirm prompt (which returns nil after "Cancelled.") recorded a user abort as a successful run in install-<ts>.log — misleading for support / post-mortems. Log the terminal outcome at each branch instead (minted / adopted / cancelled); the defer now only handles the failure case. Test: TestClientCreate_CancelLogsCancelledNotDone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): resume command includes prompted name/location, not just flags (cli#101, Bugbot) resumeCommand(opts) read opts, which carries only the flag values — so a failed interactive provision (name/location typed at a prompt) printed a resume command missing --name/--location, defeating the copy-paste-to-retry goal. Write the resolved values back into opts after gathering them, so the defer's resumeCommand reflects what the user actually entered. Test: TestClientCreate_ResumeCommandIncludesPromptedValues. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): doctor folds auth into the kubeconfig-exit code; install log advertises no path on open failure (cli#101, Bugbot) Two more Bugbot findings: - `cluster doctor` returned exit 3 on a kubeconfig load / clientset failure even when the auth section had failed (e.g. a 401), so automation could read a bad token as a kubeconfig-only problem. A failed auth section now escalates the exit to 2; exit 3 is kept for the auth-OK case (the documented contract). - newInstallLog returned the intended path even when opening the file failed, so the failure hint could print "Full log:" for a file that was never written. It now returns an empty path, and the caller's guard skips the hint. Tests: doctor kubeconfig-fail → exit 2 (auth fail) / exit 3 (auth OK); install log returns an empty path when the file can't be opened. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): cluster doctor treats a 426 as a hard upgrade failure, not a transient warning (Bugbot/#113) (#122) doctor's live token check (WhoAmI) treated every non-401 error as a transient "couldn't verify — check your network" warning. A 426 (the server enforces a newer CLI, surfaced as *api.UpgradeRequiredError by the #98 handling) is a definite, actionable problem — now reported as a hard ✖ "this CLI is too old — upgrade to >= X" failure with the min-version message. Test: TestRunAuthChecks_426IsHardFailure. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): don't misread a v2-shaped config as v1 (Bugbot/#113) (#123) Load routed any config with version < 2 to migrateV1, which reads only the flat v1 fields — so a v2-shaped file with a missing/wrong version would be migrated to an empty record, silently dropping its profiles. migrateV1 now fires only for a genuine v1 record (old version AND no `profiles` object); a profiles-bearing file is always parsed as v2. Test: TestLoadV2ShapedWithoutVersion_NotMigrated. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Asad Iqbal (Saadi) <asad.dsoft@gmail.com> Co-authored-by: shujaat_tracebloc <153823837+shujaatTracebloc@users.noreply.github.com> Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Merge pull request #111 from tracebloc/sec/rfc-0001-r8-mandatory-verify fix(security): mandatory / fail-closed cosign verification in the CLI installer (RFC-0001 R8) * fix: validate COSIGN_VERSION and enforce TLS 1.2 on installer downloads (#114) Bugbot (promotion PR #113), two findings in scripts/install.sh: - Unvalidated cosign version in URL: COSIGN_VERSION (env-overridable) was interpolated into the Sigstore download URL without the semver/path-traversal gate applied to RELEASE_VERSION. Generalized validate_tag into validate_version_tag and apply it to COSIGN_VERSION before building the URL. (scripts/install.sh:45) - Signature download curls omit TLS: the .sig/.cert fetches (and the binary, SHA256SUMS, and latest-redirect curls) lacked --tlsv1.2, unlike the new cosign bootstrap fetches. Add --tlsv1.2 to every security-sensitive download. (scripts/install.sh:335) Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * chore(schema): re-vendor ingest.v1.json — register seq2seq as recognize-but-unsupported (#118) The CI "Schema drift check" (scripts/sync-schema.sh --check) was failing on develop itself and therefore every PR — the vendored internal/schema/ingest.v1.json had drifted from data-ingestors master, which added the `seq2seq` task category (enum entry + an if/then requiring `texts`, membership in the self-supervised text group, and an updated `texts` description). The drift is purely additive — it does not change validation for any already-supported category. - Re-vendored the schema (sync-schema.sh) → --check now passes. - Registered seq2seq in internal/push/category.go as recognize-but-not-yet- CLI-supported (CLISupported:false + UnsupportedNote), Family text: the CLI's discover/build for its raw-.txt / source\ttarget `texts` layout isn't implemented, so push reports it as pending rather than leaving a schema<->registry gap (the cli#74 drift class that TestRegistryCoversSchema- Categories pins). Updated the parity tests. Mirrors #103, which did exactly this for causal_language_modeling. Full seq2seq push support (discover/build, flip CLISupported) is a follow-up feature — the sibling of cli#105. go build / vet / test ./... green; drift check green. Closes #117 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(auth): send User-Agent version header + handle 426 Upgrade Required (cli#98) (#115) RFC-0001 §13 / §14 R11 / Appendix C.1 — the CLI half of the minimum-supported-CLI-version handshake (server half: backend#888, shipped in backend#916). - Every backend request now carries `User-Agent: tracebloc-cli/<ver> (<os>/<arch>)`, injected by a transport wrapper on the shared internal/api client so login + provisioning are both covered without threading the version through each command. The version is the ldflags build info, recorded once via api.SetUserAgent in NewRootCmd; a build with no version reports "dev" (unparseable server-side → fails open). - A 426 from any endpoint is detected centrally in post/get and surfaced as a typed *UpgradeRequiredError carrying min_version, so every command degrades to the same actionable "your CLI is too old — upgrade to >= X" message and a clean non-zero exit (no stack trace), in both interactive and --plain modes. Tests: UA on the wire, dev fallback, 426 → UpgradeRequiredError (GET + POST), unparseable-426 body, message actionability. Closes #98. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(auth): logout revokes the token server-side (cli#112) (#116) * feat(auth): logout revokes the token server-side via POST /auth/revoke (cli#112) RFC-0001 §7.5 / R2 — the CLI half of the logout-revoke gap (server endpoint: backend#887, shipped in backend#903). Until now `logout` was local-only: it cleared ~/.tracebloc but a copied/leaked token kept authenticating (confirmed in the 2026-06-25 connect-flow FR). - New api.Client.RevokeToken → POST /auth/revoke (Bearer in, 204 out, idempotent); a non-2xx surfaces as *APIError. - logout now revokes server-side before clearing local state. Best-effort by contract: on failure (offline / already-revoked / 5xx) it logs a hint and still clears local state — the user must always be able to log out locally. Tests: RevokeToken 204→nil (Bearer + POST on the wire) and 5xx→APIError; logout calls revoke (now routed at a stub — it was about to hit real prod) and still clears local state when the revoke fails. Closes #112. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(auth): address Bugbot on logout revoke — env resolution + save ordering (cli#112) Two Medium findings on the revoke wiring: - Resolve the revoke env consistently with authedClient via a new shared sessionEnv(cfg) helper (saved env → $CLIENT_ENV → prod), instead of hardcoding prod for an empty cfg.Env — which could revoke against the wrong host and leave the real session token valid after sign-out. - Clear + persist local state BEFORE the revoke call, so a failed Save can't leave a token that's already been revoked server-side on disk as a broken "signed in" state. Revoke stays best-effort, after the local clear. Test: TestLogout_RevokesAgainstSessionEnv pins empty cfg.Env → $CLIENT_ENV. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): rename dataset → data (ingest/list/delete; keep aliases) (cli#99) (#119) Rename the `dataset` command group to `data` and its verbs: - `push` → `ingest` (canonical: `tracebloc data ingest <path>`) - `rm` → `delete` (canonical: `tracebloc data delete <table>`) - `list` stays `list` Deprecated aliases retained for one cycle via cobra's Aliases field: - `data` has `Aliases: []string{"dataset"}` - `ingest` has `Aliases: []string{"push"}` - `delete` has `Aliases: []string{"rm"}` All user-facing text updated (Short, Long, Banner, home screen, examples). Internal Go symbols renamed accordingly (newDataCmd, newDataIngestCmd, newDataDeleteCmd, runDataIngestArgs, runDataIngest, runDataDelete, etc.). Tests updated to use canonical names; alias-resolution tests added. No behaviour change beyond naming; ingestion logic and cli#67–#77 fixes are untouched. The separate top-level `ingest validate` command is unmodified. Closes #99 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): env-scoped config v2 — per-env profiles fix the cross-env clobber (cli#100) (#120) RFC-0001 §7.5 / §14 R10 / Appendix C.8. The v1 ~/.tracebloc/config.json held a single flat {env,token,active_client_id}, so `login --env X` overwrote env+token but stranded the previous env's active_client_id — a dev/stg/prod user could silently target the wrong client. - Config is now v2: { version, current_env, profiles: { <env>: {email, token, expires_at, active_client_id} } }. One active_client_id PER env. - `login --env X` switches current_env and writes X's profile via Profile(env) (which returns X's existing profile), so a re-login preserves its active_client_id instead of clobbering it; other envs are untouched. - v1 files auto-migrate to v2 on first read (the single record wrapped under profiles[env]); the first Save rewrites the file as v2. No data loss. - `logout` clears only the current env's profile; other envs untouched. (Still revokes server-side, best-effort — from cli#112.) - File stays 0600. Call sites (login / logout / auth status / client create·list·use) now read and write via cfg.Current() / Profile(env) / CurrentEnv. expires_at is persisted per profile and shown by `auth status` when present (login doesn't capture it yet). Tests: v1→v2 migration (+ empty-env→prod), the dev→prod→dev no-clobber (R10) guarantee, SignedIn semantics, 0600, and every login/logout/auth/client path on v2. Closes #100. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): --verbose + cluster doctor auth checks + resume hint & install log (cli#101) (#121) * feat(cli): --verbose + cluster doctor auth checks + resume hint & install log (cli#101) RFC-0001 §8.5 — make the zero-prompt connect flow's FAILURE path stand on its own (the installer-side streaming stays with backend#838, per the ticket). - `--verbose` / $TRACEBLOC_LOG_LEVEL: a root persistent flag that streams the device-flow → provision detail via a new verbose-gated ui.Detailf. Default output stays quiet (~the usual handful of ✔ lines). - `cluster doctor` now runs an "Auth & config" section FIRST — before the cluster checks, so it works even when no cluster is reachable (the failed-provision case): signed in? which env + account? active client set? plus a live token check (WhoAmI) — 401 → ✖ re-login, network error → ⚠. Its status folds into the overall verdict. - `client create` failure prints the exact, idempotent resume command (§7.2) + a `tracebloc cluster doctor` pointer, so a broken headless connect isn't a dead end. - Every `client create` run writes ~/.tracebloc/install-<ts>.log (0600) — a full trace on disk even when the terminal stayed quiet. Tests: ui Detailf gating; doctor auth (not-signed-in / valid / 401 / no-active-client); verbose-streams vs quiet-default; provision failure → resume command + install log. Closes #101. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): don't log a cancelled provision as "done" (cli#101, Bugbot) The success-path defer blanket-logged "done" on err==nil, so declining the confirm prompt (which returns nil after "Cancelled.") recorded a user abort as a successful run in install-<ts>.log — misleading for support / post-mortems. Log the terminal outcome at each branch instead (minted / adopted / cancelled); the defer now only handles the failure case. Test: TestClientCreate_CancelLogsCancelledNotDone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): resume command includes prompted name/location, not just flags (cli#101, Bugbot) resumeCommand(opts) read opts, which carries only the flag values — so a failed interactive provision (name/location typed at a prompt) printed a resume command missing --name/--location, defeating the copy-paste-to-retry goal. Write the resolved values back into opts after gathering them, so the defer's resumeCommand reflects what the user actually entered. Test: TestClientCreate_ResumeCommandIncludesPromptedValues. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): doctor folds auth into the kubeconfig-exit code; install log advertises no path on open failure (cli#101, Bugbot) Two more Bugbot findings: - `cluster doctor` returned exit 3 on a kubeconfig load / clientset failure even when the auth section had failed (e.g. a 401), so automation could read a bad token as a kubeconfig-only problem. A failed auth section now escalates the exit to 2; exit 3 is kept for the auth-OK case (the documented contract). - newInstallLog returned the intended path even when opening the file failed, so the failure hint could print "Full log:" for a file that was never written. It now returns an empty path, and the caller's guard skips the hint. Tests: doctor kubeconfig-fail → exit 2 (auth fail) / exit 3 (auth OK); install log returns an empty path when the file can't be opened. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): cluster doctor treats a 426 as a hard upgrade failure, not a transient warning (Bugbot/#113) (#122) doctor's live token check (WhoAmI) treated every non-401 error as a transient "couldn't verify — check your network" warning. A 426 (the server enforces a newer CLI, surfaced as *api.UpgradeRequiredError by the #98 handling) is a definite, actionable problem — now reported as a hard ✖ "this CLI is too old — upgrade to >= X" failure with the min-version message. Test: TestRunAuthChecks_426IsHardFailure. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): don't misread a v2-shaped config as v1 (Bugbot/#113) (#123) Load routed any config with version < 2 to migrateV1, which reads only the flat v1 fields — so a v2-shaped file with a missing/wrong version would be migrated to an empty record, silently dropping its profiles. migrateV1 now fires only for a genuine v1 record (old version AND no `profiles` object); a profiles-bearing file is always parsed as v2. Test: TestLoadV2ShapedWithoutVersion_NotMigrated. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli#125): write the UUID username (not the numeric id) as TRACEBLOC_CLIENT_ID (#126) `client create` wrote strconv.Itoa(pc.ID) — the numeric dashboard id — as TRACEBLOC_CLIENT_ID in both credential-file paths (mint + adopt) and in the printed credential. But TRACEBLOC_CLIENT_ID is the *auth username*: it flows cred → helm clientId → secret CLIENT_ID → pod env → controller.py os.getenv("CLIENT_ID"), which is POSTed to api-token-auth as the login username. The backend authenticates an EdgeDevice by its UUID username (str(uuid4)), never the numeric id, so a freshly-provisioned client always failed auth ("Unable to log in with provided credentials") and crash-looped. Write pc.Username instead. The numeric id stays display-only (shown as "dashboard id"). Both credential-file tests asserted the buggy numeric id and are updated to require the username; the print-path test now asserts the username is shown as the client id. This is what client PR #293's skip-verify masked — verify_credentials was a correct canary. #293 will be reverted once this ships (v0.5.1). Refs: RFC-0001 backend#830, installer credential handoff #838. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Asad Iqbal (Saadi) <asad.dsoft@gmail.com> Co-authored-by: shujaat_tracebloc <153823837+shujaatTracebloc@users.noreply.github.com> Co-authored-by: shujaat hasan <shujaathasan@shujaats-MacBook-Pro.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
main carried no unique code — only prior promote snapshots — so develop is authoritative; conflicts resolved in favour of develop.
chore(release): promote develop → main (2026-07-07)
main's promote-merge commits (#148, #155) aren't in develop's ancestry, so the next develop→main promote would conflict on release drift again. Trees are already identical (v0.6.0 == develop), so this changes no files — it only brings main's history into develop so future promotes fast-forward cleanly.
aptracebloc
approved these changes
Jul 7, 2026
divyasinghds
approved these changes
Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Zero file changes — ancestry only. Merges
mainintodevelopso develop's history includes the promote-merge commits (#148, #155). Without this, those commits stay off develop and the nextdevelop → mainpromote conflicts on release drift again (exactly what #148 hit → had to re-cut as #155).Trees are already identical (
main@ v0.6.0 == develop), so this touches no files; it only realigns ancestry so future promotes fast-forward cleanly.Durable fix for the recurring drift flagged during the v0.6.0 release. Recommend adopting "back-merge main→develop after every promote" as the release runbook's last step.
Note
Low Risk
Zero file diff — only Git ancestry; no runtime, security, or application behavior impact.
Overview
No code or config changes — this PR only updates Git history by merging
mainintodevelopafter the v0.6.0 promote.mainanddevelopalready match at v0.6.0; the merge pulls promote commits (e.g. #148, #155) ontodevelopso they are not missing from that branch. That avoids the recurring release drift problem where the next promote conflicts because ancestry diverged.The intent is to make back-merge
main→developafter every promote the last step in the release runbook.Reviewed by Cursor Bugbot for commit 9ffa8b8. Bugbot is set up for automated code reviews on this repo. Configure here.