Skip to content

feat(status): surface the environment's seal-check verdict (client status --seal)#395

Open
LukasWodka wants to merge 4 commits into
developfrom
feat/seal-status-393
Open

feat(status): surface the environment's seal-check verdict (client status --seal)#395
LukasWodka wants to merge 4 commits into
developfrom
feat/seal-status-393

Conversation

@LukasWodka

@LukasWodka LukasWodka commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Closes #393. Companion to RFC-0003 §8.2 D12 (#366) and the chart-side seal-check suite work (backend#1184).

Summary

tracebloc client status --seal runs the chart's conformance checks (its helm-test hooks — the seal check) against this machine's secure environment and prints an honest verdict with per-check detail:

  Seal check — secure environment "lukas-macbook"

  ✗ egress-enforcement — job failed: BackoffLimitExceeded
      see why: kubectl logs -n lukas-macbook job/lukas-macbook-egress-enforcement-check
  ✓ backend-reachability

  ✖ Unsealed — 1 of 2 conformance checks failed. This environment's protections are not all enforced.
  Fix the failing checks above, then re-run `tracebloc client status --seal` to confirm the seal.
Verdict Meaning Exit
Sealed every conformance check passed 0
Unsealed a check failed or couldn't run — a protection is not enforced 2
Unknown the chart ships no conformance checks; nothing was verified — explicitly NOT sealed 2

(Exit 3 = kubeconfig/cluster unreachable, exit 4 = no tracebloc client found — the same contract as the data/resources commands.)

