Skip to content

main: Fix installer issues and enhance first-run user experience#309

Merged
saadqbal merged 13 commits into
mainfrom
develop
Jul 7, 2026
Merged

main: Fix installer issues and enhance first-run user experience#309
saadqbal merged 13 commits into
mainfrom
develop

Conversation

@saadqbal

@saadqbal saadqbal commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Related

Type of change

  • Feature
  • Bug fix
  • Tech-debt / refactor
  • Docs
  • Security / hardening
  • Breaking change

Test plan

Screenshots / recordings

Deployment notes

Checklist

  • Tests added / updated and passing locally
  • Docs updated if behavior or config changed
  • No secrets / credentials in the diff
  • For security-sensitive paths: appropriate reviewer requested

Note

Medium Risk
Touches provisioning ownership gates and default ingestor version (runtime ingestion behavior); installer script changes are broad but heavily tested via new e2e and bats coverage.

Overview
Installer & first-run UX — Copy and summaries steer users toward tracebloc data ingest, collaborator wording, and a clearer post-connect “what’s next” flow. Bootstrap verification messaging is friendlier (RFC references trimmed). TRACEBLOC_TRAINING_RESOURCES sets spawned-job CPU/RAM in generated Helm values; CLI post-install messaging distinguishes “verified on PATH” vs “verified for new terminals” when the current shell hasn’t reloaded rc (#304).

Provisioning hardening (#303) — Shared detect_installed_client for Helm one-client checks. Before client create, refuse minting when a local client exists in a namespace the signed-in account doesn’t own (with a legacy tracebloc namespace exception). Carbon zone prompts use timezone-based detection and clearer client create failure surfacing.

Helm chart — Default spawned ingestor float tag 0.30.5 (aligns with jobs-manager task catalogue / client-runtime#162). resource-monitor gets tracebloc.proxyEnv for corporate egress (client-runtime#154).

CIpath-persist (distro matrix, fresh interactive shell PATH) and label-gated e2e-journey (k3d → CLI → stub release → cluster info + data validate). Installer workflow listens for PR labeled so the e2e label runs journey without a push. Helm CI drops redundant per-platform schema job; job names clarified. manifest.sha256 updated for touched scripts.

Reviewed by Cursor Bugbot for commit 90f6d19. Bugbot is set up for automated code reviews on this repo. Configure here.

saadqbal and others added 6 commits July 6, 2026 14:33
… can't find tracebloc (#305)

* fix(client#304): don't claim "verified on your PATH" when the current shell can't find tracebloc

_cli_on_fresh_path spawns fresh shells to prove the CLI persists into new
terminals. But the caller's current shell (the one running the installer)
predates the rc PATH edit when the binary lands in ~/.local/bin — so the
success branch printed "verified on your PATH" while the very next
`tracebloc …` the user typed there failed with command-not-found (#304).

Gate the clean "verified on your PATH" verdict on `has tracebloc` (current
shell resolves it too). When it persists for new terminals but this shell
can't see it yet, say so honestly and point to a new terminal / `source <rc>`
instead of re-appending the line the rc already carries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(supply-chain): regenerate manifest.sha256 for the CLI PATH-message fix

install-cli.sh changed; refresh the R8 signed-manifest checksum so
gen-manifest.sh --check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…one (#306)

* fix(client#303): refuse a second client BEFORE minting, so we don't orphan one

On the browser path, `tracebloc client create` mints a fresh client whenever
the backend can't match this cluster to a client in the signed-in account —
e.g. a pre-anchor client whose cluster_id is still null, or one owned by a
different account. The one-client-per-machine guard only ran later, in the
Helm step, so by the time it refused, a brand-new client was already
registered on the dashboard and would never install — an orphan (#303).

Add a pre-flight in provision_client, after sign-in and before the mint: if a
client is already installed locally (shared detect_installed_client probe,
now also used by the Helm guard) and the signed-in account demonstrably does
NOT own its namespace (via `client list`), refuse before calling create.

Namespace is the only stable join key — the local Helm release stores the UUID
clientId, `client list` shows the numeric id. The pre-flight fires only on a
positive "not yours"; a fresh machine, an owned local client (create adopts),
or an inconclusive list read all fall through to create's own idempotent
adopt/conflict handling, so the same-account re-run/upgrade path is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(supply-chain): regenerate manifest.sha256 for the provisioning pre-flight

install-client-helm.sh + provision.sh changed; refresh the R8 signed-manifest
checksums so gen-manifest.sh --check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(client#303): don't refuse a legacy tracebloc-namespace client the account owns (Bugbot)

The pre-flight keyed ownership on the local Helm release namespace via
`client list`. But a client installed under the legacy fixed `tracebloc`
namespace is listed on the dashboard under its minted slug, so `client list`
never shows `tracebloc` even for the account's OWN older client — a skew
install_client_helm reconciles by clientId (the reliable key `client list`
doesn't expose). The namespace-only check wrongly refused that valid re-run.

Refuse only when the absent namespace is a non-legacy slug (confident foreign);
for `tracebloc`, defer to `client create` (adopts if it's yours, 409s if truly
cross-account) and the Helm-step one-client guard, which both key on clientId.

Regenerates manifest.sha256 for the provision.sh change (R8).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… jobs (#280)

- The `schema` job re-rendered each platform's values to /dev/null — exactly
  what `template` already does (and `template` also kubeconform-validates the
  output), so `schema` added 4 matrix jobs of zero extra coverage. Removed.
  (Not a required status check — branch protection requires only Lint + Unit
  tests.)
- helm-ci's `lint`/`unittest` jobs were named "Lint"/"Unit tests", colliding
  with the same-named required checks that standard-checks.yml emits
  unconditionally on every PR. Renamed to "Helm lint"/"Helm unit tests" so the
  required contexts come unambiguously from standard-checks.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…aming, location fix, clearer errors) (#307)

* feat(installer): overhaul first-run UX — plainer output, location auto-detect, real errors

Line-by-line pass over the one-command install experience, driven by a real
production run that failed opaquely on a mistyped location zone ("berlin").

- Bootstrap: reframe supply-chain verification as plain-language reassurance
  (drop cosign/manifest/keyless jargon and the alarming "cosign not found"),
  lowercase brand, drop the redundant "Running… environment setup" line.
- Welcome banner: drop the inaccurate "nothing modified outside ~/.tracebloc
  and Docker" promise (the installer also brew-installs Docker + system deps);
  fix brand casing on the sudo prompt.
- Step 1: de-duplicate the Apple-Silicon messaging and warn on the real
  limitation (no GPU training on macOS) rather than on the chip itself.
- Step 3 (provision): auto-detect the carbon-reporting zone from the system
  timezone via the OS's own zone.tab (no network call, no embedded zone list
  to drift) and let the user Enter-to-accept / override / skip; surface the
  ACTUAL client-create failure inline (e.g. an unrecognized zone) instead of a
  generic "see the log".
- Step 4 + summary + PowerShell: advertise the shipped dataset commands
  (dataset push/rm → data ingest/delete).
- Drop internal ticket refs (RFC-0001 R8) that leaked into user-facing errors.

Tests: provision, bootstrap, install-cli, summary bats suites green (50/50).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(installer): copy spec interim — collaborators, client noun, goal-led next steps, location fixes

Interim slice of the installer UX v2 spec (full restructure gated on the
next CLI release):

- Banner: drop the vendor value-prop lines (bash + ps1) — the trust claim
  lives in the verified connected summary.
- vendors → other collaborators everywhere user-facing; training phrased
  as gated on the user's approval.
- Next steps rewritten around the goal: ingest data → create your use
  case (real /my-use-cases URL) → invite other collaborators; dashboard +
  CLI help demoted to footnotes.
- "Name this machine" → "Name this client" (entity noun rule).
- Location prompt: fix the live skip-path bug — the released CLI (v0.5.1)
  hard-requires --location when it can't prompt, so a blank/skip answer
  doomed the create with a buried error. Now: Enter accepts the detected
  zone; no detection → re-ask (3 tries) instead of minting a doomed
  request; unattended runs fall back to the detected zone silently.

Tests: 67/68 bats green (1 pre-existing environmental failure on
validate_config, identical on develop).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test+chore: sync manifest.sha256 and assertions to the v2 copy strings

- Regenerate scripts/manifest.sha256 (7 script hashes changed by the copy
  pass) — the Static analysis drift check diffs it against the tree.
- Update two bats assertions to the new user-facing strings: the no-name
  fatal now says 'name for this client' (noun rule) and the bootstrap
  happy path prints 'installer files verified'. Caught by Linux CI only:
  on macOS, bats silently passes failing [[ ]] assertions (bash 3.2
  errexit quirk), so local green was misleading.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(installer): harden location fallback + create-output temp path (review nits)

Two small robustness fixes from PR review, both in provision_client:

- Trim name/location BEFORE the silent auto-detect fallback. A
  whitespace-only location answer (spaces then Enter) was non-empty at
  the fallback check, so it slipped through untrimmed and only got
  emptied afterward — re-dooming the create with a buried error, the
  exact failure this PR fixes. Now it degrades to the detected zone.
- When mktemp is unavailable, capture client-create output inside the
  install dir (HOST_DATA_DIR, which we own) instead of a predictable
  world-writable /tmp path that is a symlink-clobber target under sudo.

Manifest regenerated for the provision.sh change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Asad Iqbal <asad.dsoft@gmail.com>
The generated values hardcoded RESOURCE_LIMITS/REQUESTS to
cpu=2,memory=8Gi with no way to override — the 'default configuration'
was frozen. One env knob now sets both (requests == limits, Guaranteed
QoS, matching client-runtime's lockstep default):

    TRACEBLOC_TRAINING_RESOURCES="cpu=4,memory=16Gi" bash <(curl … i.sh)

Groundwork for the `tracebloc resources` command (design approved by
Lukas 2026-07-06): that command becomes the friendly layer over the
same chart values; this is the install-time escape hatch.

+2 bats tests (override lands in generated values; default unchanged).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… cluster info -> push) (#214)

* test: add cross-repo fresh-shell last-mile E2E harness

Adds the two-leg install-journey harness that closes the gap which let a
PATH-persistence regression ship green: no existing test opens a fresh
shell after install and asserts the documented next command works.

Leg 1 — scripts/tests/path-persist.sh: runs in a plain distro container,
installs the tracebloc CLI via cli/install.sh (configurable ref via
TRACEBLOC_CLI_REF), then for each shell among bash/zsh/fish spawns a fresh
login AND non-login shell and asserts `command -v tracebloc` resolves and
`tracebloc version` runs. A fresh non-login bash reads ~/.bashrc (not
~/.profile), so this catches the whole PATH-persistence class — red on the
pre-fix installer, green on the fixed one.

Leg 2 — scripts/tests/e2e-journey.sh: extends the e2e-cluster pattern.
Brings the cluster up via create_cluster(), installs the CLI, applies a
credential-free stub matching the CLI's real discovery contract (a
*-jobs-manager Deployment with the chart's hallmark labels + an `ingestor`
ServiceAccount), points the kubeconfig context's namespace at it, and
asserts `tracebloc cluster info` succeeds AND resolves from a fresh shell,
then `dataset push --dry-run` on a tiny sample CSV. Long steps run under a
watchdog timeout so a hang fails instead of spinning. The context-on-default
namespace auto-discover sub-assertion is gated pending the CLI change.

CI: new path-persist job (distro matrix, like distro-prereqs, fail-fast
false) and e2e-journey job (amd64, nightly + `e2e` label only, mirroring
cli's e2e.yml gating). Both new scripts added to the static shellcheck list.

Part of #737.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: default install-journey harness to the public release installer

cli#61's PATH-persistence fix shipped in cli v0.3.1 and is in every
release since (verified: the served releases/latest install.sh persists
PREFIX to ~/.bashrc / ~/.zshrc / fish config). Switch both legs' default
TRACEBLOC_CLI_REF from the now-merged fix/install-path-persist branch raw
URL to https://github.com/tracebloc/cli/releases/latest/download/install.sh
and drop the TODO(cli#61) — the guard now exercises the shipped installer
and would go red if a future release regressed the PATH class.

Re-validated the e2e-journey stub against the current CLI discovery
contract (internal/cluster/discover.go @ v0.5.1): selector
app.kubernetes.io/name=client,app.kubernetes.io/managed-by=Helm, a
*-jobs-manager Deployment, instance/version labels, and the `ingestor`
SA for the cluster-info TokenRequest — unchanged, stub still valid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(ci): bootstrap bash before path-persist so alpine:3 actually runs

The path-persist matrix ran `docker run <distro> bash path-persist.sh` as the
container entrypoint. Alpine's base image ships only busybox `sh` — no bash — so
the container died at init (exit 127, "bash: executable file not found") before a
single line of the script ran. The Alpine cell was never testing the installer;
it was failing at container startup. The script's own apk-aware _pm_install can't
help — you need bash to reach the code that installs bash.

Wrap the entrypoint in a POSIX `sh -c` that ensures bash then execs the script.
`command -v bash` short-circuits on every glibc distro (bash already present); the
apk branch fires only on Alpine. Now the Alpine cell exercises the real installer.

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>
@saadqbal saadqbal self-assigned this Jul 7, 2026
Comment thread scripts/lib/summary.sh
… label trigger (#310) (#311)

The install-journey harness merged in #214 asserted nothing: guard()
captured `local rc=$?` inside the `then` branch of `if ! timeout ...`,
so rc read the NEGATED status (always 0). Every assertion runs under
guard, so set -e never tripped and the job printed E2E JOURNEY PASS even
when steps failed or hung. Reproduced: a step exiting 3 and a 124 hang
both returned 0.

- guard(): run the command, capture status via `|| rc=$?`, then act on
  it — a failed step now propagates, a 124 hang is treated as a hard
  failure.
- Add a negative-control self-test at startup: guard() must return
  non-zero for a command that exits non-zero, or the script refuses to
  run. Locks the vacuous-pass class so it can't regress silently
  (bash -n and shellcheck both passed on the buggy version).
- installer-tests.yaml: add timeout-minutes to path-persist (20) and
  e2e-journey (30) — every other job already had one; and add `labeled`
  to the pull_request trigger types so adding the `e2e` label starts a
  run immediately instead of on the next push.

Remaining from the #214 review (path-persist has no red/green signal)
is tracked separately on #310 — it's a genuine rewrite that needs CI
validation across the distro matrix.

Refs #310. Follow-up to #214 (review by @aptracebloc).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread scripts/lib/provision.sh
LukasWodka and others added 3 commits July 7, 2026 17:12
resource-monitor is the only backend-calling workload whose pod gets NO
proxy env from the chart - jobs-manager, pods-monitor, requests-proxy,
image-refresh, auto-upgrade and the egress check all include
tracebloc.proxyEnv already. Behind a corporate egress proxy its
api-token-auth and edge-device-heartbeat POSTs dial direct and fail, so
proxied clusters show stale/no node resource data.

One include, appended after the existing env list (the DaemonSet has a
fixed env list - no .Values.env passthrough - so the jobs-manager-style
exclusion dance does not apply). Renders nothing when HTTP_PROXY_HOST
is unset; NO_PROXY carries the cluster-internal ranges and the
kubernetes python client ignores ambient proxy env, so in-cluster API
calls are unaffected.

Companion to tracebloc/client-runtime#154 (shared ProxyConfig +
resource_monitor threading the split-key settings); with this include
alone, current images already work via requests' trust_env handling of
the URL-form vars.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ix + interactive per-shell (#310) (#313)

As merged in #214 the path-persist guard could not distinguish a fixed
installer from a broken one:

- It ran as root, so cli/install.sh landed the binary in /usr/local/bin
  (on every PATH). The installer only PERSISTS a PATH entry for an
  off-PATH prefix, so no rc was ever written and every shell resolved the
  binary trivially — green on a broken installer too.
- The login × non-login matrix mis-modelled 'open a new terminal': a
  non-interactive `bash -c` reads NO rc file, and a *login* bash reads
  ~/.profile, not the ~/.bashrc the installer writes on Linux.

Rewrite to actually exercise persistence:

- Pin INSTALL_PREFIX=$HOME/.local/bin (a $HOME dir the installer always
  persists, off the default root PATH). Precondition-check it's not
  already on PATH, else bail rather than assert nothing.
- install.sh persists to ONE rc keyed off $SHELL, so install once per
  shell with SHELL=bash/zsh/fish, then assert a fresh INTERACTIVE shell of
  that kind (bash -ic / zsh -ic / fish -ic) — the one that reads that rc.
- Per-shell negative control (bash/zsh): a fresh NON-interactive shell
  must NOT resolve the binary; if it does, the prefix leaked onto the base
  PATH and a pass would be meaningless. (Skipped for fish: fish_add_path
  writes a universal var and `fish -c` still sources config.fish.)

Verified locally: with an isolated binary name (the dev host has a real
tracebloc on PATH), a non-interactive shell does NOT resolve it while a
fresh interactive shell does + `version` runs. bash -n, shellcheck
(error + warning), actionlint all clean. The full distro × shell matrix
is CI-only (real cli binary download + per-distro shell installs).

Fixes #310. Follow-up to #214.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
jobs-manager validates ingest.yaml at submit time against a schema that now
tracks data-ingestors' newer categories (sentence_pair_classification,
embeddings, seq2seq, causal_language_modeling — client-runtime#162). Those
first ship in the ingestor 0.5 line (v0.5.6); the 0.3 float tag only
supports 11 categories. Leaving the spawned ingestor on 0.3 while the
submit schema accepts 15 re-opens the submit-vs-run drift bug: a customer
submitting one of the new categories passes validation, a Job is minted,
and the 0.3 image can't process it.

Point the default float tag at the 0.5 line so the spawned ingestor
supports the full catalogue the schema accepts. `0.5` auto-tracks 0.5.x
patches (imagePullPolicy=Always); a future 0.6 needs an explicit bump.

- values.yaml: images.ingestor.tag 0.3 -> 0.5 (+ rationale comment).
- jobs-manager-deployment.yaml: --reuse-values nil-guard fallback 0.3 -> 0.5
  so a pre-images.ingestor-key release upgraded with --reuse-values doesn't
  silently fall back to the drifting 0.3.
- jobs_manager_test.yaml: default-wiring assertion 0.3 -> 0.5.

Verified: `helm template` renders INGESTOR_IMAGE_TAG "0.5" on both the
default and nil-guard paths.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fcb89ef. Configure here.

Comment thread scripts/lib/install-client-helm.sh Outdated
divyasinghds added a commit that referenced this pull request Jul 7, 2026
…bugbot)

Address two Cursor Bugbot findings on the develop→main promotion (#309):

1. Whitespace bypasses the location requirement. In the no-timezone-detection
   path, `client_location` was checked with `[[ -n ]]` before trimming, so a
   whitespace-only answer broke the retry loop and skipped the "location is
   required" error, then fell through to a `client create` with no --location.
   Trim inside the prompt loops, before the non-empty checks (both the detected
   and no-detection paths).

2. mktemp failure skipped the client guard. detect_installed_client did
   `mktemp || return 0`, treating an environment error as "no client here" and
   silently skipping the Helm one-client guard and the #303 ownership check.
   Fall back to a path in a dir we own (same pattern as the create step) and
   only give up when there is genuinely nowhere to write.

Regenerated scripts/manifest.sha256 for the changed scripts.

NOTE: Bugbot's third finding ("Wrong CLI command in summary" — claiming
`tracebloc data ingest` should be `tracebloc dataset push`) is a FALSE POSITIVE
and is intentionally NOT changed. Per tracebloc/cli internal/cli/data.go, the
canonical command group is `data` with subcommand `ingest`; `dataset`/`push`
are deprecated aliases kept for one deprecation cycle. The summary's
`tracebloc data ingest ./data` is the correct, current form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…bugbot) (#315)

Address two Cursor Bugbot findings on the develop→main promotion (#309):

1. Whitespace bypasses the location requirement. In the no-timezone-detection
   path, `client_location` was checked with `[[ -n ]]` before trimming, so a
   whitespace-only answer broke the retry loop and skipped the "location is
   required" error, then fell through to a `client create` with no --location.
   Trim inside the prompt loops, before the non-empty checks (both the detected
   and no-detection paths).

2. mktemp failure skipped the client guard. detect_installed_client did
   `mktemp || return 0`, treating an environment error as "no client here" and
   silently skipping the Helm one-client guard and the #303 ownership check.
   Fall back to a path in a dir we own (same pattern as the create step) and
   only give up when there is genuinely nowhere to write.

Regenerated scripts/manifest.sha256 for the changed scripts.

NOTE: Bugbot's third finding ("Wrong CLI command in summary" — claiming
`tracebloc data ingest` should be `tracebloc dataset push`) is a FALSE POSITIVE
and is intentionally NOT changed. Per tracebloc/cli internal/cli/data.go, the
canonical command group is `data` with subcommand `ingest`; `dataset`/`push`
are deprecated aliases kept for one deprecation cycle. The summary's
`tracebloc data ingest ./data` is the correct, current form.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
saadqbal and others added 2 commits July 7, 2026 18:59
…316)

The guard() watchdog added in #310/#311 wraps steps in `timeout`, which
execs an external command in a fresh process and cannot see shell
functions. create_cluster is a function sourced from cluster.sh, so
`guard 600 "create_cluster" -- create_cluster` failed with
"timeout: failed to run command 'create_cluster': No such file or
directory" (exit 127) before the cluster was ever created — failing the
E2E last-mile journey job.

Run create_cluster inside a real `bash` (which timeout CAN exec) that
re-sources the libs. Exported CLUSTER_NAME / TRACEBLOC_NO_AUTOSTART /
USER carry through the env, and the CLI binaries install_* dropped into
/usr/local/bin are on the inherited PATH.

Refs #310

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… --dry-run (#318)

Step 4 only ran for real once #316 fixed the create_cluster exit-127
regression (the old vacuous guard() and then the step-1 crash had hidden
it). It failed with "table name is required (set --table)".

`dataset push --dry-run` is no longer an offline check: the current CLI
runs cluster discovery AND a Bound-PVC check (resolveClusterTarget with
needPVC=true) before its dry-run stop, and this harness's credential-free
stub creates no PVC — so that path can't pass here regardless of flags.

Switch to `tracebloc data validate <spec>`, the CLI's purpose-built
offline schema check (validates against the embedded ingest.v1.json, no
cluster) — exactly the "offline-validatable, no creds, no reachable
platform" smoke the step was always meant to be. Assert both directions
(valid spec passes, invalid spec rejected) so a green run isn't vacuous,
mirroring the guard() self-test. Spec shape matches the CLI's own
known-good fixture (testdata/smoke/valid-image-classification.yaml).

Verified against the real released binary (tracebloc/cli v0.6.0, which
releases/latest resolves to): valid -> exit 0, missing-intent -> exit 2.

Closes #317. Follow-up to #316 / #310.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@saadqbal saadqbal merged commit 491f534 into main Jul 7, 2026
69 of 70 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants