usage: vexa-state-report [-h] --namespace NAMESPACE [--dry-run] [--kubeconfig KUBECONFIG]
[--context CONTEXT] [--out OUT]
[--transcription-match TRANSCRIPTION_MATCH] [--verify-redaction]
[--no-verify-redaction]
vexa-state-report — the shape of your setup, so what we ship fits it.
A READ-ONLY `kubectl get` SWEEP OF ONE NAMESPACE, WRITTEN TO ONE FILE. No
database connection, no `pods/exec`, no credentials of any kind, no SQL.
You already run Vexa. This reads the shape of the environment it runs in, so
the bundle we build for you works with what you already have and asks for
nothing you do not. If you are on 0.10, that is what tells us you are ready
for 0.12.
ONE FILE, AND THAT IS THE DESIGN. `state-report.yaml`. Not a directory, not an
archive, nothing to extract. The person who has to approve this before it
leaves their perimeter must read ALL of it, and a pile of JSON files is a
cross-referencing exercise rather than a read. YAML because the reader is a
Kubernetes engineer who reads it all day, and because it carries comments — so
the explanation of each section sits above the section instead of in a second
document that can drift from it.
WHAT IT COLLECTS, and the list is complete:
1. PLATFORM Kubernetes or OpenShift, its version, the cloud or distro
underneath, the node shapes available to schedule on, and
the storage this cluster offers
2. WIRING which components exist and how they are connected — the
database and transcription especially: in-cluster or
external, how each is addressed, versions, GPU or CPU — and
how the estate is EXPOSED: Ingress and, on OpenShift, Route,
because an OpenShift estate normally has no Ingress at all
and reading only one of them says "nothing is exposed"
3. RESOURCES requests and limits per container, plus the namespace's
ResourceQuotas and LimitRanges. This is not decoration: a
quota-controlled namespace stopped a subscriber's bot pods
being admitted, because they declare no resources of their
own and no LimitRange supplied a default
4. VERSIONS the image tags and digests ACTUALLY running, so the jump is
known exactly rather than assumed, and the PLACEMENT each
workload declares — node selector, affinity kinds,
tolerations, priority and runtime class, topology spread.
Taints say which nodes repel; these say where the workloads
are pinned, and transcription pinned to a GPU pool is the
case that breaks a bundle silently
5. VALUES the settings this deployment has customised, so what we ship
does not overwrite a deliberate choice
6. REGISTRY whether images come from Docker Hub or through a mirror —
reported as observed, never as inferred
7. ADMISSION what this namespace will let run: its Pod Security labels
and, on OpenShift, the SCC UID and group ranges. This is
what decides whether the delivered workloads need
runAsNonRoot, a seccomp profile and dropped capabilities
8. NETWORK the NetworkPolicies in the namespace, by SHAPE — whether
anything default-denies egress, which is what decides
whether this cluster can reach a registry at all. Rule
bodies are not read: they carry internal addresses and are
not needed to build
9. INSTALL the name of the Helm release already here, and whether Argo
CD — including the OpenShift GitOps operator's own instance
— or Kyverno already run. A `--release-name` that matches
nothing installs a SECOND copy of the estate beside the
running one, against the same database, and installing our
Argo beside an operator-managed one is the same footgun
NEVER COLLECTED: schema, rows, row counts, SQL of any kind, transcripts,
meeting content, credentials. The database appears here only as a COMPONENT —
engine, version, in-cluster or external, how it is addressed, its resources —
and every one of those facts is read from the cluster, never by connecting.
THREE REFUSALS, and they are the design rather than caveats on it.
1. IT DOES NOT TRANSMIT. There is no --submit, no destination flag and no
endpoint constant anywhere in this file. It writes one file, prints its
path, and stops. What leaves the perimeter leaves because a human read it
and sent it.
2. IT DOES NOT WRITE, AND IT DOES NOT CONNECT. Every call is `kubectl get -o
json` or `kubectl version -o json`. No other verb: no exec, no apply, no
patch, no delete, no port-forward, no logs. There is no database client in
this file and no flag that would take a password.
3. IT CARRIES NO CONTENT. There is no field anywhere below that could hold a
customer's data, because nothing here reads any. Settings capture is
ALLOWLIST-FIRST: a variable is excluded unless its name matches the
allowlist, so redaction is the second net and not the only one. Secret and
ConfigMap VALUES are never read; a `valueFrom` records only that the
deployment expects one. Node names, service addresses and ingress
hostnames are not collected either — they are inventory, not shape.
ABSENT OVER ZERO, everywhere. A source that could not be read is recorded as
absent with a reason. It is never defaulted to zero or to an empty list: zero
is a claim, and a fabricated zero in a document whose whole purpose is to say
what somebody already has is worse than a stated gap.
The `absent` list carries a second kind of entry, and deliberately: NAMED
QUESTIONS — the handful of things that decide what we build and that no
read-only call can answer at all. Whether a pull of a release image by digest
actually succeeds needs a probe pod, which is a write; whether a NetworkPolicy
lets DNS out needs the rule bodies, which this refuses to read; whether the
registry sits behind a corporate CA appears in no object here. Each is stated
as a question with the reason it cannot be read, because an unasked question
is indistinguishable from an answered one.
────────────────────────────────────────────────────────────────────────────
ADDING A COLLECTOR — this file expects to be edited by people we have never met
This tool runs in estates we have not seen, and the first thing it will do in
some of them is miss something. That is not a failure mode to apologise for,
it is the lifecycle: the engineer standing in front of the gap is the only
person who can close it, the kit is Apache-2.0, and a patch back is worth more
to us than a support ticket.
So a collector is deliberately small and self-contained:
def collect_thing(ctx):
'''One paragraph: what this is for and what it refuses.'''
out = {}
doc, err = ctx.kube.get("things")
if doc is None:
return ctx.absent(out, "things", err) # absent, not zero
out["things"] = [...]
return out
...and then one line in SECTIONS at the bottom of this file, with the comment a
reader will see above it in the YAML. That is the whole contract:
* take `ctx`, return a dict, and it becomes one block of state-report.yaml;
* never raise to say "nothing here". Record absent with a reason. If you do
raise, the driver names your collector, keeps every other section, and the
report says which one failed — a broken collector must not cost an
operator their whole run;
* if you saw a value you decided not to write down, put it in
`ctx.withheld`. The redaction self-check scans the finished document for
everything in that set, so an allowlist you extended stays checkable;
* BUDGET IS A FEATURE. This file is read end to end by somebody deciding
whether to send it. Prefer one compact line to five nested ones; say each
fact once. Roughly 200-300 lines, comments included, is the shape, and
300 is a ceiling rather than a guideline — a section that would push past
it summarises instead of growing, and says what it summarised;
* ONE TEST FOR WHETHER IT BELONGS: is it one of the nine things above? Node
shapes, quotas, ingress class, resource limits, GPU-vs-CPU, image digests,
replica counts, allowlisted non-secret settings — yes. Anything describing
their data — no, and no amount of usefulness changes that.
────────────────────────────────────────────────────────────────────────────
python3 kit/report/vexa_state_report.py --namespace vexa [--dry-run]
Exit codes: 0 written · 2 usage · 3 redaction leak (the file is kept so it can
be inspected and reported, and it must not be sent). An unreadable resource is
NOT a usage error: it degrades to `absent` with a reason and still writes.
options:
-h, --help show this help message and exit
--namespace NAMESPACE
the namespace the Vexa workloads run in
--dry-run print every kubectl command a real run would issue and what it would
write, then exit 0 having connected to nothing and written nothing. Paste
it into a change ticket.
--kubeconfig KUBECONFIG
--context CONTEXT
--out OUT a directory to write state-report.yaml into, or a filename to write
(default: the current directory)
--transcription-match TRANSCRIPTION_MATCH
regex matching the transcription workloads whose runtime shape is captured
(default 'whisper|transcri|asr|diariz|stt')
--verify-redaction scan the finished report and refuse to finish if a withheld value survived
(default; exit 3)
--no-verify-redaction
what this is, in one sentence
A read-only `kubectl get` sweep of one namespace, written to one file. No
database connection, no pods/exec, no credentials of any kind, no SQL.
why we ask for it
You already run Vexa. This reads the shape of the environment it runs in, so
what we build for you works with what you already have and asks for nothing
you do not. If you are on 0.10, that is what tells us you are ready for 0.12.
what it collects
1 platform Kubernetes or OpenShift, version, cloud, node shapes with the
taints on them and what is allocatable, storage
2 wiring which components exist and how they are connected — database
and transcription especially: in-cluster or external, how each
is addressed, versions, GPU or CPU — and how the estate is
exposed: Ingress and, on OpenShift, Route
3 resources requests and limits per container, and the namespace's
ResourceQuotas and LimitRanges
4 versions image tags and digests actually running, and where each
workload is pinned
5 values the settings this deployment has customised
6 registry Docker Hub or a mirror — as observed, never inferred
7 admission the namespace's Pod Security labels and, on OpenShift, its SCC
ranges — what it will let run at all
8 network the NetworkPolicies, by shape: whether anything default-denies
egress. Rule bodies are not read
9 install the Helm release name already here, and whether Argo CD, the
OpenShift GitOps operator or Kyverno already run
what you get
state-report.yaml — one file, roughly 200-300 lines, every block carrying a
plain-English comment above it. No directory, no archive, nothing to extract.
You are meant to read all of it before any of it is sent.
what it enforces, and where to check it
reads only every call is `kubectl get -o json` or `kubectl version -o json`
(Kube — there is no other verb anywhere in the file)
no database no client, no SQL, no flag that takes a password; engine and
version are read from what is running (collect_wiring)
allowlist a setting is dropped unless its name matches the allowlist, and
dropped anyway if it matches password|token|secret|key|apikey
(env_allowed)
no transmit no --submit, no destination, no network client
START WITH --dry-run. It connects to nothing and prints every command it would
run, and what it would write.