Add ndjson output to smt commands - #16
Merged
Merged
Conversation
ajwdev
added this pull request to stack #18
October 2, 2026 16:52
`--format ndjson` only applied to query results; `::smt` checks always
printed human text, and the process exited 0 even when a check failed,
so neither scripts nor CI could consume them.
smt_command now takes the output format and returns whether the check
passed. check_access, reaches, cluster-admin, check_isolation,
node_selector, anti_affinity and karpenter emit JSON lines in ndjson
mode. Plain output is unchanged apart from the annotation below.
Behavior change (exit code): repl::run returns Result<bool> and main
exits 1 if any `::smt` check reported a failure during the session.
This applies to plain output as well as ndjson, and to interactive
sessions: quitting after a FAIL now exits 1. Usage errors and unknown
subcommands do not count as failures.
Behavior change (JSON schema): each line is an object with `result`
("pass" or "fail") and `check`. Failures add `principal` and, for
access checks, `namespace`, `apigroup`, `resource`, `verb`, `kind`
("direct" or "escalation", reaches/cluster-admin only) and `paths`
(binding_kind, binding_namespace, binding_name, role_kind, role_name,
via[{identity, mechanism}]). For example:
{"result":"fail","check":"cluster-admin","kind":"direct",
"principal":"admin@example.com","namespace":"","apigroup":"*",
"resource":"*","verb":"*","paths":[{"binding_kind":
"ClusterRoleBinding","binding_namespace":"","binding_name":
"cluster-admin","role_kind":"ClusterRole","role_name":
"cluster-admin","via":[]}]}
{"result":"pass","check":"check_isolation","namespace":"nonexistent"}
(Wrapped here for the commit message; real output is one line each.)
Human output now annotates non-serviceaccount principals in the
reaches/cluster-admin listing with their subject kind, for example
`admin@example.com (user)`. SmtEncoder::principal_kind derives this
from the all_user_perm and all_group_perm relations, which
assert_rbac_axioms now also loads.
Add tests/smt_ndjson.rs, which runs the built binary against the
testdata fixture and checks the ndjson shape and the exit status for a
failing check, a passing check and the plain-text annotation.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ajwdev
force-pushed
the
ajw-smt-ndjson-output
branch
from
October 2, 2026 16:56
0f085f8 to
daa6756
Compare
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.
Summary
Make
::smtchecks machine-consumable.smt_commandtakes the outputformatand returns whether the check passed.check_access,reaches,cluster-admin,check_isolation,node_selector,anti_affinityandkarpenteremit ndjson with--format ndjson. Plain output is unchanged apart from the annotation below.repl::runreturnsResult<bool>;mainexits 1 when any::smtcheck failed.admin@example.com (user), viaSmtEncoder::principal_kind(readsall_user_perm/all_group_perm, now loaded byassert_rbac_axioms).--formatdoc comment mention ndjson for::smtand the exit status.Behavior change
Exit code: the process now exits 1 if any
::smtcheck reported a failure in the session, for plain and ndjson output and for interactive sessions too (quit after a FAIL gives 1). Previously always 0. Usage errors and unknown subcommands do not count.JSON schema: one object per line. Always
result(passorfail) andcheck. Failures addprincipalplus the query (namespace,apigroup,resource,verb),kind(directorescalation, reaches/cluster-admin only) andpaths(binding_kind,binding_namespace,binding_name,role_kind,role_name,via[{identity, mechanism}]). Scheduling checks usepod/node/labelsfields.Stability: this JSON schema is experimental and may change while the interface for
::smtresults settles (for example toward a flatter, relational shape with findings, paths and hops as separate rows). Please do not build long-lived scripts on the nestedpaths/vialayout yet. The exit-code change above is not affected by this note.Testing
New
tests/smt_ndjson.rsruns the built binary againsttestdata/with a temp config: a failing::smt cluster-admingives only JSON lines withcheck/result/principal/pathsand exit 1; a passingcheck_isolationgives onepassline and exit 0; plain output showsadmin@example.com (user)and exits 1.cargo fmt --checkis clean and clippy reports no new warnings besides the lib-crate dead-code class the REPL code already triggers (the REPL is only used by the binary).Before / after (plain,
::smt cluster-admin, testdata)Before: exit 0, listing without annotations. After: exit 1, and the user principals are annotated:
🤖 Generated with Claude Code