Design choices

  • Flag on client status, not a new command. The repo has no top-level status; its status command is tracebloc client status, and the seal is the conformance dimension of that status. --wait set the precedent for mode flags there. --seal is mutually exclusive with --wait, and the cluster-targeting flags (--kubeconfig/--context/--namespace) + --timeout are rejected when their mode isn't active rather than silently ignored.
  • Chart contract (internal/helm/seal.go) — aligned with the chart side's docs/SEAL-CHECK.md (backend#1184 branch security/seal-check-suite-1184): runnable test hooks (Jobs/Pods) labelled tracebloc.io/seal-check: "true" form the suite, and tracebloc.io/seal-check-name is each check's stable identifier (egress-enforcement, backend-reachability, storage-assertions). No labelled hooks → run all of the chart's runnable helm tests, with a visible fallback note (works against today's unlabelled probes until the chart PR merges). No runnable test hooks at all → unknown, honestly. A tracebloc.io/seal-hint annotation is a CLI-side optional channel for a per-check failure hint (labels can't carry sentences); absent, the hint falls back to a copy-pasteable kubectl logs pointer.
  • One helm test --filter name=<hook>,… per check, because helm stops a suite at the first failure — per-check invocation reports every check's state (the partially-degraded picture status: surface environment conformance (seal check) result #393 asks for), with the exit code as the unambiguous per-check signal. Each run's filter also carries the release's non-runnable test hooks (the storage check's SA/RBAC plumbing at negative hook-weight): helm's --filter excludes every unlisted test hook, so filtering to the check alone would strand it without its ServiceAccount and report a false Unsealed. Plumbing never counts as a check (a plumbing-only chart is still "unknown"). Hook names come from helm get hooks on the same release store the test action reads, so they can't drift.
  • Cluster resolution reuses resolveClusterTarget (§7.3 active-client binding, "runs on another machine" rewrite, discovered release name — release ≠ namespace on customer-managed installs) and helm stays pinned to the resolved kubeconfig/context, never the ambient one.
  • Honest output (the fix(delete): verify the host-data wipe before printing ✔ (RFC-0003) #389 spirit): only a fully-passed suite exits 0; "unknown" is never worded as sealed; Ctrl-C mid-suite and a failed hook enumeration produce no verdict at all (exit quietly / error out — remaining checks didn't fail, they never ran).

Also in this PR

  • client.go sat at its 1050-line file-budget ceiling, so the status command moved verbatim into client_status.go (client.go → 871 lines; the diff on client.go is a pure removal).
  • Printer.Spinner strings added to the copy-catalog harvest — they render as static lines on non-TTY runs and were previously missing from the zz-all-strings.golden backstop (this PR's Checking <check>… included).
  • Copy catalog: all three seal verdicts pinned as stable screens in 08-client.golden (rendered through the pure renderSealResult), with mustRender markers so they can't silently vanish.

Test plan

  • make ci all green (vet, race tests, lint, fmt-check, schema-check, vulncheck, file-budget, deadcode, check-style); internal/cli coverage 84%+ (floor 80), internal/helm 94%
  • internal/helm/seal_test.go: hook parsing against realistic helm get hooks output (both seal labels, hint annotation, aux SA hook non-runnable, non-test hooks filtered, legacy test-success counted, comment-only docs skipped), fail-closed on unparseable manifests, exact argv for get hooks / test --filter name=a,name=b incl. kubeconfig/context pinning, raw error+output passthrough
  • internal/cli/seal_test.go: sealed / unsealed (failure detail distilled from helm's output, chart hint + kubectl-logs fallback, suite continues past a failure) / unknown for both no-hooks and plumbing-only charts (never claims sealed) / fallback-unlabeled / labelled-subset-only / aux plumbing carried in every filter but never counted / enumeration-error → no verdict / resolve-error propagation (exit 4) / Ctrl-C → exit 130, no verdict / helmFailureDetail table / cobra flag guards / --seal --timeout reaches the per-check budget
  • Golden copy catalog regenerated (TB_UPDATE_GOLDEN=1) and reviewed: three seal screens + new --help in 08-client.golden, all new strings in zz-all-strings.golden
  • Smoke-tested the built binary: --wait --seal and cluster-flags-without---seal guards fire with exit 1
  • Live run against a k3d install once the chart-side suite (backend#1184, security/seal-check-suite-1184) merges — until then the fallback path exercises today's unlabelled probes

🤖 Generated with Claude Code


Note

Medium Risk
Touches cluster resolution and helm test execution on customer environments; exit-code contract is script-facing but behavior is heavily tested and fails closed on ambiguous states.

Overview
tracebloc client status --seal runs the chart’s helm-test conformance hooks against the active secure environment and prints a per-check verdict (sealed / unsealed / unknown). Only a full pass exits 0; unsealed and unknown use exit 2, with cluster errors aligned to existing data-command codes (3/4). --wait (backend online polling) and --seal are mutually exclusive; kubeconfig/context/namespace and --timeout apply only when the matching mode is active.

New internal/cli/seal.go and internal/helm/seal.go discover hooks via helm get hooks, run one helm test per check (so every check is reported), and always include non-runnable aux hooks in the filter so checks don’t false-fail. Older charts without seal labels fall back to all runnable tests; hook-list failures and Ctrl-C avoid printing a misleading verdict.

client status logic moves from client.go into client_status.go (file-budget). Troubleshooting exit-code docs and copy-catalog goldens cover the three seal screens and new user strings.

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

…atus --seal)

`tracebloc client status --seal` runs the chart's conformance checks (its
helm-test hooks — the RFC-0003 §8.2 seal check) against this machine's
secure environment and prints an honest verdict with per-check detail:

  sealed     every conformance check passed                    (exit 0)
  unsealed   a check failed — a protection is not enforced     (exit 2)
  unknown    the chart ships no checks; nothing was verified   (exit 2)

Chart contract (works against today's probes, picks up the growing
backend#1184 suite as it lands): test hooks labelled
`tracebloc.io/seal-check: "true"` form the suite; with no labelled hook
every helm test runs, with a visible fallback note; the optional
`tracebloc.io/seal-name` / `tracebloc.io/seal-hint` annotations refine a
check's display name and its failure hint. Checks run one
`helm test --filter name=<hook>` at a time so a first failure can't hide
the rest of the suite's state, and helm stays pinned to the resolved
kubeconfig/context — never the ambient one.

Honest-output rules (the #389 spirit): only a fully-passed suite exits 0;
"unknown" is never worded as sealed; a cancelled run (Ctrl-C) and a failed
hook enumeration yield no verdict at all.

Also: moves the status command into client_status.go (client.go sat at its
1050-line file-budget ceiling) and adds Printer.Spinner strings to the
copy-catalog harvest (they print as static lines on non-TTY runs and were
previously missed by the backstop).

Closes #393

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka LukasWodka self-assigned this Jul 22, 2026
@LukasWodka
LukasWodka requested a review from saadqbal July 22, 2026 17:03
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

@LukasWodka

Copy link
Copy Markdown
Contributor Author

👋 Heads-up — Code review queue is at 31 / 30

Above the WIP limit. The team convention is to review existing PRs before opening new work.

Open PRs currently in Code review (oldest first):

  • backend#1155 — chore(deps): bump pillow from 12.2.0 to 12.3.0 · author: @dependabot · no reviewer assigned
  • backend#1167 — perf(psr): offload resume edge send to Celery, preserve queue rows (#980) · author: @aptracebloc · reviewer: @saqlainsyed007
  • backend#1170 — chore(deps): bump gitpython from 3.1.50 to 3.1.52 · author: @dependabot · no reviewer assigned
  • cli#389 — fix(delete): verify the host-data wipe before printing ✔ (RFC-0003) · author: @saadqbal · reviewer: @LukasWodka
  • cli#390 — feat(cli): auto-update — nudge + tracebloc upgrade (F1) · author: @LukasWodka · no reviewer assigned
  • cli#391 — fix(push): stop progress bar colliding with staging log lines (D3) · author: @LukasWodka · no reviewer assigned
  • cli#392 — feat(install): prefer ~/bin when already on PATH (B2, RFC 0001) · author: @LukasWodka · no reviewer assigned
  • cli#394 — feat(cli): tracebloc prepare-host wrapper (#1178, cli) · author: @LukasWodka · no reviewer assigned
  • client#366 — Installer copy catalog (byte-exact golden of what a user sees) · author: @LukasWodka · no reviewer assigned
  • client#370 — feat(install): host capability/privilege probe + audit (RFC 0001, #1171) · author: @LukasWodka · reviewer: @saadqbal

Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.)

The table is the cross-command scripting contract; the new seal mode
produces 2 (unsealed / unknown via exitChecksFailed) and shares the
3 / 4 cluster-resolution codes with the data and resources commands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread internal/cli/seal.go
…ks in every filter

Two alignments against the chart-side suite (client docs/SEAL-CHECK.md,
backend#1184) that landed in parallel:

- The per-check identifier is the tracebloc.io/seal-check-name LABEL
  (values: egress-enforcement, backend-reachability, storage-assertions),
  not a seal-name annotation — read it from labels. The hint stays an
  optional CLI-side annotation (labels cannot carry sentences); absent, the
  kubectl-logs fallback stands.

- The storage-assertions Job depends on a ServiceAccount/RBAC that are
  themselves test hooks at negative hook-weight, and helm's --filter
  excludes every unlisted test hook — plumbing included. Filtering to the
  check alone would strand the Job without its SA and report a false
  Unsealed. Every per-check run now lists the check PLUS the release's
  non-runnable test hooks (applied instantly; helm only waits on
  Jobs/Pods), while other checks stay excluded so the verdict remains
  per-check. Non-runnable hooks never count as checks: a chart whose only
  test hooks are plumbing is still honestly "unknown".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread internal/cli/seal.go
…C during enumeration (Bugbot)

- helm.TestTarget.KubeContext now carries target.Resolved.Context, not the
  raw --context flag: with the flag omitted the raw value is empty and helm
  falls back to its own ambient resolution ($HELM_KUBECONTEXT included),
  which can diverge from the context client-go discovery just used. Same
  pinning `resources set` applies.

- A context cancellation during `helm get hooks` now exits 130 quietly
  (like the per-check loop and `status --wait`) instead of reporting a hard
  enumeration failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

@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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1d9c07c. Configure here.

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.

1 participant