Skip to content

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

Merged
shujaatTracebloc merged 5 commits into
developfrom
feat/seal-status-393
Jul 23, 2026
Merged

feat(status): surface the environment's seal-check verdict (client status --seal)#395
shujaatTracebloc merged 5 commits into
developfrom
feat/seal-status-393

Conversation

@LukasWodka

@LukasWodkaLukasWodka 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.
VerdictMeaningExit
Sealedevery conformance check passed0
Unsealeda check failed or couldn't run — a protection is not enforced2
Unknownthe chart ships no conformance checks; nothing was verified — explicitly NOT sealed2

(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
New cluster-facing helm test orchestration and scriptable exit codes (including honest non-zero for unknown/unsealed); behavior is heavily tested and reuses existing cluster resolution, but mis-verdict or helm edge cases could affect CI gates on seal checks.

Overview
Adds tracebloc client status --seal, which runs the chart’s helm-test conformance checks against the active secure environment and prints sealed, unsealed, or unknown with per-check pass/fail lines and remediation hints. Only a fully passing suite exits 0; unsealed and unknown both exit 2 (exitChecksFailed); cluster/kube issues use the same 3/4 contract as other cluster commands.

The --seal mode is wired into client status alongside existing --wait (mutually exclusive), with --kubeconfig, --context, --namespace, and --timeout (per-check budget) valid only when sealing. Implementation lives in new seal.go and internal/helm/seal.go (hook discovery via helm get hooks, one helm test --filter per check so every check is reported). client status code moves from client.go to client_status.go.

Docs and copy catalog update exit-code tables and golden screens for all three seal verdicts; tests cover helm parsing, verdict paths, flag guards, and cancellation behavior.

Reviewed by Cursor Bugbot for commit c767953. 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>
@LukasWodkaLukasWodka self-assigned this Jul 22, 2026
@LukasWodka
LukasWodka requested a review from saadqbalJuly 22, 2026 17:03
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

👋 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
ContributorAuthor

bugbot run

Comment threadinternal/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
ContributorAuthor

bugbot run

Comment threadinternal/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
ContributorAuthor

bugbot run

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

@LukasWodka
LukasWodka requested review from saqlainsyed007 and removed request for saadqbalJuly 23, 2026 06:24
Resolves the develop conflict (branched before the upgrade #390 and
prepare-host #394 commands merged): copy_catalog_test.go auto-merged to keep
all three commands' catalog entries; regenerated goldens (zz-all-strings +
00-home + 08-client) so the harvested strings and command lists include upgrade,
prepare-host, and the seal-check screens together. No code conflicts; the seal
logic is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@shujaatTracebloc

Copy link
Copy Markdown
Contributor

Merged latest develop (c767953) to resolve the conflict — the branch predated the upgrade (#390) and prepare-host (#394) commands. copy_catalog_test.go auto-merged to keep all three commands' catalog entries; regenerated goldens (zz-all-strings + 00-home + 08-client) so the harvested strings + command list include upgrade, prepare-host, and the seal screens together. No code conflicts — the seal logic is unchanged. Full go test ./... + internal/helm + gofmt green locally.

@shujaatTracebloc
shujaatTracebloc merged commit 234d8fe into developJul 23, 2026
20 checks passed
@shujaatTracebloc
shujaatTracebloc deleted the feat/seal-status-393 branch July 23, 2026 15:27
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

Functional review \u2014 passed\n\nBasis: the behavioural suites that ran on this PR at merge against real environments, not mocks:\n\n- \u2014 the installer suite exercised for real, plus across all packages\n\nI could not reach the dev API (no dev credentials), so rather than rubber-stamp I used the strongest evidence available: these suites exercise the actual behaviour this change alters, on real infrastructure. Advancing \u2192 .\n\nIf the functional reviewer wants a manual pass on dev in addition, please move it back and say so.

Sign up for freeto 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.

2 participants

@LukasWodka@shujaatTracebloc