<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Jsonnet-as-a-Service</title><link>https://jaas.projects.metio.wtf/</link><description>Recent content on Jsonnet-as-a-Service</description><generator>Hugo</generator><language>en</language><atom:link href="https://jaas.projects.metio.wtf/index.xml" rel="self" type="application/rss+xml"/><item><title>Admission webhook</title><link>https://jaas.projects.metio.wtf/usage/admission-webhook/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/admission-webhook/</guid><description>&lt;p&gt;JaaS ships an optional validating admission webhook for &lt;code&gt;JsonnetSnippet&lt;/code&gt;. It is
independent of, but layered on top of, &lt;a href="https://jaas.projects.metio.wtf/usage/operator-mode/"&gt;operator mode&lt;/a&gt;
.&lt;/p&gt;
&lt;h2 id="enabling-it"&gt;Enabling it&lt;/h2&gt;
&lt;p&gt;Set &lt;code&gt;--enable-webhook&lt;/code&gt; to boot the webhook server. It requires
&lt;code&gt;--enable-flux-integration&lt;/code&gt; (the webhook is wired only inside the operator boot
path; enabling it alone is rejected as a flag error) and TLS material — &lt;code&gt;tls.crt&lt;/code&gt;
and &lt;code&gt;tls.key&lt;/code&gt; — under &lt;code&gt;--webhook-cert-dir&lt;/code&gt; (default
&lt;code&gt;/tmp/k8s-webhook-server/serving-certs&lt;/code&gt;). The server binds &lt;code&gt;--webhook-port&lt;/code&gt;
(default &lt;code&gt;9443&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="what-it-validates"&gt;What it validates&lt;/h2&gt;
&lt;p&gt;The webhook rejects a &lt;code&gt;JsonnetSnippet&lt;/code&gt; whose &lt;code&gt;spec.externalVariables&lt;/code&gt; declares a
key that collides with an operator-level &lt;code&gt;--ext-var&lt;/code&gt;. An operator-supplied
external variable always wins, so a snippet that tries to redeclare one would
render against a value it does not control; the webhook refuses the snippet at
admission time instead.&lt;/p&gt;</description></item><item><title>Alerting</title><link>https://jaas.projects.metio.wtf/usage/alerting/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/alerting/</guid><description>&lt;p&gt;JaaS turns a sustained problem into a notification two ways: a Prometheus
&lt;code&gt;PrometheusRule&lt;/code&gt; that pages on its &lt;a href="https://jaas.projects.metio.wtf/usage/metrics/"&gt;metrics&lt;/a&gt;
, and Kubernetes
Events that Flux&amp;rsquo;s notification-controller can route to chat or e-mail.&lt;/p&gt;
&lt;h2 id="the-binary"&gt;The binary&lt;/h2&gt;
&lt;p&gt;The operator emits a standard Kubernetes &lt;code&gt;Event&lt;/code&gt; on every Ready-condition
transition — &lt;code&gt;Normal&lt;/code&gt; for &lt;code&gt;Synced&lt;/code&gt;, &lt;code&gt;Warning&lt;/code&gt; for every other reason. The reason
string fills both the event &lt;code&gt;reason&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt;. These Events need no flag to
enable; they are written whenever the operator reconciles.&lt;/p&gt;</description></item><item><title>ArtifactTooLarge</title><link>https://jaas.projects.metio.wtf/runbooks/artifacttoolarge/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/artifacttoolarge/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=ArtifactTooLarge&lt;/code&gt;. The Message states the rendered byte count and the configured cap.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The snippet&amp;rsquo;s rendered output exceeds the operator&amp;rsquo;s &lt;code&gt;--max-artifact-bytes&lt;/code&gt; (Helm: &lt;code&gt;operator.storage.maxArtifactBytes&lt;/code&gt;). The cap is a defense-in-depth control — one runaway snippet shouldn&amp;rsquo;t fill a shared storage volume.&lt;/p&gt;
&lt;p&gt;Common triggers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a snippet generating massive arrays via &lt;code&gt;std.range(n)&lt;/code&gt; with a much larger &lt;code&gt;n&lt;/code&gt; than intended&lt;/li&gt;
&lt;li&gt;accidentally inlining a large data fixture via &lt;code&gt;importstr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;forgetting to project / filter when fanning out per-tenant configs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;p&gt;Check the rendered size locally:&lt;/p&gt;</description></item><item><title>Building and Testing</title><link>https://jaas.projects.metio.wtf/contributing/building/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/contributing/building/</guid><description>&lt;p&gt;The host needs no Go toolchain. All build and test commands run inside a containerized dev shell defined by &lt;code&gt;dev/Containerfile&lt;/code&gt; and driven by &lt;code&gt;ilo&lt;/code&gt;. The &lt;code&gt;.ilo.rc&lt;/code&gt; at the repo root supplies the shell arguments, so the short form is always available:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ilo bash -c &lt;span class="s1"&gt;&amp;#39;&amp;lt;command&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The dev shell pre-installs the Go toolchain, all static-analysis tools, &lt;code&gt;controller-gen&lt;/code&gt;, and the envtest asset bundle. The Go module cache is mounted from &lt;code&gt;${XDG_CACHE_HOME}/go&lt;/code&gt; so repeated builds do not re-download dependencies.&lt;/p&gt;</description></item><item><title>CI and Releases</title><link>https://jaas.projects.metio.wtf/contributing/ci-and-release/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/contributing/ci-and-release/</guid><description>&lt;h2 id="static-analysis"&gt;Static analysis&lt;/h2&gt;
&lt;p&gt;golangci-lint is not used. The tools below run directly, both in CI and locally inside the dev shell (see &lt;a href="https://jaas.projects.metio.wtf/contributing/building/"&gt;Building and Testing&lt;/a&gt;
). Every tool is a separate, auditable binary with its own config file.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Tool&lt;/th&gt;
					&lt;th&gt;Scope&lt;/th&gt;
					&lt;th&gt;Config&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;go vet&lt;/code&gt; (all analyzers)&lt;/td&gt;
					&lt;td&gt;Go correctness&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://staticcheck.dev"&gt;staticcheck&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;Bugs, simplifications, style&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;staticcheck.conf&lt;/code&gt; (&lt;code&gt;checks = [&amp;quot;all&amp;quot;]&lt;/code&gt;)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://github.com/securego/gosec"&gt;gosec&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;Security patterns&lt;/td&gt;
					&lt;td&gt;inline &lt;code&gt;#nosec&lt;/code&gt; justifications&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://go.dev/security/vuln/"&gt;govulncheck&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;Known vulnerabilities in the dependency graph&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://github.com/arch-go/arch-go"&gt;arch-go&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;Architecture rules&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;arch-go.yml&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://github.com/mvdan/gofumpt"&gt;gofumpt&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;Strict formatting&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://reuse.software"&gt;REUSE&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;License / copyright metadata on every file&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;REUSE.toml&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://yamllint.readthedocs.io"&gt;yamllint&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;YAML&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;.yamllint.yaml&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://github.com/rhysd/actionlint"&gt;actionlint&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;GitHub Actions workflows&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://github.com/DavidAnson/markdownlint-cli2"&gt;markdownlint&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;Markdown&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;.markdownlint.yaml&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://github.com/crate-ci/typos"&gt;typos&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;Spelling&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;.typos.toml&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;a href="https://github.com/aquasecurity/trivy"&gt;Trivy&lt;/a&gt;
&lt;/td&gt;
					&lt;td&gt;Container image CVEs&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="architecture-rules"&gt;Architecture rules&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;arch-go.yml&lt;/code&gt; pins two invariants enforced with 100% compliance:&lt;/p&gt;</description></item><item><title>Configuration reference</title><link>https://jaas.projects.metio.wtf/installation/configuration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/installation/configuration/</guid><description>&lt;p&gt;Every JaaS flag is listed here with its default and a one-line description. Run
&lt;code&gt;jaas --help&lt;/code&gt; to see the same list at runtime. The tables on this page are
generated from the binary&amp;rsquo;s own flag definitions, so they never drift from the
runtime contract.&lt;/p&gt;
&lt;p&gt;The Helm chart exposes most flags under &lt;code&gt;arguments.*&lt;/code&gt;; operator-specific flags
are under &lt;code&gt;operator.*&lt;/code&gt;. The full set of chart values is in the
&lt;a href="https://jaas.projects.metio.wtf/installation/helm-values/"&gt;Helm chart values&lt;/a&gt;
 reference.&lt;/p&gt;
&lt;h2 id="jsonnet-server"&gt;Jsonnet server&lt;/h2&gt;
&lt;p&gt;The Jsonnet server evaluates snippets and returns JSON. It binds on
&lt;code&gt;--listen-address:--port&lt;/code&gt; by default.&lt;/p&gt;</description></item><item><title>CRD watch engagement failing</title><link>https://jaas.projects.metio.wtf/runbooks/crd-watch-engagement/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/crd-watch-engagement/</guid><description>&lt;p&gt;Fires when &lt;code&gt;jaas_crd_watch_engagement_failures_total{gvk=...}&lt;/code&gt; has increased above the per-hour threshold for the alert window. JaaS lazy-watches Flux source CRDs: at boot, only the CRDs already installed get a watch; when a previously-missing CRD becomes &lt;code&gt;Established=True&lt;/code&gt; (operator installed source-controller post hoc, say), the &lt;code&gt;crdWatcher&lt;/code&gt; engages a runtime watch on it via &lt;code&gt;Controller.Watch&lt;/code&gt;. &lt;strong&gt;When that engagement fails, the apiextensions informer fires no further events on a stable CRD&lt;/strong&gt; — meaning the watch stays un-engaged forever until either the CRD object&amp;rsquo;s metadata/status is changed by something else, or the operator restarts.&lt;/p&gt;</description></item><item><title>Creating source artifacts</title><link>https://jaas.projects.metio.wtf/usage/creating-sources/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/creating-sources/</guid><description>&lt;p&gt;A &lt;code&gt;JsonnetSnippet&lt;/code&gt;&amp;rsquo;s &lt;code&gt;spec.sourceRef&lt;/code&gt; consumes a Flux source. The operator reads
the referenced source CR&amp;rsquo;s &lt;code&gt;status.artifact.url&lt;/code&gt;, downloads the tarball Flux&amp;rsquo;s
source-controller serves there, verifies its &lt;code&gt;status.artifact.digest&lt;/code&gt;, and
extracts it into the snippet&amp;rsquo;s file tree. Every supported kind —
&lt;code&gt;GitRepository&lt;/code&gt;, &lt;code&gt;OCIRepository&lt;/code&gt;, and &lt;code&gt;Bucket&lt;/code&gt; — reaches the operator through
that same &lt;code&gt;status.artifact&lt;/code&gt; contract, so the operator never talks to a git
remote, an OCI registry, or an object store directly. Flux owns that fetch; the
operator consumes the artifact Flux already produced.&lt;/p&gt;</description></item><item><title>CrossNamespaceRefRejected</title><link>https://jaas.projects.metio.wtf/runbooks/crossnamespacerefrejected/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/crossnamespacerefrejected/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=CrossNamespaceRefRejected&lt;/code&gt;. The Message names the offending reference (a library or a sourceRef).&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The operator is running with &lt;code&gt;--no-cross-namespace-refs=true&lt;/code&gt; (the chart default) and the snippet references a library or Flux source in a different namespace.&lt;/p&gt;
&lt;p&gt;This is a deliberate isolation control — it mirrors Flux&amp;rsquo;s &lt;code&gt;--no-cross-namespace-refs&lt;/code&gt; and stops a tenant in namespace A from reaching libraries / sources in namespace B without an explicit relationship.&lt;/p&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;p&gt;Inspect the spec and identify which reference points outside the snippet&amp;rsquo;s namespace:&lt;/p&gt;</description></item><item><title>DependencyCycle</title><link>https://jaas.projects.metio.wtf/runbooks/dependencycycle/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/dependencycycle/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=DependencyCycle&lt;/code&gt;. The Message names the snippet that closes the cycle.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The snippet&amp;rsquo;s &lt;code&gt;spec.sourceRef&lt;/code&gt; chain transitively points back at the snippet itself. The reconciler detects this and refuses to publish so chained snippets don&amp;rsquo;t loop forever (each republish would trigger every downstream snippet to re-render, which would re-trigger the upstream, and so on).&lt;/p&gt;
&lt;p&gt;Two cycle shapes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Direct sourceRef cycle:&lt;/strong&gt; &lt;code&gt;A.spec.sourceRef → ExternalArtifact/A&lt;/code&gt;. A snippet sourcing from its own published artifact.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Library-mediated cycle:&lt;/strong&gt; &lt;code&gt;A.spec.libraries → JsonnetLibrary/L&lt;/code&gt;, where &lt;code&gt;L.spec.sourceRef → ExternalArtifact/A&lt;/code&gt; (or a longer chain back to A).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The validating webhook (&lt;code&gt;--enable-webhook&lt;/code&gt;) rejects new CRs that introduce a cycle at admission; the reconciler check is a fallback for when admission is bypassed or the cycle is introduced retroactively (e.g., adding a new library that closes a loop with existing snippets).&lt;/p&gt;</description></item><item><title>Deploying manifests with StageSet</title><link>https://jaas.projects.metio.wtf/tutorials/deploying-manifests/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/tutorials/deploying-manifests/</guid><description>&lt;p&gt;JaaS pairs with &lt;a href="https://stageset.projects.metio.wtf/"&gt;stageset-controller&lt;/a&gt;
 to
deploy Kubernetes manifests as code: you author the manifests in Jsonnet, the
JaaS operator renders and publishes them as a Flux &lt;code&gt;ExternalArtifact&lt;/code&gt;, and
stageset-controller rolls that artifact out across ordered, gated stages.&lt;/p&gt;
&lt;p&gt;This tutorial covers the JaaS side — authoring the manifests with top-level
arguments and external variables, and publishing the rendered JSON. The rollout
side (the &lt;code&gt;StageSet&lt;/code&gt; resource, its stages, gates, and actions) lives on the
stageset-controller site and is linked at the end.&lt;/p&gt;</description></item><item><title>Eval-concurrency saturation</title><link>https://jaas.projects.metio.wtf/runbooks/eval-saturation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/eval-saturation/</guid><description>&lt;p&gt;Not tied to a single &lt;code&gt;Reason&lt;/code&gt; — this page covers what to do when the global concurrent-eval cap (&lt;code&gt;--max-concurrent-evals&lt;/code&gt;) is full and JaaS is shedding new evaluations. The cap exists because the synchronous go-jsonnet API has no context-aware cancellation: once an eval starts it runs to natural completion, so an unbounded queue lets a runaway snippet pile up goroutines that outlive every caller&amp;rsquo;s deadline.&lt;/p&gt;
&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;One or more of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JaaSEvalSaturation&lt;/code&gt; is firing — &lt;code&gt;jaas_eval_in_flight / jaas_eval_max_concurrent&lt;/code&gt; has been above the threshold (default &lt;code&gt;0.9&lt;/code&gt;) for the alert window.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JaaSEvalRejected&lt;/code&gt; is firing — &lt;code&gt;rate(jaas_eval_unavailable_total[5m])&lt;/code&gt; has been above the threshold.&lt;/li&gt;
&lt;li&gt;HTTP clients see &lt;code&gt;503 Service Unavailable&lt;/code&gt; with body &lt;code&gt;{&amp;quot;error&amp;quot;: &amp;quot;evaluation_unavailable&amp;quot;, &amp;quot;message&amp;quot;: &amp;quot;concurrent-eval cap is full; retry after backoff&amp;quot;}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl describe jsonnetsnippet&lt;/code&gt; shows recurring &lt;code&gt;Warning EvalUnavailable&lt;/code&gt; events with message &lt;code&gt;reconcile deferred for 1s by --max-concurrent-evals&lt;/code&gt;. Ready condition stays untouched (backpressure is not failure).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jaas_eval_outstanding_timed_out&lt;/code&gt; is also elevated — confirms the runaway-snippet diagnosis: orphaned evals are pinning slots while their parents have already given up.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagnosis-why-is-the-cap-full"&gt;Diagnosis: why is the cap full?&lt;/h2&gt;
&lt;p&gt;The cap fills for two distinct reasons. The right remediation depends on which.&lt;/p&gt;</description></item><item><title>Evaluation and security</title><link>https://jaas.projects.metio.wtf/usage/evaluation-and-security/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/evaluation-and-security/</guid><description>&lt;p&gt;JaaS runs Jsonnet on the server and returns the result over HTTP. Three caps
bound each evaluation, and a small security model governs what a snippet and its
callers can reach. Review and tune both sections before exposing the service to
a wider audience.&lt;/p&gt;
&lt;h2 id="evaluation-caps"&gt;Evaluation caps&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Flag&lt;/th&gt;
					&lt;th&gt;Default&lt;/th&gt;
					&lt;th&gt;Effect&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--evaluation-timeout&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;5s&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Wall-clock budget per evaluation. Exceeding it returns &lt;code&gt;504 evaluation_timeout&lt;/code&gt;. &lt;code&gt;0&lt;/code&gt; disables the timeout.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--max-stack&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;500&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Maximum Jsonnet call-stack depth. &lt;code&gt;0&lt;/code&gt; uses go-jsonnet&amp;rsquo;s own default.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--max-concurrent-evals&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;max(GOMAXPROCS*4, 16)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;In-flight evaluations allowed at once. Excess requests return &lt;code&gt;503 evaluation_unavailable&lt;/code&gt;. &lt;code&gt;0&lt;/code&gt; disables the cap.&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./jaas &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --snippet-directory examples/snippets/dashboards &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --evaluation-timeout 2s &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --max-stack &lt;span class="m"&gt;1000&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --max-concurrent-evals &lt;span class="m"&gt;32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The default for &lt;code&gt;--max-concurrent-evals&lt;/code&gt; bounds worst-case goroutine pile-up
under a runaway snippet. Each in-flight evaluation pins roughly one CPU for its
working set, so raising the cap far above the available parallelism queues work
without adding throughput.&lt;/p&gt;</description></item><item><title>EvaluationFailed</title><link>https://jaas.projects.metio.wtf/runbooks/evaluationfailed/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/evaluationfailed/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=EvaluationFailed&lt;/code&gt;. The Message contains the raw go-jsonnet diagnostic — file name, line, column, and the underlying error.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The snippet failed to evaluate. Three broad categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Syntax error&lt;/strong&gt; — unclosed brace, missing comma, bad indent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime error&lt;/strong&gt; — &lt;code&gt;std.extVar('missing')&lt;/code&gt; for an unset variable, division by zero, &lt;code&gt;error '...'&lt;/code&gt; thrown explicitly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Import error&lt;/strong&gt; — &lt;code&gt;import 'missing.libsonnet'&lt;/code&gt; resolves to nothing in the snippet&amp;rsquo;s file map or library imports.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;p&gt;Read the Message — it names the file and line. Reproduce locally:&lt;/p&gt;</description></item><item><title>EvaluationTimeout</title><link>https://jaas.projects.metio.wtf/runbooks/evaluationtimeout/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/evaluationtimeout/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=EvaluationTimeout&lt;/code&gt;. The snippet&amp;rsquo;s eval ran longer than the operator&amp;rsquo;s &lt;code&gt;--evaluation-timeout&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;Snippets are evaluated synchronously per reconcile. The deadline is wall-clock, not CPU — but go-jsonnet has no mid-evaluation cancellation, so a snippet that runs over the deadline still keeps consuming CPU on the operator pod until it returns naturally.&lt;/p&gt;
&lt;p&gt;Common triggers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a snippet recursing deeper than necessary (try lowering &lt;code&gt;--max-stack&lt;/code&gt; to surface this as a stack-limit error instead, then optimize)&lt;/li&gt;
&lt;li&gt;a snippet that loads a huge sourceRef tarball and walks it&lt;/li&gt;
&lt;li&gt;a snippet that calls &lt;code&gt;std.set&lt;/code&gt; / &lt;code&gt;std.uniq&lt;/code&gt; over a very large array&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;p&gt;Time it locally:&lt;/p&gt;</description></item><item><title>External variables and TLAs</title><link>https://jaas.projects.metio.wtf/usage/external-variables-and-tlas/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/external-variables-and-tlas/</guid><description>&lt;p&gt;JaaS feeds two kinds of input into an evaluation: external variables, set by the
process owner at startup, and top-level arguments, supplied per request through
the URL query string.&lt;/p&gt;
&lt;h2 id="external-variables"&gt;External variables&lt;/h2&gt;
&lt;p&gt;External variables come from two sources. The environment mechanism reads every
variable prefixed with &lt;code&gt;JAAS_EXT_VAR_&lt;/code&gt; — the suffix is the variable name:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;JAAS_EXT_VAR_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Alice &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;JAAS_EXT_VAR_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ./jaas --snippet-directory examples/snippets/dashboards
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;--ext-var KEY=VALUE&lt;/code&gt; flag does the same and is repeatable. On a key
conflict, the flag takes precedence over the environment value:&lt;/p&gt;</description></item><item><title>ExternalArtifact output contract</title><link>https://jaas.projects.metio.wtf/api/externalartifact/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/api/externalartifact/</guid><description>&lt;p&gt;For every successfully evaluated &lt;code&gt;JsonnetSnippet&lt;/code&gt;, the JaaS operator upserts a
Flux &lt;code&gt;ExternalArtifact&lt;/code&gt; CR (&lt;code&gt;source.toolkit.fluxcd.io/v1&lt;/code&gt;) in the same namespace
as the snippet. JaaS does not own the &lt;code&gt;ExternalArtifact&lt;/code&gt; CRD — it is defined and
installed by Flux&amp;rsquo;s source-controller. The full CRD schema is in the
&lt;a href="https://fluxcd.io/flux/components/source/externalartifacts/"&gt;Flux ExternalArtifact reference&lt;/a&gt;
;
below is the subset JaaS writes and the invariants downstream consumers can rely on.&lt;/p&gt;
&lt;p&gt;For task-oriented context, see &lt;a href="https://jaas.projects.metio.wtf/usage/operator-mode/"&gt;Operator mode&lt;/a&gt;
.&lt;/p&gt;
&lt;h2 id="what-jaas-writes"&gt;What JaaS writes&lt;/h2&gt;
&lt;h3 id="specsourceref"&gt;&lt;code&gt;spec.sourceRef&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;JaaS stamps a back-pointer to the originating snippet on &lt;code&gt;spec.sourceRef&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>ExternalVariableConflict</title><link>https://jaas.projects.metio.wtf/runbooks/externalvariableconflict/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/externalvariableconflict/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=ExternalVariableConflict&lt;/code&gt;. The Message names the conflicting key.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The snippet&amp;rsquo;s &lt;code&gt;spec.externalVariables&lt;/code&gt; declares a key that the operator already owns via &lt;code&gt;--ext-var&lt;/code&gt; (cluster operator-level). Operator keys win by design — they&amp;rsquo;re how the cluster admin pins cluster-scoped values like &lt;code&gt;cluster&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;environment&lt;/code&gt; so a tenant snippet can&amp;rsquo;t override them.&lt;/p&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Which keys does the operator own?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl --namespace &amp;lt;jaas-ns&amp;gt; get pod --selector app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;jaas --output yaml &lt;span class="p"&gt;|&lt;/span&gt; grep -A1 &lt;span class="s2"&gt;&amp;#34;\--ext-var=&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cross-reference with the snippet&amp;rsquo;s &lt;code&gt;spec.externalVariables&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Grafana dashboards</title><link>https://jaas.projects.metio.wtf/tutorials/grafana-dashboards/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/tutorials/grafana-dashboards/</guid><description>&lt;p&gt;JaaS pairs with the
&lt;a href="https://grafana.github.io/grafana-operator/"&gt;grafana-operator&lt;/a&gt;
 to manage
Grafana dashboards as code: you author the dashboard in Jsonnet, the JaaS
operator renders it and publishes the dashboard JSON as a Flux
&lt;code&gt;ExternalArtifact&lt;/code&gt;, and the grafana-operator reconciles that artifact into a live
Grafana instance.&lt;/p&gt;
&lt;p&gt;This tutorial covers the JaaS side — authoring the dashboard, importing
grafonnet as a &lt;code&gt;JsonnetLibrary&lt;/code&gt;, and publishing the rendered JSON. The
grafana-operator side (the &lt;code&gt;GrafanaDashboard&lt;/code&gt; CR, datasources, folders) lives on
their site and is linked at the end.&lt;/p&gt;</description></item><item><title>Helm chart values</title><link>https://jaas.projects.metio.wtf/installation/helm-values/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/installation/helm-values/</guid><description>&lt;p&gt;The jaas Helm chart lives in the
&lt;a href="https://github.com/metio/helm-charts/tree/main/charts/jaas"&gt;metio/helm-charts&lt;/a&gt;

monorepo and is published at &lt;code&gt;oci://ghcr.io/metio/helm-charts/jaas&lt;/code&gt;. The tables
below are generated from each chart&amp;rsquo;s &lt;code&gt;values.yaml&lt;/code&gt;, so they track the chart&amp;rsquo;s
current values rather than a hand-maintained copy.&lt;/p&gt;
&lt;p&gt;For how the values map onto the binary&amp;rsquo;s runtime behaviour, see the
&lt;a href="https://jaas.projects.metio.wtf/installation/configuration/"&gt;Configuration reference&lt;/a&gt;
 — every &lt;code&gt;arguments.*&lt;/code&gt;
value drives the corresponding &lt;code&gt;--flag&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="jaas-chart"&gt;jaas chart&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;&lt;th&gt;Value&lt;/th&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Default&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;additionalLibraries&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object|null&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Additional library OCI volumes to mount. The key is the name of the directory and the value is an OCI volume that will be mounted beneath .Values.paths.libraries, e.g., the commented example below would result in all files from &amp;#39;ghcr.io/metio/something:latest&amp;#39; to be mounted at &amp;#39;/srv/libraries/something/&amp;#39; (using the default configuration) OCI mounts suit large, independently-released library bundles pinned by digest. For shared libraries you want to manage as ordinary manifests — published and updated via kubectl/GitOps without an operator restart — apply a namespaced JsonnetLibrary instead and reference it from a snippet&amp;#39;s spec.libraries.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;CLI flags for the jaas binary. These arguments map directly to their corresponding CLI flag.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.evaluationTimeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;5s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Per-request go-jsonnet evaluation timeout. 0 disables.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.jsonnetEndpointPath&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;jsonnet&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;URL path segment the jsonnet handler is mounted under (GET /&amp;lt;path&amp;gt;/...).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.listenAddress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;::&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Bind address for the jsonnet HTTP and management servers. `::` listens on every IPv6 interface and — because Go opens IPV6_V6ONLY=0 by default on Linux — also accepts IPv4 connections via v4-mapped addresses. Use `0.0.0.0` to constrain to IPv4 on clusters that don&amp;#39;t support dual-stack pods.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.logFormat&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;json&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Log output format for the jaas binary (json, text).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.logLevel&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;info&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Log verbosity for the jaas binary (debug, info, warn, error).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.managementListenAddress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;::&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Bind address for the management (probes) HTTP server. Same dual-stack semantics as listenAddress.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.managementReadTimeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Read timeout on the management (probes) HTTP server.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.managementWriteTimeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Write timeout on the management (probes) HTTP server.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.maxConcurrentEvals&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;32&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Maximum in-flight Jsonnet evaluations across both HTTP and operator paths. Excess requests return 503 (HTTP) or RequeueAfter (operator) with EvalUnavailable backpressure events. Sized to bound worst-case goroutine pile-up when a runaway snippet&amp;#39;s synchronous eval outlives every caller&amp;#39;s ctx — the cap turns &amp;#34;unbounded leak&amp;#34; into a tunable backpressure boundary. 0 disables the gate; the operator default (process-side) is max(GOMAXPROCS*4, 16).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.maxStack&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;500&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;go-jsonnet stack-depth ceiling. Guards against unbounded recursion.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.readTimeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Read timeout on the jsonnet HTTP server.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.shutdownDelay&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;5s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Grace period readiness stays false before in-flight requests are aborted on SIGTERM, so endpoint controllers can drain the pod first.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arguments.writeTimeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Write timeout on the jsonnet HTTP server.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;crds&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;CRD lifecycle. The chart ships the controller&amp;#39;s CRDs under templates/ so a `helm upgrade` applies schema changes automatically. Set create=false to manage them out-of-band — e.g. pre-installed cluster-wide, or in CI where the same chart is installed once per values file and a kept cluster-scoped CRD owned by the first release can&amp;#39;t be re-adopted by the next.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;crds.create&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;deployment&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Fine tune the Deployment resource.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;deployment.additionalLabels&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra labels merged onto the Deployment metadata.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;deployment.revisionHistoryLimit&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;spec.revisionHistoryLimit on the Deployment.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;externalVariables&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object|null&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;External variables (std.extVar). They will be added as JAAS_EXT_VAR_your_name and are available as your_name in your Jsonnet snippets.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;externalVariablesFrom&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;External variables loaded from ConfigMaps or Secrets. Each array expects the name of either ConfigMaps or Secrets. Only those values in ConfigMaps and Secrets that start with JAAS_EXT_VAR will be available as external variable in your Jsonnet snippets.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;externalVariablesFrom.configMaps&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;ConfigMap names to source JAAS_EXT_VAR_* env vars from.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;externalVariablesFrom.secrets&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Secret names to source JAAS_EXT_VAR_* env vars from.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;global&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Global values are values that can be accessed from any chart or subchart by exactly the same name.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Container image for the jaas workload. Overwrite these in case you want to use an internal registry mirror.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image.pullPolicy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;IfNotPresent&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Pull policy for the jaas container. IfNotPresent is the default — safe for tagged release images. Switch to Always when running off a mutable tag (latest, edge, dev) so each restart re-checks the registry. Never is useful when the image is preloaded into the node (kind, k3d).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image.registry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ghcr.io&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image.repository&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;metio/jaas&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image.tag&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Optional explicit tag override. Defaults to .Chart.AppVersion (the chart&amp;#39;s released version). Useful when running a custom build: --set-string image.tag=dev-abc123&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;imagePullSecrets&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Optional pull secrets in case you use a private mirror/image&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Well-known jsonnet libraries. All OFF by default — a default install mounts no OCI volumes. Enable per renderer-mode need (the standalone HTTP renderer imports these from pre-baked image bundles). Each enabled library renders a `-library-path` arg, a read-only volumeMount, and an OCI `image:` volume. Static OCI mounts are mutually exclusive with operator.enabled (Flux mode): in Flux mode libraries come from JsonnetLibrary CRs, not these image bundles.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.docsonnet&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;docsonnet library. Mounts a JOI OCI bundle when enabled.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.docsonnet.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.docsonnet.registry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ghcr.io&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.docsonnet.repository&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;metio/joi-jsonnet-libs-docsonnet&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.docsonnet.version&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.grafonnet&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;grafonnet library (Grafana dashboards). Mounts a JOI OCI bundle when enabled.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.grafonnet.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.grafonnet.registry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ghcr.io&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.grafonnet.repository&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;metio/joi-grafana-grafonnet&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.grafonnet.version&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The JOI images are calendar-tagged (plus a moving `latest`), not tagged by the upstream grafonnet version — there is no `v11.4.0` tag. Match the other libraries and track `latest`; pin to a dated tag (e.g. 2026.6.14) to freeze.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.xtd&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;xtd library. Mounts a JOI OCI bundle when enabled.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.xtd.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.xtd.registry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ghcr.io&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.xtd.repository&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;metio/joi-jsonnet-libs-xtd&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;libraries.xtd.version&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Opt-in install-namespace render with Pod Security Standards labels. Default-off so the chart doesn&amp;#39;t claim ownership of a pre-existing namespace operators created via kubectl / Argo. Enable for fresh installs that want the namespace&amp;#39;s PSS posture baked into the chart.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.additionalLabels&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra labels on the rendered Namespace.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.annotations&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra annotations on the rendered Namespace.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.create&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Whether the chart renders (and owns) its install Namespace.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.podSecurity&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Pod Security Standards admission labels stamped on the namespace.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.podSecurity.audit&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;restricted&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;PSS audit level.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.podSecurity.auditVersion&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.podSecurity.enforce&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;restricted&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;`restricted` is the strictest PSS level. Image volumes (used by the chart for snippet/library mounts) are restricted-compliant from Kubernetes 1.33 onward; clusters on 1.32 or older must drop this to `baseline`.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.podSecurity.enforceVersion&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.podSecurity.warn&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;restricted&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;PSS warn level.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;namespace.podSecurity.warnVersion&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Opt-in NetworkPolicy for the jaas pod (storage port lockdown &amp;#43; per-port ingress controls).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.additionalIngress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Free-form additional ingress rules merged into the policy verbatim.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.calico&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Calico engine (engine=calico) raw passthrough. Entries are merged verbatim into the projectcalico.org NetworkPolicy&amp;#39;s spec.ingress / spec.egress.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.calico.egress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra spec.egress rules merged verbatim (Calico NetworkPolicy schema).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.calico.ingress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra spec.ingress rules merged verbatim (Calico NetworkPolicy schema).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.cilium&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Cilium engine (engine=cilium) raw passthrough. Entries are merged verbatim into the CiliumNetworkPolicy&amp;#39;s spec.ingress / spec.egress. This is how you tighten the cilium policy — e.g. add fromEndpoints to the ingress rules, or additional toEndpoints / toFQDNs egress.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.cilium.egress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra spec.egress rules merged verbatim (CiliumNetworkPolicy schema).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.cilium.ingress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra spec.ingress rules merged verbatim (CiliumNetworkPolicy schema).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.clusterNetworkPolicy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;ClusterNetworkPolicy engine (engine=clusterNetworkPolicy) raw passthrough. Entries are merged verbatim into the policy.networking.k8s.io ClusterNetworkPolicy&amp;#39;s spec.ingress / spec.egress.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.clusterNetworkPolicy.egress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra spec.egress rules merged verbatim (ClusterNetworkPolicy schema).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.clusterNetworkPolicy.ingress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra spec.ingress rules merged verbatim (ClusterNetworkPolicy schema).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.clusterNetworkPolicy.priority&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1000&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;spec.priority on the ClusterNetworkPolicy (lower wins within the tier).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.defaultDeny&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Render a namespace-wide default-deny so EVERY pod in the namespace is denied by default and the per-workload allowlists are the only exceptions (zero-trust namespace). OFF by default because it also denies co-located workloads — enable only when JaaS has its own namespace. Off keeps a pod-scoped setup: only JaaS&amp;#39;s pods are locked down, neighbours untouched.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.defaultDeny.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.defaultDeny.order&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;2000&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Calico/ClusterNetworkPolicy sort key for the deny-all. It must rank the deny-all AFTER the per-workload allowlists so the allowlists win: Calico evaluates lower order first (the allowlist policies carry no order = lowest precedence, so this defaults high), and ClusterNetworkPolicy gives lower priority numbers higher precedence (the allowlist priority defaults to networkPolicy.clusterNetworkPolicy.priority, so this defaults higher). The kubernetes and cilium engines have no precedence knob — deny &amp;#43; allow simply combine additively, allow wins.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.egress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.egress.dns&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;When egress is enabled, allow DNS (UDP&amp;#43;TCP 53) to the cluster DNS namespace.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.egress.dnsNamespace&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;kube-system&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Namespace of the cluster DNS service (matched by kubernetes.io/metadata.name).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.egress.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Render egress rules. Adds Egress to policyTypes, so everything not explicitly allowed is denied. Off by default: enable only after allowing everything the pod needs (DNS, the kube-apiserver, source-controller, S3, the OTLP collector) via egress.to — otherwise the operator loses cluster access.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.egress.to&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Additional egress peers merged verbatim into spec.egress. This is where you allow the kube-apiserver (an ipBlock CIDR), the flux-system namespace (source-controller), and external endpoints (S3, OTLP). NetworkPolicy cannot select the apiserver by label, so it must be an ipBlock here.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Opt-in. When enabled, a NetworkPolicy targets the jaas pod and locks the storage HTTP port down to the configured consumer selector (defaults to any pod in flux-system — kustomize-controller / helm-controller). Other ports stay open so kubelet probes, the jsonnet HTTP path, and the webhook traffic from kube-apiserver continue to work. Leave OFF on initial install if the cluster has no NetworkPolicy controller (Calico/Cilium/etc.) — without one, the policy is silently inert; with one, default-deny semantics apply to everything not listed here.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.engine&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;kubernetes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Policy engine to render for. kubernetes (default) emits a vanilla networking.k8s.io NetworkPolicy. cilium / calico / clusterNetworkPolicy emit the engine-native equivalent instead (CiliumNetworkPolicy, projectcalico.org NetworkPolicy, or policy.networking.k8s.io ClusterNetworkPolicy). The per-port .from knobs below apply to the kubernetes engine only; alternative engines are pod-scoped allow-all on the required ports and tighten via their native passthrough lists (cilium/calico/clusterNetworkPolicy .ingress / .egress).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.http&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Restrict sources allowed to hit the jsonnet HTTP endpoint. Empty list allows everything (typical when an Ingress fronts the service).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.http.from&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.management&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Restrict sources allowed to hit the management probes. Empty list allows everything — kubelet probes source from the node IP.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.management.from&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.metrics&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Sources allowed to scrape the operator metrics port. Empty = all (typically your Prometheus). Only rendered in operator mode with metrics enabled.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.metrics.from&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.storage&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Sources allowed to read published ExternalArtifact tarballs from the storage HTTP port. The consumers are the Flux controllers that dereference ExternalArtifact.status.artifact.url — kustomize-controller and helm-controller (both in flux-system) — NOT source-controller, which only produces its own typed artifacts. Defaults to any pod in flux-system so the stock Flux consumers work out of the box. Add an entry per extra consumer namespace (e.g. stageset-controller in stageset-system): - namespaceSelector: matchLabels: kubernetes.io/metadata.name: stageset-system&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.storage.from&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.webhook&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Restrict sources allowed to dial the webhook. Empty list allows everything — kube-apiserver cannot be expressed as a podSelector.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;networkPolicy.webhook.from&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Operator mode — opt-in. When enabled, jaas runs alongside its HTTP path as a Kubernetes operator that watches JsonnetSnippet / JsonnetLibrary CRs and publishes evaluated results as Flux ExternalArtifact resources.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;On `helm uninstall`, run a pre-delete Job that bulk-deletes every JsonnetSnippet so the operator&amp;#39;s finalizer drops the published ExternalArtifact &amp;#43; tarball BEFORE the operator pod itself is removed. Without this, the operator goes away first and downstream Flux consumers are left referencing orphaned ExternalArtifacts. Disable when uninstall is driven by something other than Helm (e.g., ArgoCD with hooks off) — the manual cleanup pattern is: kubectl delete jsonnetsnippet --all -A --wait=true --timeout=2m helm uninstall jaas -n &amp;lt;ns&amp;gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.activeDeadlineSeconds&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;600&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Job-level safety net. activeDeadlineSeconds caps the Job&amp;#39;s total runtime; if the operator is wedged, Helm won&amp;#39;t be blocked forever.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.backoffLimit&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;backoffLimit on the pre-delete Job.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.image&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Image for the pre-delete cleanup Job (a kubectl image).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.image.pullPolicy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;IfNotPresent&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.image.registry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;registry.k8s.io&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.image.repository&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;kubectl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.image.tag&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;v1.34.0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.kubectlTimeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;2m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Maximum wall-clock time the pre-delete Job waits for snippet finalizers to run before it strips them forcibly and proceeds. Bigger when snippets publish to slow S3 endpoints.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.resources&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Resource requests/limits for the cleanup Job container.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.resources.cpu&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;100m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.resources.ephemeralStorage&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;16Mi&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Ephemeral-storage requests/limits. The cleanup Job runs a few `kubectl` calls then exits — disk usage is negligible. Sized small to satisfy kube-score&amp;#39;s resource-completeness check without crowding cluster ephemeral-storage budgets.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.cleanupOnDelete.resources.memory&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;64Mi&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.defaultServiceAccount&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;The default ServiceAccount the operator impersonates when a snippet does not specify spec.serviceAccountName. Snippets without an effective SA are rejected at reconcile time.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.extVars&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Operator-level external variables. Keys here are non-overridable: a JsonnetSnippet whose spec.externalVariables names any of these keys is rejected at admission (and as a fallback at reconcile time).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.labelSelector&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Optional label selector that narrows which CRs the operator watches. Leave empty to watch every CR in the cluster.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.leaderElection&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Leader election guards against two operator replicas double-reconciling the same JsonnetSnippet. With replicas.max: 1 (the chart default) only one replica ever runs, so the lock is just defensive — but operators who scale the Deployment up (rolling restarts, blue-green) MUST keep this on. Off is supported (single replica only) but unsafe to scale.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.leaderElection.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.leaderElection.id&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Lease object name. Defaults to &amp;#34;&amp;lt;release name&amp;gt;-operator&amp;#34; so multiple JaaS installs in the same namespace don&amp;#39;t fight over a shared lease.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.maxWithdrawWait&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1h&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Bound the time a deleted JsonnetSnippet&amp;#39;s finalizer can hold while Publisher.Withdraw keeps failing. Past this, the operator emits a Warning WithdrawForced event, drops the finalizer, and lets the snippet be garbage-collected — possibly leaving an orphan tarball in storage (`&amp;lt;namespace&amp;gt;/&amp;lt;name&amp;gt;/&amp;lt;rev&amp;gt;.tar.gz`). Required so a permanently-broken backend (S3 perma-down, deleted bucket, revoked RBAC) doesn&amp;#39;t block namespace teardown. 1h rides out transient apiserver/S3 incidents while bounding the wait.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Controller-runtime exposes a Prometheus metrics endpoint covering workqueue depth, reconcile latencies, the manager&amp;#39;s REST client, and whatever custom collectors the reconciler registers. The chart wires the endpoint to a dedicated Service; ServiceMonitor rendering is opt-in for clusters running the Prometheus Operator.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Opt-in PrometheusRule shipping a starter set of alerts on the operator&amp;#39;s custom metrics plus a handful of standard controller-runtime signals. Requires the Prometheus Operator&amp;#39;s `monitoring.coreos.com/v1` API to be installed in the cluster. Thresholds default to conservative values — page on sustained failure patterns, not single bad reconciles. Tune per cluster.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.annotations&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra annotations on the PrometheusRule.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.extraAlertLabels&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Labels merged onto EVERY alert this PrometheusRule renders. Useful for routing all jaas alerts through one Alertmanager receiver (e.g., `team: platform`).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.extraRules&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra alert rules appended to the rendered PrometheusRule under a separate &amp;#34;jaas-operator-extras&amp;#34; group. Lets operators ship cluster-specific alerts (e.g. custom thresholds on jaas metrics, alerts joined against deployment labels) without forking the chart. The list is rendered verbatim — each entry must be a valid PrometheusRule alert (see Prometheus documentation for the schema). To silence a built-in alert: raise its threshold under the `thresholds` block above to an impossibly high value. There is no per-built-in disable toggle — the threshold pattern is the recommended path because it surfaces &amp;#34;this alert is intentionally inert&amp;#34; in the chart values rather than hiding the configuration behind a flag list. Example: extraRules: - alert: JaaSManySnippetsDown expr: count(jaas_snippet_reconcile_total{status=&amp;#34;False&amp;#34;}) &amp;gt; 10 for: 5m labels: { severity: critical, team: platform } annotations: summary: &amp;#39;&amp;gt;10 snippets failing across all namespaces&amp;#39;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;30s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Evaluation interval for every group in the rendered rule.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.labels&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra labels copied onto the PrometheusRule object itself (typically the labels your Prometheus instance selects on).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.runbookAnnotationKey&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;runbook_url&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Annotation key the runbook URL lands under on every alert. `runbook_url` is the Prometheus-operator convention (kube-prom, Alertmanager templates, most third-party tooling key off it); `runbook` is common in older internal stacks. Anything goes — the value is just a YAML map key.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;All-numeric/duration thresholds in one place so operators can adjust the noise floor without copy-pasting the rule body.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.artifactSizeBytes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;16777216&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSSnippetArtifactGrowing: p99 rendered bytes threshold. Defaults to 16 MiB — well below the 64 MiB MaxArchiveBytes default but enough to catch a runaway snippet early.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.artifactSizeDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;30m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.crdWatchEngagementFailuresDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;30m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.crdWatchEngagementFailuresPerHour&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSCRDWatchEngagementFailing: hourly increase on the CRD watch engagement failure counter. The retry pipeline will eventually re-engage, but a sustained inability to engage means dependent snippets aren&amp;#39;t re-rendering on upstream source events.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.evalLeakedDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;5m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.evalLeakedFloor&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSEvalLeakedGoroutines: floor on the leak gauge before alerting. The gauge counts eval goroutines whose parent ctx already fired — orphans still consuming CPU until their synchronous go-jsonnet call returns naturally. CLAUDE.md&amp;#39;s documented design is &amp;#34;&amp;gt; 0 for 5m&amp;#34; — any sustained leak is textbook runaway-snippet signal. 0 is intentional; the accompanying duration window catches transient spikes without paging.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.evalRejectedDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.evalRejectedRate&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0.05&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSEvalRejected: per-second rate of evaluations the semaphore turned away. 0.05/s ≈ one rejection every 20s, a rate that indicates sustained overload rather than a transient spike a single retry would clear.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.evalSaturationDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.evalSaturationRatio&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0.9&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSEvalSaturation: ratio of in-flight evaluations to the configured cap before alerting. 0.9 means &amp;#34;running at 90 %&amp;#43; of the eval semaphore for evalSaturationDuration&amp;#34; — close enough to the cap that any new burst will start landing rejections. The alert guards on jaas_eval_max_concurrent &amp;gt; 0 so a disabled gate (--max-concurrent-evals=0) never fires.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.forceDropsDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;5m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.forceDropsPerHour&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSForceDropsAccumulating: hourly increase on the force-drop counter. Even a single force-drop means an orphan tarball was left behind; sustained drops mean a permanently-broken pipeline (revoked RBAC on the EA Delete, deleted S3 bucket, missing CRD). The remediation runbook is storage-recovery.md.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.podDownDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;5m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSOperatorPodDown: how long an operator pod can stay NotReady before paging. Below 5m the alert tends to fire during normal pod rolls.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.reconcileErrorDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.reconcileErrorRate&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0.1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSSnippetReconcileErrorsHigh: per-snippet Ready=False rate (reconciles per second, 5m window). 0.1/s ≈ one failure every 10s — a clearly stuck snippet, not transient flapping.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.reconcileLatencyDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;15m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.reconcileLatencySeconds&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;30&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSReconcileLatencyHigh: reconcile-time p99 ceiling, in seconds. 30s is generous — long enough for an OCI fetch plus eval of a non-trivial snippet, short enough to catch a genuinely stuck reconciler.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.sweepFailuresDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;30m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.sweepFailuresPerHour&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSStorageSweepFailures: per-hour count of failing background sweep passes before alerting. Default 3 absorbs the occasional flake without paging while catching a genuinely degraded backend (chronic disk full, revoked permissions). The sweep runs on operator.storage.sweep.interval (default 10m), so 3/hour ≈ half the passes failing.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.tenantTokenMintFailureDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.tenantTokenMintFailureRate&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0.01&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSTenantTokenMintFailing: per-second mint failure rate across the cluster. Even one mint failure on a (namespace, SA) pair flags revoked `serviceaccounts/token: create` or a deleted SA — actionable immediately.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.webhookCertRenewalFailuresDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;30m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.webhookCertRenewalFailuresPerHour&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSWebhookCertRenewalFailing: hourly increase on the cert renewal failure counter. The Renewer ticks every Validity/3 (typically every few months), so a single failure shouldn&amp;#39;t page; sustained failures across multiple ticks (RBAC drift, CertDir write-perm loss) mean the rotation pipeline is broken and the existing cert&amp;#39;s natural expiry is the deadline.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.workqueueDepth&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;50&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JaaSControllerWorkqueueDepthHigh: items the workqueue holds before alerting. Default 50 is high enough to absorb a normal roll-out, low enough that genuine apiserver stalls page.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.prometheusRule.thresholds.workqueueDuration&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;15m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.serviceMonitor&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Opt-in ServiceMonitor (Prometheus Operator) for the metrics endpoint.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.serviceMonitor.annotations&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra annotations on the ServiceMonitor.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.serviceMonitor.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.serviceMonitor.interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;30s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Scrape interval the Prometheus instance honors. Match this to your shared scrape config.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.serviceMonitor.labels&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra labels the ServiceMonitor carries (typically the labels your Prometheus instance selects on, e.g., release: kube-prom).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.metrics.serviceMonitor.scrapeTimeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Per-scrape timeout. Must stay strictly less than interval.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.noCrossNamespaceRefs&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;When true, the operator rejects JsonnetSnippet / library refs that point at a SourceRef in a different namespace.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.rbac&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Opt out of chart-rendered RBAC when external governance owns ClusterRole/RoleBinding shape (ArgoCD app-of-apps, Crossplane, GitOps controller that manages RBAC separately). The operator still expects the named SA to have the verbs documented in README&amp;#39;s &amp;#34;Operator Mode&amp;#34; section; with rbac.create=false you&amp;#39;re responsible for granting them.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.rbac.create&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.rerenderBurst&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;120&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Per-snippet token-bucket depth.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.rerenderRate&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;60/min&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Per-snippet steady-state re-render budget. N/period where period is one of sec, min, hour. Token-bucket combined with rerenderBurst.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.serviceAccount&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;ServiceAccount the operator runs as. When create is true the chart provisions one; otherwise the operator binds against the named SA.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.serviceAccount.annotations&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Annotations to attach to the operator ServiceAccount. Common uses: AWS IRSA (eks.amazonaws.com/role-arn), GCP Workload Identity (iam.gke.io/gcp-service-account), Azure Workload Identity (azure.workload.identity/client-id). These bind cloud-IAM identities to the SA so the operator can access cloud resources without static credentials.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.serviceAccount.create&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.serviceAccount.name&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;jaas&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Artifact storage for published ExternalArtifact tarballs (operator mode).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.backend&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;local&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Artifact backend used to persist ExternalArtifact tarballs. local — emptyDir or PVC mounted into the pod. Simple, single-pod (RWO PVC) unless a ReadWriteMany storage class is configured. s3 — any S3-compatible bucket (AWS S3, MinIO, Ceph RGW, etc.). The chart-default story for multi-replica HA: every replica reads from the same bucket; leader election still gates writes. PVC settings below are ignored when backend=s3. Artifact backend used to persist ExternalArtifact tarballs (local | s3).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.baseURL&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Public URL prefix downstream Flux consumers dereference. Defaults to the in-cluster Service DNS name when empty — operators wiring this up via Ingress should set it explicitly.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.gcGrace&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;5m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Minimum time a superseded artifact revision stays fetchable after being evicted from the keep-set. Closes the pin→fetch race in which a Flux consumer reads ExternalArtifact.status.artifact a moment before the operator garbage-collects the superseded revision and then 404s on the URL. Supersession time is derived from on-disk storage metadata so the window survives operator restarts. Zero restores eager pruning; the snippet teardown path (finalizer Withdraw) is unaffected. See docs/consumers.md for when to leave the default vs raise spec.history for deliberate rollback retention.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.headless&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;When true, the storage Service is rendered as headless (clusterIP: None) so DNS resolves each pod IP directly. Useful for external sidecars that want stable per-pod targets, or for traffic that should skip kube-proxy. Default ClusterIP otherwise.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.listenAddress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;::&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Bind address for the storage HTTP server. Same dual-stack semantics as arguments.listenAddress: `::` accepts both IPv6 and IPv4-mapped traffic on Linux.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.maxArtifactBytes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Cap the rendered artifact size in bytes. Snippets whose rendered output exceeds this fail with ReasonArtifactTooLarge before any disk/S3 write — stops one runaway snippet from filling a shared storage volume. Zero disables.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.path&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/var/lib/jaas/artifacts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Directory inside the container the operator writes tarballs to. Only consulted when backend=local.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.persistence&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Persist tarballs across pod restarts via a PVC. With persistence OFF (the default) the chart uses an emptyDir, which is lost on pod restart — downstream Flux consumers then see brief 404s while every snippet re-renders. With persistence ON, the PVC survives restarts and downstream consumers see no gap. PVC access mode is RWO by default, which constrains the chart to a single replica (RWO can only attach to one pod). For multi-replica HA you need an RWX storage class — uncommon in cloud providers.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.persistence.accessModes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;accessModes the PVC declares. ReadWriteOnce is the safe default; ReadWriteMany unlocks multi-replica HA when the StorageClass supports it.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.persistence.annotations&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra annotations on the PVC (e.g., CSI volume tags).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.persistence.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.persistence.labels&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra labels on the PVC.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.persistence.selector&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Optional PVC selector for matching pre-provisioned volumes.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.persistence.size&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10Gi&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Requested volume size. 10 GiB holds many thousand small tarballs.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.persistence.storageClassName&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;storageClassName picks the StorageClass. Empty means &amp;#34;cluster default&amp;#34; — usually right.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.readTimeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;30s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Read timeout on the storage HTTP server.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.s3&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;S3 backend configuration. Only consulted when backend=s3.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.s3.anonymous&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Skip request signing entirely. Public-bucket test mode only.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.s3.bucket&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Bucket the artifacts live in. Must already exist; jaas does not create it. Required.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.s3.credentialsSecret&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Credentials are supplied one of two ways — never inline. Inline values would land on the Pod&amp;#39;s command line (visible in `ps`, in the PodSpec, and in the stored Helm release), so the chart deliberately offers no accessKey/secretKey/sessionToken field: credentialsSecret.name — an existing Secret carrying AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optionally AWS_SESSION_TOKEN. It is wired into the pod via envFrom.secretRef; minio-go&amp;#39;s env chain picks the keys up automatically since no -s3-* credential flag is passed. leave credentialsSecret empty — minio-go&amp;#39;s IAM/IRSA discovery chain takes over (AWS_* env vars, EKS web-identity, EC2 metadata). Bind a cloud identity to the operator&amp;#39;s ServiceAccount via operator.serviceAccount.annotations.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.s3.credentialsSecret.name&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.s3.endpoint&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Endpoint host:port (e.g. s3.amazonaws.com, minio.minio.svc:9000). Required.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.s3.prefix&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Optional object-key prefix so jaas can coexist with other tenants in one bucket: &amp;lt;prefix&amp;gt;/&amp;lt;namespace&amp;gt;/&amp;lt;snippet&amp;gt;/&amp;lt;rev&amp;gt;.tar.gz&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.s3.region&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Region the bucket lives in. Required for AWS multi-region; ignored by most S3-compatible servers.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.s3.useSSL&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Talk HTTPS. Disable only for local MinIO over HTTP.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.sizeLimit&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;256Mi&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;emptyDir size limit. Only consulted when persistence.enabled is false. Tarballs typically are well under 1 MiB; the sizing here is generous so transient bursts don&amp;#39;t OOM the pod.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.sweep&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Periodic GC: sweep orphaned &amp;lt;rev&amp;gt;.tar.gz.tmp residue left by Puts whose process died mid-rename. Local backend only — the S3 backend&amp;#39;s Put is atomic so no .tmp files exist. Set interval to &amp;#34;0s&amp;#34; to disable.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.sweep.interval&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.sweep.maxTmpAge&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;30m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.storage.writeTimeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;5m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Write timeout on the storage HTTP server (--storage-write-timeout): how long a single response is allowed to take before the connection is closed. To cap artifact size, use operator.storage.maxArtifactBytes.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.tracing&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;OpenTelemetry tracing for the operator. When endpoint is empty (the default), no spans are emitted and the OTel SDK is in no-op mode. Set the endpoint to ship spans to an OTLP gRPC collector: tracing: endpoint: otel-collector.observability.svc:4317 insecure: true&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.tracing.endpoint&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.tracing.insecure&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.tracing.sampleRatio&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.watchNamespaces&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Namespaces this operator instance scopes its cache to. Empty (the default) means cluster-wide. When set, the manager&amp;#39;s informer cache only observes CRs in these namespaces, AND the rendered RBAC pivots from a single ClusterRoleBinding to one RoleBinding per namespace — multi-tenant operator-instances pattern.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Validating admission webhook (operator mode). Rejects JsonnetSnippets whose spec.externalVariables collide with the operator&amp;#39;s extVars.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.certDir&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/tmp/k8s-webhook-server/serving-certs&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Directory inside the container where the TLS cert and key land. With certMode=cert-manager this is mounted read-only from the secretName below. With certMode=self-signed it&amp;#39;s a writable emptyDir the operator writes into on startup.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.certManager&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;cert-manager-mode knobs. Only consulted when certMode is &amp;#34;cert-manager&amp;#34;. When self-signed, this block is ignored and no cert-manager API objects are rendered.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.certManager.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.certManager.issuerRef&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.certManager.issuerRef.kind&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Issuer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Issuer kind: Issuer or ClusterIssuer&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.certManager.issuerRef.name&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Issuer name in the chart&amp;#39;s namespace (Issuer) or cluster (ClusterIssuer). Required when certMode=cert-manager.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.certMode&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;self-signed&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;How TLS material for the webhook is provisioned: cert-manager — chart renders a cert-manager Certificate; the Secret is mounted into the pod read-only. self-signed — operator generates a CA &amp;#43; serving cert at startup and stamps its own ValidatingWebhookConfiguration caBundle. Requires the operator to have get/update on the named VWC (added automatically). Removes the cert-manager dependency entirely. The default is self-signed because it needs no prerequisites and always works; cert-manager mode only works when cert-manager is installed in the cluster.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.failurePolicy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Fail&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Failure policy for the ValidatingWebhookConfiguration. Fail is the safer default — a webhook outage blocks JsonnetSnippet writes cluster-wide until the operator is back. Switch to Ignore only when you&amp;#39;re willing to accept invalid snippets reaching the reconciler. Either way, the reconciler enforces the same ext-var-conflict invariant as a fallback so admission bypass never silently breaks the contract. During an operator restart (rolling update, crash-loop, eviction) the webhook is briefly unreachable and Fail blocks every JsonnetSnippet create/update for that window — typically &amp;lt;5s with `LeaderElectionReleaseOnCancel: true`. If your CI/GitOps tooling can&amp;#39;t tolerate that, scope the webhook with objectSelector / namespaceSelector to limit the blast radius, or set failurePolicy: Ignore.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.matchConditions&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;matchConditions are CEL expressions evaluated server-side before the webhook is dialed. Lets operators short-circuit admission checks for specific snippet shapes without a round trip. Requires Kubernetes 1.30&amp;#43;.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.namespaceSelector&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;namespaceSelector restricts the webhook to Namespaces carrying the listed labels. A common pattern: opt-in per namespace via a label so the apiserver doesn&amp;#39;t dial the webhook for unrelated namespaces during operator outages. Example: namespaceSelector: matchLabels: jaas.metio.wtf/admission: enforce&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.objectSelector&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;objectSelector restricts the webhook to JsonnetSnippets carrying the listed labels. Useful when only some snippets are operator-managed (the rest live in Argo or kustomize). Example: objectSelector: matchLabels: jaas.metio.wtf/managed: &amp;#34;true&amp;#34;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.secretName&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;jaas-webhook-cert&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The Secret the cert lives in. cert-manager writes it; the Deployment mounts it read-only at certDir.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.selfSignedValidity&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;8760h&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Validity of the self-signed serving cert. Operators that want short-lived rotation should use cert-manager instead — the self-signed mode renews on every pod restart, no in-flight rotation. Only consulted when certMode=self-signed.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;operator.webhook.sideEffects&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Side-effect mode for the ValidatingWebhookConfiguration.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;paths&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Filesystem paths inside the container. Modify the paths used, use this in case you run a custom image.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;paths.libraries&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/srv/libraries&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Directory library OCI volumes mount beneath.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;paths.snippets&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/srv/snippets&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Directory snippet OCI volumes mount beneath.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pdb&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Fine tune the PodDisruptionBudget. Rendered only when replicas.max &amp;gt; replicas.min.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pdb.maxUnavailable&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer|string|null&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;PDB maxUnavailable. Default 1 caps voluntary disruptions to one pod at a time. Set to null when using minAvailable instead.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pdb.minAvailable&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer|string|null&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Disruption budget shape. Exactly one of minAvailable / maxUnavailable is rendered into the PDB spec; setting both is a config error K8s would reject. Default `maxUnavailable: 1` caps voluntary disruptions to one pod at a time regardless of replica count — the right knob for a rolling restart / node drain story. `minAvailable: N` is the alternative when you want &amp;#34;always keep at least N pods up&amp;#34; instead — useful only when the workload&amp;#39;s headroom is a fixed floor, not the more common &amp;#34;one out at a time&amp;#34;. Override examples: pdb: { maxUnavailable: 2 } # allow draining two nodes in parallel pdb: { minAvailable: 1, maxUnavailable: null } # floor of 1 instead pdb: { minAvailable: &amp;#34;50%&amp;#34;, maxUnavailable: null } # percentage form PDB minAvailable. Mutually exclusive with maxUnavailable; null leaves it unset.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pdb.unhealthyPodEvictionPolicy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;spec.unhealthyPodEvictionPolicy on the PDB. Empty defers to the cluster default.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pod&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Fine tune the managed Pod resources.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pod.additionalLabels&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Extra labels merged onto the pod template.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ports&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Container port assignments for the jaas pod.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ports.http&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;8080&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Used by the /jsonnet endpoint&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ports.management&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;8081&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Used by startup, readiness, and liveness probes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ports.metrics&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;8083&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Used by controller-runtime&amp;#39;s Prometheus metrics endpoint (operator.enabled only). Defaults to 8083 to avoid the collision with controller-runtime&amp;#39;s own :8080 default and the jsonnet HTTP port. Set to 0 in operator.metrics.enabled to disable entirely.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ports.storage&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;8082&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Used by the operator&amp;#39;s storage HTTP server (operator.enabled only)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ports.webhook&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;9443&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Used by the validating admission webhook (operator.webhook.enabled only)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;replicas&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Replica bounds for the Deployment. It is safe to increase the number of replicas.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;replicas.max&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;replicas.min&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;resources&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Per-container resource requests/limits. Smallest possible values here, increase if you have more resources to spare.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;resources.cpu&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;32m&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;resources.ephemeralStorage&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10Mi&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;resources.memory&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;64Mi&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;service&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Fine tune the Service resource.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;service.ipFamilies&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Explicit list of IP families for the Service. Empty defers to the cluster&amp;#39;s default. Example for explicit IPv4&amp;#43;IPv6: ipFamilies: [IPv4, IPv6] Order matters: the first family becomes the primary ClusterIP.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;service.ipFamilyPolicy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;K8s Service ipFamilyPolicy. Empty leaves the cluster&amp;#39;s default (usually SingleStack). Set to PreferDualStack on dual-stack clusters that may still have single-stack nodes, or RequireDualStack to refuse single-stack clusters at admission.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Opt-in service-mesh L7 authorization &amp;#43; mTLS for the jaas pod. Complementary to networkPolicy, NOT a replacement: networkPolicy is L3/L4 (which pods/IPs may open a connection), serviceMesh is L7/identity (which mesh identities may call which port). Both can be enabled at once and stack additively. Authorizes only the mesh-reachable ports — http (jsonnet), storage (artifacts), and metrics (operator). The webhook (kube-apiserver) and management/probe (kubelet) ports are deliberately left out: those callers speak plain TLS / no mesh identity, and locking them to mesh principals would break admission and health probing.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.defaultDeny&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Render a namespace-wide default-deny so every pod in the install namespace rejects unauthorized mesh traffic and the per-workload allows above are the only exceptions (zero-trust namespace). Istio renders an empty-spec AuthorizationPolicy (deny-all) scoped to the whole namespace, which sits at lower precedence than the workload ALLOW. Linkerd has no per-object deny-all; the namespace default is set via the config.linkerd.io/default-inbound-policy annotation, so this is stamped onto the chart-managed Namespace (requires namespace.create=true) — otherwise annotate the namespace out-of-band. Enable only when jaas owns its namespace; it also denies co-located workloads.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.defaultDeny.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Opt-in. When enabled, the chart renders the selected engine&amp;#39;s authorization &amp;#43; mTLS objects for the jaas pod. Inert unless that mesh (Istio or Linkerd) is actually installed and the pod is injected.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.engine&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;istio&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Mesh engine to render for. istio emits security.istio.io AuthorizationPolicy (&amp;#43; optional PeerAuthentication). linkerd emits policy.linkerd.io Server / AuthorizationPolicy / MeshTLSAuthentication objects.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.http&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Per-port allowed mesh identities. Each `from` entry is a source matcher: - principals: SPIFFE/mesh identities (Istio source.principals, e.g. cluster.local/ns/&amp;lt;ns&amp;gt;/sa/&amp;lt;sa&amp;gt;). For Linkerd these map to MeshTLSAuthentication identities (the proxy identity string, e.g. &amp;lt;sa&amp;gt;.&amp;lt;ns&amp;gt;.serviceaccount.identity.linkerd.cluster.local, or &amp;#34;*&amp;#34;). - namespaces: source namespaces (Istio source.namespaces). ISTIO-ONLY — Linkerd authenticates by workload identity, not by namespace, so this field is ignored on the linkerd engine. An EMPTY `from` list on a port means OPEN (allow any caller), mirroring networkPolicy&amp;#39;s empty-`from`=open semantics. On Istio that is a port rule with no `from`; on Linkerd it is a MeshTLSAuthentication of identities: [&amp;#34;*&amp;#34;] (any authenticated meshed client). Mesh identities allowed to hit the jsonnet HTTP endpoint (ports.http). Empty = open.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.http.from&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.istio&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Istio engine raw passthrough. Entries are merged verbatim into the AuthorizationPolicy&amp;#39;s spec.rules (security.istio.io/v1 rule schema). Use this to add rules the per-port `from` knobs above can&amp;#39;t express — path/method matchers, `when` JWT-claim conditions, ipBlocks, etc.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.istio.rules&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.linkerd&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Linkerd engine raw passthrough. Entries are appended verbatim as additional documents after the rendered Server / AuthorizationPolicy / MeshTLSAuthentication set — each entry must be a complete object (policy.linkerd.io AuthorizationPolicy, HTTPRoute, etc.).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.linkerd.authorizations&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.metrics&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Mesh identities allowed to scrape the operator metrics port (ports.metrics). Operator mode &amp;#43; metrics only. Empty = open.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.metrics.from&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.mtls&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;mTLS posture (Istio engine only). Empty defers to the mesh&amp;#39;s own default (mesh-wide PeerAuthentication / MeshConfig). permissive renders a PeerAuthentication accepting both mTLS and plaintext. strict requires mTLS on the workload&amp;#39;s ports EXCEPT the webhook &amp;#43; management ports, which get a port-level PERMISSIVE carve-out so the non-mesh kube-apiserver and kubelet still connect. Linkerd negotiates mTLS automatically between meshed pods, so this knob does not apply to the linkerd engine.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.storage&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Mesh identities allowed to read published artifacts from the storage port (ports.storage). Operator mode only. Empty = open.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;serviceMesh.storage.from&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;array&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;snippets&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;object|null&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;(empty)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;Snippet OCI volumes to mount. The key is the name of the directory and the value is an OCI volume that will be mounted beneath .Values.paths.snippets, e.g., the commented example below would result in all files from &amp;#39;ghcr.io/metio/something:latest&amp;#39; to be mounted at &amp;#39;/srv/snippets/something/&amp;#39; (using the default configuration)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="joi-library-chart"&gt;joi library chart&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/metio/helm-charts/tree/main/charts/joi"&gt;joi&lt;/a&gt;
 chart
publishes &lt;a href="https://github.com/metio/jsonnet-oci-images"&gt;Jsonnet OCI Images&lt;/a&gt;
 as
&lt;code&gt;JsonnetLibrary&lt;/code&gt; + &lt;code&gt;OCIRepository&lt;/code&gt; pairs, so snippets can import vendored
libraries (grafonnet, k8s-libsonnet, …) without bundling them. Deploy it
alongside jaas when snippets reference shared libraries.&lt;/p&gt;</description></item><item><title>High reconcile latency</title><link>https://jaas.projects.metio.wtf/runbooks/reconcile-latency/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/reconcile-latency/</guid><description>&lt;p&gt;Linked from the &lt;code&gt;JaaSReconcileLatencyHigh&lt;/code&gt; alert. Fires when the controller-runtime &lt;code&gt;controller_runtime_reconcile_time_seconds&lt;/code&gt; histogram p99 exceeds the configured threshold (default 30s) for the alert window.&lt;/p&gt;
&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ALERTS{alertname=&amp;#34;JaaSReconcileLatencyHigh&amp;#34;, controller=&amp;#34;jsonnetsnippet&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubectl get jsonnetsnippet&lt;/code&gt; shows status updates trickling in well after spec changes.&lt;/li&gt;
&lt;li&gt;Operator pod CPU is moderate-to-high but the queue is draining (distinguishes this from &lt;a href="https://jaas.projects.metio.wtf/runbooks/workqueue-saturation/"&gt;workqueue-saturation.md&lt;/a&gt;
, where the queue itself is growing).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;Reconcile latency is the wall-clock cost of one &lt;code&gt;Reconcile()&lt;/code&gt; call. Inside the call, JaaS does (in order):&lt;/p&gt;</description></item><item><title>InvalidSpec</title><link>https://jaas.projects.metio.wtf/runbooks/invalidspec/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/invalidspec/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=InvalidSpec&lt;/code&gt;. The condition Message names which field is at fault.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;Spec-level validation that admission should have caught but the reconciler is enforcing as a fallback:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;spec.entryFile&lt;/code&gt; is empty&lt;/li&gt;
&lt;li&gt;both &lt;code&gt;spec.files&lt;/code&gt; and &lt;code&gt;spec.sourceRef&lt;/code&gt; are set (mutually exclusive)&lt;/li&gt;
&lt;li&gt;neither &lt;code&gt;spec.files&lt;/code&gt; nor &lt;code&gt;spec.sourceRef&lt;/code&gt; is set&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.entryFile&lt;/code&gt; does not match any key in the resolved file map&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe jsonnetsnippet &amp;lt;name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Read the Message — it names the field.&lt;/p&gt;
&lt;h2 id="remediation"&gt;Remediation&lt;/h2&gt;
&lt;p&gt;Fix the spec and reapply. If the validating webhook is enabled (&lt;code&gt;--enable-webhook&lt;/code&gt;), &lt;code&gt;kubectl apply&lt;/code&gt; rejects the invalid spec at admission instead of letting it land and fail later.&lt;/p&gt;</description></item><item><title>JaaS and grafana-operator</title><link>https://jaas.projects.metio.wtf/comparisons/grafana-operator/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/comparisons/grafana-operator/</guid><description>&lt;p&gt;JaaS and the &lt;a href="https://grafana.github.io/grafana-operator/"&gt;grafana-operator&lt;/a&gt;
 are
not alternatives — they do different jobs and are commonly used together. JaaS
&lt;em&gt;produces&lt;/em&gt; dashboard JSON from Jsonnet; grafana-operator &lt;em&gt;consumes&lt;/em&gt; dashboard
JSON and reconciles it into a Grafana instance.&lt;/p&gt;
&lt;h2 id="division-of-labour"&gt;Division of labour&lt;/h2&gt;
&lt;p&gt;grafana-operator manages Grafana itself. It reconciles &lt;code&gt;GrafanaDashboard&lt;/code&gt;,
&lt;code&gt;GrafanaDatasource&lt;/code&gt;, &lt;code&gt;GrafanaFolder&lt;/code&gt;, and related resources into one or more
Grafana instances, handling authentication, folder placement, datasource wiring,
and drift correction inside Grafana. A &lt;code&gt;GrafanaDashboard&lt;/code&gt; can take its dashboard
model from inline JSON, a URL, a ConfigMap, a &lt;code&gt;grafana.com&lt;/code&gt; dashboard ID, or a
remote source.&lt;/p&gt;</description></item><item><title>JaaS vs jsonnet-controller</title><link>https://jaas.projects.metio.wtf/comparisons/jsonnet-controller/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/comparisons/jsonnet-controller/</guid><description>&lt;p&gt;&lt;a href="https://github.com/pelotech/jsonnet-controller"&gt;pelotech/jsonnet-controller&lt;/a&gt;
 is
a Flux-style controller that builds Jsonnet inside the controller and applies the
result to the cluster, with a configuration model compatible with
&lt;a href="https://github.com/kubecfg/kubecfg"&gt;kubecfg&lt;/a&gt;
. JaaS and jsonnet-controller both
turn Jsonnet into Kubernetes objects under Flux, but they draw the boundary
between rendering and deployment in different places.&lt;/p&gt;
&lt;h2 id="coupled-build-and-apply-vs-a-rendering-service"&gt;Coupled build-and-apply vs a rendering service&lt;/h2&gt;
&lt;p&gt;jsonnet-controller couples the two halves: one controller reads a Jsonnet
source, evaluates it, and applies the resulting objects to the cluster. The
rendered output lives inside the controller&amp;rsquo;s reconcile loop; the unit of
configuration is &amp;ldquo;build this Jsonnet and apply it here.&amp;rdquo;&lt;/p&gt;</description></item><item><title>JaaS vs Tanka</title><link>https://jaas.projects.metio.wtf/comparisons/tanka/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/comparisons/tanka/</guid><description>&lt;p&gt;&lt;a href="https://tanka.dev"&gt;Grafana Tanka&lt;/a&gt;
 and JaaS both render Kubernetes manifests
from Jsonnet, and both build on the same &lt;a href="https://github.com/jsonnet-bundler/jsonnet-bundler"&gt;&lt;code&gt;jsonnet-bundler&lt;/code&gt;&lt;/a&gt;

vendoring conventions. The difference is &lt;em&gt;where the rendering runs and how the
result reaches the cluster&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="the-two-models"&gt;The two models&lt;/h2&gt;
&lt;p&gt;Tanka renders and applies from a developer workstation or a CI runner. You
organise your code as environments (&lt;code&gt;environments/&amp;lt;env&amp;gt;/main.jsonnet&lt;/code&gt; plus a
&lt;code&gt;spec.json&lt;/code&gt;), run &lt;code&gt;tk show&lt;/code&gt; or &lt;code&gt;tk export&lt;/code&gt; to inspect the rendered objects, and
&lt;code&gt;tk apply&lt;/code&gt; to push them to the cluster the environment names in its &lt;code&gt;apiServer&lt;/code&gt;
field. The workstation or CI runner needs the Jsonnet toolchain, the vendored
library tree, and credentials for the target cluster.&lt;/p&gt;</description></item><item><title>JaaS vs the jsonnet CLI</title><link>https://jaas.projects.metio.wtf/comparisons/jsonnet-cli/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/comparisons/jsonnet-cli/</guid><description>&lt;p&gt;The &lt;a href="https://jsonnet.org/"&gt;&lt;code&gt;jsonnet&lt;/code&gt;&lt;/a&gt;
 command-line tool — usually paired with
&lt;a href="https://github.com/jsonnet-bundler/jsonnet-bundler"&gt;&lt;code&gt;jsonnet-bundler&lt;/code&gt;&lt;/a&gt;
 (&lt;code&gt;jb&lt;/code&gt;)
for vendoring libraries — evaluates Jsonnet to JSON on your machine. JaaS runs
the &lt;strong&gt;same go-jsonnet core&lt;/strong&gt; as a service. This is not a question of which
implementation is correct; it is a question of &lt;em&gt;where the evaluation runs and
what surrounds it&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="what-the-service-adds"&gt;What the service adds&lt;/h2&gt;
&lt;p&gt;Over a local binary invocation, JaaS adds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;An HTTP endpoint other systems can call.&lt;/strong&gt; &lt;code&gt;GET /jsonnet/&amp;lt;snippet&amp;gt;&lt;/code&gt; returns
the evaluated JSON, with Top Level Arguments supplied as query parameters and
external variables configured on the service. Anything that speaks HTTP can
request a render without installing the toolchain or the vendor tree. See the
&lt;a href="https://jaas.projects.metio.wtf/usage/rendering-endpoint/"&gt;rendering endpoint&lt;/a&gt;
 usage page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An operator that turns a snippet into a revisioned Flux artifact.&lt;/strong&gt; With
&lt;code&gt;--enable-flux-integration&lt;/code&gt;, a &lt;code&gt;JsonnetSnippet&lt;/code&gt; is evaluated continuously and
published as a content-addressed &lt;code&gt;ExternalArtifact&lt;/code&gt; that Flux consumers apply
in-cluster — re-rendered automatically when its source changes. See
&lt;a href="https://jaas.projects.metio.wtf/usage/operator-mode/"&gt;operator mode&lt;/a&gt;
.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Import resolution that matches &lt;code&gt;jsonnet -J vendor&lt;/code&gt;.&lt;/strong&gt; JaaS resolves imports
with the same semantics as the CLI&amp;rsquo;s JPATH/vendor search, so the JSON a
snippet produces under the service matches what the CLI produces locally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluation caps.&lt;/strong&gt; &lt;code&gt;--evaluation-timeout&lt;/code&gt; bounds wall-clock time per render,
&lt;code&gt;--max-stack&lt;/code&gt; bounds call-stack depth, and &lt;code&gt;--max-concurrent-evals&lt;/code&gt; bounds how
many evaluations run at once — so one expensive snippet cannot exhaust a
shared server. The CLI imposes none of these on its own.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read-scope sandboxing.&lt;/strong&gt; Snippet-name resolution goes through Go&amp;rsquo;s
&lt;code&gt;os.Root&lt;/code&gt;, which rejects &lt;code&gt;..&lt;/code&gt; traversal and symlinks that escape the
configured snippet directory, so a crafted request cannot read arbitrary
files. The &lt;a href="https://jaas.projects.metio.wtf/usage/evaluation-and-security/"&gt;evaluation and security&lt;/a&gt;
 page
details the caps and the boundaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-the-plain-cli-is-the-right-tool"&gt;When the plain CLI is the right tool&lt;/h2&gt;
&lt;p&gt;The CLI is the better choice for work that is local and one-off:&lt;/p&gt;</description></item><item><title>JOI images</title><link>https://jaas.projects.metio.wtf/usage/joi-images/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/joi-images/</guid><description>&lt;p&gt;&lt;a href="https://github.com/metio/jsonnet-oci-images"&gt;Jsonnet OCI Images&lt;/a&gt;
 (JOI) package
popular Jsonnet libraries as single-layer OCI images, one per upstream library,
published at &lt;code&gt;ghcr.io/metio/joi-&amp;lt;org&amp;gt;-&amp;lt;repo&amp;gt;&lt;/code&gt;. Because each image is a single
layer, the same artifact serves two roles: a container &lt;strong&gt;image volume&lt;/strong&gt; mounted
into jaas, and a Flux &lt;strong&gt;&lt;code&gt;OCIRepository&lt;/code&gt;&lt;/strong&gt; source the operator fetches — so a
snippet imports a vendored library without bundling it.&lt;/p&gt;
&lt;p&gt;Deploy them with the &lt;a href="https://jaas.projects.metio.wtf/installation/helm-values/#joi-library-chart"&gt;joi Helm chart&lt;/a&gt;
,
which renders a &lt;code&gt;JsonnetLibrary&lt;/code&gt; + &lt;code&gt;OCIRepository&lt;/code&gt; pair for each enabled library.
A snippet then imports a library by its alias, choosing the version in the import
path:&lt;/p&gt;</description></item><item><title>Jsonnet libraries</title><link>https://jaas.projects.metio.wtf/usage/jsonnet-libraries/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/jsonnet-libraries/</guid><description>&lt;p&gt;Snippets import reusable Jsonnet from two places: namespaced &lt;code&gt;JsonnetLibrary&lt;/code&gt;
custom resources and OCI-mounted shared libraries the operator carries on disk.
Both feed the same import-alias namespace, so a snippet&amp;rsquo;s &lt;code&gt;import&lt;/code&gt; statements
look identical regardless of where the library comes from.&lt;/p&gt;
&lt;h2 id="the-jsonnetlibrary-crd"&gt;The JsonnetLibrary CRD&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;JsonnetLibrary&lt;/code&gt; is a namespaced bundle of &lt;code&gt;.libsonnet&lt;/code&gt; files. Like a snippet,
it declares exactly one source — inline &lt;code&gt;spec.files&lt;/code&gt; or a &lt;code&gt;spec.sourceRef&lt;/code&gt; to a
Flux source (&lt;code&gt;GitRepository&lt;/code&gt;, &lt;code&gt;OCIRepository&lt;/code&gt;, &lt;code&gt;Bucket&lt;/code&gt;, &lt;code&gt;ExternalArtifact&lt;/code&gt;).
The library carries no registration name of its own; the import alias is chosen
on the snippet side.&lt;/p&gt;</description></item><item><title>JsonnetLibrary</title><link>https://jaas.projects.metio.wtf/api/jsonnetlibrary/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/api/jsonnetlibrary/</guid><description>&lt;p&gt;&lt;code&gt;JsonnetLibrary&lt;/code&gt; (&lt;code&gt;jlib&lt;/code&gt;) is a namespaced bundle of &lt;code&gt;.libsonnet&lt;/code&gt; files that
&lt;code&gt;JsonnetSnippet&lt;/code&gt; CRs in the same namespace can import. The library carries no
artifact of its own and has no controller reconciling it today — it exists purely
as a supply-side source for snippets. The import alias is set on the snippet side
via &lt;code&gt;LibraryRef.importPath&lt;/code&gt; (defaulting to the library&amp;rsquo;s &lt;code&gt;metadata.name&lt;/code&gt;); the
library itself carries no registration name. Task-oriented guidance lives in
&lt;a href="https://jaas.projects.metio.wtf/usage/jsonnet-libraries/"&gt;Jsonnet libraries&lt;/a&gt;
.&lt;/p&gt;</description></item><item><title>JsonnetSnippet</title><link>https://jaas.projects.metio.wtf/api/jsonnetsnippet/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/api/jsonnetsnippet/</guid><description>&lt;p&gt;&lt;code&gt;JsonnetSnippet&lt;/code&gt; (&lt;code&gt;jsnip&lt;/code&gt;) is the published unit of Jsonnet evaluation. The JaaS
operator watches these namespaced CRs, evaluates the Jsonnet they describe, and
upserts a Flux &lt;code&gt;ExternalArtifact&lt;/code&gt; whose &lt;code&gt;status.artifact.url&lt;/code&gt; points at the
rendered result. Task-oriented guidance lives in
&lt;a href="https://jaas.projects.metio.wtf/usage/operator-mode/"&gt;Operator mode&lt;/a&gt;
 and &lt;a href="https://jaas.projects.metio.wtf/usage/snippet-sources/"&gt;Snippet sources&lt;/a&gt;
.&lt;/p&gt;
&lt;h2 id="example"&gt;Example&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;jaas.metio.wtf/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;JsonnetSnippet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hello-world&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;default&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serviceAccountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hello-world-tenant&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entryFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;main.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;rendered&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;10m&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;suspend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;main.jsonnet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; local lib = import &amp;#39;mylib/main.libsonnet&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; lib.dashboard(std.extVar(&amp;#39;env&amp;#39;), std.extVar(&amp;#39;cluster&amp;#39;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;libraries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;JsonnetLibrary&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mylib&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;importPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mylib&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;externalVariables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;production&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;eu-west-1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tlas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;My Dashboard&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Exactly one of &lt;code&gt;spec.files&lt;/code&gt; or &lt;code&gt;spec.sourceRef&lt;/code&gt; must be set. Admission rejects
CRs that set neither or both.&lt;/p&gt;</description></item><item><title>Kubernetes</title><link>https://jaas.projects.metio.wtf/installation/kubernetes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/installation/kubernetes/</guid><description>&lt;p&gt;JaaS ships as a container image at &lt;code&gt;ghcr.io/metio/jaas:latest&lt;/code&gt; and as a Helm
chart at &lt;code&gt;oci://ghcr.io/metio/helm-charts/jaas&lt;/code&gt;. Pre-built binaries for Linux,
macOS, and Windows are attached to each GitHub release for operators who prefer
to run the binary directly.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://kubernetes.io/"&gt;Kubernetes&lt;/a&gt;
 cluster, &lt;strong&gt;v1.28 or later&lt;/strong&gt;, with
&lt;code&gt;kubectl&lt;/code&gt; configured against it.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://helm.sh/"&gt;Helm&lt;/a&gt;
 &lt;strong&gt;v3.14 or later&lt;/strong&gt; — OCI chart support is required to
pull the chart from &lt;code&gt;ghcr.io&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Flux CR-based mode (below) additionally needs:&lt;/p&gt;</description></item><item><title>LibraryNotFound</title><link>https://jaas.projects.metio.wtf/runbooks/librarynotfound/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/librarynotfound/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=LibraryNotFound&lt;/code&gt;. The Message names the missing library.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;spec.libraries[*]&lt;/code&gt; entry references a &lt;code&gt;JsonnetLibrary&lt;/code&gt; CR that the operator cannot Get. Common reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the library CR doesn&amp;rsquo;t exist (typo, wrong namespace, not yet applied)&lt;/li&gt;
&lt;li&gt;the tenant ServiceAccount doesn&amp;rsquo;t have &lt;code&gt;get&lt;/code&gt; on the library kind in the library&amp;rsquo;s namespace&lt;/li&gt;
&lt;li&gt;the library is in a different namespace and &lt;code&gt;--no-cross-namespace-refs=true&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Confirm the library exists.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl --namespace &amp;lt;ns&amp;gt; get jsonnetlibrary &amp;lt;name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Test the tenant&amp;#39;s RBAC.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl auth can-i get jsonnetlibrary &amp;lt;name&amp;gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --as&lt;span class="o"&gt;=&lt;/span&gt;system:serviceaccount:&amp;lt;ns&amp;gt;:&amp;lt;tenant-sa&amp;gt; -n &amp;lt;library-ns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If &lt;code&gt;can-i&lt;/code&gt; returns &lt;code&gt;no&lt;/code&gt;, RBAC is the gap.&lt;/p&gt;</description></item><item><title>Local rendering</title><link>https://jaas.projects.metio.wtf/tutorials/local-rendering/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/tutorials/local-rendering/</guid><description>&lt;p&gt;JaaS runs as a cluster-free Jsonnet renderer: point it at a directory of
snippets and a directory of libraries, then &lt;code&gt;GET&lt;/code&gt; a snippet name to receive the
evaluated JSON. No Kubernetes, no operator mode, no Flux. The evaluation core is
the same one the operator uses, so a snippet that renders correctly here renders
identically in-cluster.&lt;/p&gt;
&lt;p&gt;This tutorial runs against this repository&amp;rsquo;s &lt;code&gt;examples/&lt;/code&gt; layout, so clone the
repo first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/metio/jaas
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; jaas
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="step-1--get-the-binary-or-container-image"&gt;Step 1 — Get the binary or container image&lt;/h2&gt;
&lt;p&gt;Pre-built binaries are attached to each
&lt;a href="https://github.com/metio/jaas/releases"&gt;GitHub release&lt;/a&gt;
. Download the archive
for your platform, unpack it, and the &lt;code&gt;jaas&lt;/code&gt; binary is inside.&lt;/p&gt;</description></item><item><title>Logging</title><link>https://jaas.projects.metio.wtf/usage/logging/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/logging/</guid><description>&lt;p&gt;JaaS logs through Go&amp;rsquo;s &lt;code&gt;log/slog&lt;/code&gt;. Every request, reconcile, and lifecycle event
is a structured record you can filter and parse rather than scrape with a regex.
In operator mode, controller-runtime&amp;rsquo;s own output — leader election, cache sync,
manager startup — flows through the &lt;strong&gt;same&lt;/strong&gt; slog handler via the logr bridge
(&lt;code&gt;ctrl.SetLogger(logr.FromSlogHandler(...))&lt;/code&gt;), so the manager&amp;rsquo;s logs share the
configured level and format instead of emitting controller-runtime&amp;rsquo;s default zap
output.&lt;/p&gt;
&lt;h2 id="the-binary"&gt;The binary&lt;/h2&gt;
&lt;p&gt;Two flags control logging. They apply in every mode JaaS runs in:&lt;/p&gt;</description></item><item><title>Metrics</title><link>https://jaas.projects.metio.wtf/usage/metrics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/metrics/</guid><description>&lt;p&gt;The JaaS operator exposes a Prometheus metrics endpoint covering controller-runtime&amp;rsquo;s
standard families plus a custom &lt;code&gt;jaas_*&lt;/code&gt; family the reconciler registers. Scrape
it for dashboards and feed it into the shipped &lt;a href="https://jaas.projects.metio.wtf/usage/alerting/"&gt;alerts&lt;/a&gt;
.&lt;/p&gt;
&lt;h2 id="the-binary"&gt;The binary&lt;/h2&gt;
&lt;p&gt;controller-runtime&amp;rsquo;s Prometheus endpoint binds &lt;code&gt;--metrics-bind-address&lt;/code&gt; (default
&lt;code&gt;:8083&lt;/code&gt;), serving the standard text exposition format at &lt;code&gt;/metrics&lt;/code&gt;. Setting it
to &lt;code&gt;0&lt;/code&gt; disables the endpoint. The default deliberately avoids controller-runtime&amp;rsquo;s
built-in &lt;code&gt;:8080&lt;/code&gt;, which would collide with the Jsonnet HTTP port.&lt;/p&gt;
&lt;p&gt;The full flag list with defaults is on the
&lt;a href="https://jaas.projects.metio.wtf/installation/configuration/"&gt;configuration page&lt;/a&gt;
.&lt;/p&gt;</description></item><item><title>Network policy</title><link>https://jaas.projects.metio.wtf/usage/network-policy/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/network-policy/</guid><description>&lt;p&gt;The Helm chart ships an opt-in &lt;code&gt;NetworkPolicy&lt;/code&gt; for the JaaS pod. It is off by
default and renders only when &lt;code&gt;networkPolicy.enabled&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;. Two independent
layers are on offer: pod-scoped allowlists that lock down only JaaS&amp;rsquo;s own pods
(the safe default), and an additional namespace-wide default-deny for a zero-trust
namespace. The ingress and egress tables below describe exactly what traffic JaaS
depends on — in both renderer mode and &lt;a href="https://jaas.projects.metio.wtf/usage/operator-mode/"&gt;operator mode&lt;/a&gt;
 — so
everything else can be denied.&lt;/p&gt;</description></item><item><title>Observability</title><link>https://jaas.projects.metio.wtf/usage/observability/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/observability/</guid><description>&lt;p&gt;JaaS gives you four ways to see what it is doing in a cluster. Structured logs
tell you what happened on a single request or reconcile; traces follow one
operation across its spans; metrics aggregate behaviour into time series for
dashboards and alerts; and alerts plus Kubernetes Events turn a sustained
problem into a page or a notification.&lt;/p&gt;
&lt;p&gt;Each pillar has its own page covering both the binary&amp;rsquo;s flags and the Helm chart
keys that drive them:&lt;/p&gt;</description></item><item><title>Operations</title><link>https://jaas.projects.metio.wtf/installation/operations/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/installation/operations/</guid><description>&lt;p&gt;Day-two operations for a running JaaS install. Initial install and hardening
decisions are in &lt;a href="https://jaas.projects.metio.wtf/installation/kubernetes/"&gt;Kubernetes&lt;/a&gt;
 and
&lt;a href="https://jaas.projects.metio.wtf/installation/production/"&gt;Production&lt;/a&gt;
.&lt;/p&gt;
&lt;h2 id="graceful-shutdown-and-drain"&gt;Graceful shutdown and drain&lt;/h2&gt;
&lt;p&gt;When Kubernetes sends &lt;code&gt;SIGTERM&lt;/code&gt;, JaaS executes a two-phase shutdown to avoid
dropping in-flight requests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The readiness probe flips to &lt;code&gt;false&lt;/code&gt; (&lt;code&gt;503&lt;/code&gt; on &lt;code&gt;/ready&lt;/code&gt;). Kubernetes
endpoint controllers begin deregistering the pod from Services.&lt;/li&gt;
&lt;li&gt;JaaS waits for &lt;code&gt;--shutdown-delay&lt;/code&gt; (default &lt;code&gt;5s&lt;/code&gt;) before closing its
listeners. This window lets the endpoint propagation complete so no new
traffic arrives after the server closes.&lt;/li&gt;
&lt;li&gt;After the delay, the servers shut down gracefully with a 30-second
&lt;code&gt;context.WithTimeout&lt;/code&gt;. The operator goroutine is also cancelled and awaited
within the same 30-second window.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The distroless runtime image has no &lt;code&gt;sleep&lt;/code&gt; binary, so the drain delay is
implemented in the binary rather than via a &lt;code&gt;preStop&lt;/code&gt; hook. A second
&lt;code&gt;SIGTERM&lt;/code&gt; (or &lt;code&gt;SIGINT&lt;/code&gt;) during the drain cuts the wait short.&lt;/p&gt;</description></item><item><title>Operator mode</title><link>https://jaas.projects.metio.wtf/usage/operator-mode/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/operator-mode/</guid><description>&lt;p&gt;JaaS runs as a Kubernetes operator alongside its HTTP renderer. In this mode it
watches custom resources, evaluates the Jsonnet they describe, and publishes the
result as a Flux &lt;a href="https://fluxcd.io/"&gt;&lt;code&gt;ExternalArtifact&lt;/code&gt;&lt;/a&gt;
 that downstream
controllers consume. The HTTP renderer keeps running; the operator is an
additional set of goroutines, not a separate binary.&lt;/p&gt;
&lt;h2 id="enabling-the-operator"&gt;Enabling the operator&lt;/h2&gt;
&lt;p&gt;Set &lt;code&gt;--enable-flux-integration&lt;/code&gt; on the binary:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;jaas --enable-flux-integration &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --storage-path&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/jaas/artifacts &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --storage-base-url&lt;span class="o"&gt;=&lt;/span&gt;http://jaas-storage.jaas.svc:8082
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--storage-path&lt;/code&gt; and &lt;code&gt;--storage-base-url&lt;/code&gt; are required in operator mode — they
tell the operator where to write artifact tarballs and the public URL prefix
downstream consumers fetch them from.&lt;/p&gt;</description></item><item><title>Operator pod not ready</title><link>https://jaas.projects.metio.wtf/runbooks/operator-pod-down/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/operator-pod-down/</guid><description>&lt;p&gt;Linked from the &lt;code&gt;JaaSOperatorPodDown&lt;/code&gt; alert. Fires when at least one jaas pod has been &lt;code&gt;Ready=False&lt;/code&gt; for the alert window (default 5m).&lt;/p&gt;
&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ALERTS{alertname=&amp;#34;JaaSOperatorPodDown&amp;#34;, namespace=&amp;#34;&amp;lt;jaas-ns&amp;gt;&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;New JsonnetSnippets stay &lt;code&gt;Ready=Unknown&lt;/code&gt; indefinitely (no controller reconciling them).&lt;/li&gt;
&lt;li&gt;Existing snippets keep serving stale &lt;code&gt;ExternalArtifact&lt;/code&gt; content (the storage HTTP server may still respond; reconciliation is the part that stopped).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;One of the chart&amp;rsquo;s two probes is failing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Liveness&lt;/strong&gt; (&lt;code&gt;/live&lt;/code&gt;) is unconditional 200 — a failure here means the pod&amp;rsquo;s HTTP server itself isn&amp;rsquo;t responding (deadlock, OOM, panic).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Readiness&lt;/strong&gt; (&lt;code&gt;/ready&lt;/code&gt;) consults &lt;code&gt;HealthState&lt;/code&gt;, which goes &lt;code&gt;false&lt;/code&gt; during &lt;code&gt;drainBeforeShutdown&lt;/code&gt; or before the listeners bind.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Startup&lt;/strong&gt; (&lt;code&gt;/start&lt;/code&gt;) returns 503 until &lt;code&gt;MarkStarted()&lt;/code&gt; is called — bind failures (port already in use, permission denied) keep the pod stuck here forever.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Frequent causes:&lt;/p&gt;</description></item><item><title>Pending</title><link>https://jaas.projects.metio.wtf/runbooks/pending/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/pending/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;kubectl get jsonnetsnippet&lt;/code&gt; shows &lt;code&gt;READY=Unknown&lt;/code&gt; (or &lt;code&gt;False&lt;/code&gt; with &lt;code&gt;REASON=Pending&lt;/code&gt;) immediately after the snippet was created or its spec was updated.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The operator has observed the CR but hasn&amp;rsquo;t completed its first reconcile pass yet. Transient by design.&lt;/p&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe jsonnetsnippet &amp;lt;name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the timestamp on the &lt;code&gt;Pending&lt;/code&gt; condition is older than ~30 seconds, the operator is either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;not running (&lt;code&gt;kubectl --namespace &amp;lt;jaas-namespace&amp;gt; get pods&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;backed up on its work queue (check &lt;code&gt;kubectl logs deploy/jaas&lt;/code&gt; and the &lt;code&gt;workqueue_depth&lt;/code&gt; metric)&lt;/li&gt;
&lt;li&gt;not the leader (multi-replica install, &lt;code&gt;kubectl --namespace &amp;lt;jaas-namespace&amp;gt; get lease&lt;/code&gt; shows the holder)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="remediation"&gt;Remediation&lt;/h2&gt;
&lt;p&gt;If transient, wait. If persistent:&lt;/p&gt;</description></item><item><title>Production</title><link>https://jaas.projects.metio.wtf/installation/production/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/installation/production/</guid><description>&lt;p&gt;The chart&amp;rsquo;s defaults are safe for an initial install but not optimised for
sustained production workloads. Work through these decisions before exposing JaaS
to real traffic. Each links to the detailed guide.&lt;/p&gt;
&lt;h2 id="1-pick-a-storage-backend"&gt;1. Pick a storage backend&lt;/h2&gt;
&lt;p&gt;The single largest decision. Artifacts must survive pod restarts and, for HA,
be readable by every replica simultaneously.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Backend&lt;/th&gt;
					&lt;th&gt;Persistence&lt;/th&gt;
					&lt;th&gt;Multi-replica HA&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;local&lt;/code&gt; + emptyDir (chart default)&lt;/td&gt;
					&lt;td&gt;No&lt;/td&gt;
					&lt;td&gt;No&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;local&lt;/code&gt; + RWO PVC&lt;/td&gt;
					&lt;td&gt;Yes&lt;/td&gt;
					&lt;td&gt;No — single replica only&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;local&lt;/code&gt; + RWX PVC&lt;/td&gt;
					&lt;td&gt;Yes&lt;/td&gt;
					&lt;td&gt;Yes — requires RWX storage class&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;s3&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Yes&lt;/td&gt;
					&lt;td&gt;Yes — leader writes, all replicas read&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For cloud installs, &lt;code&gt;s3&lt;/code&gt; (AWS S3, MinIO, Ceph RGW, GCS S3-compat API) is the
recommended backend. Pair it with leader election (on by default) so only the
lease-holder writes. For on-prem, a PVC with the access mode your storage class
supports is the practical path.&lt;/p&gt;</description></item><item><title>Quickstart</title><link>https://jaas.projects.metio.wtf/tutorials/quickstart/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/tutorials/quickstart/</guid><description>&lt;p&gt;This tutorial takes you from an empty cluster to one published Flux
&lt;code&gt;ExternalArtifact&lt;/code&gt; carrying rendered JSON. The path is operator mode with no
optional knobs — no webhook, no S3, no Flux source CRs — and a single
&lt;code&gt;JsonnetSnippet&lt;/code&gt; whose source is inline &lt;code&gt;spec.files&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A Kubernetes cluster. &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;minikube&lt;/code&gt;, or a managed cluster all work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;kubectl&lt;/code&gt; configured to talk to it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;helm&lt;/code&gt; 3.x.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Flux installed, at &lt;strong&gt;v2.7.0 or newer&lt;/strong&gt;. A &lt;code&gt;JsonnetSnippet&lt;/code&gt; publishes its
result as a Flux &lt;code&gt;ExternalArtifact&lt;/code&gt;, and the &lt;code&gt;ExternalArtifact&lt;/code&gt; CRD lands in
source-controller v1.7.0 (Flux v2.7.0) — earlier bundles have no such CRD and
the publish path fails. Install all of Flux:&lt;/p&gt;</description></item><item><title>RBACDenied</title><link>https://jaas.projects.metio.wtf/runbooks/rbacdenied/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/rbacdenied/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe jsonnetsnippet &amp;lt;name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Status:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Conditions:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Reason: RBACDenied
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Status: False
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Type: Ready
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Message: RBAC denied reading the source CR — grant the tenant ServiceAccount get on the source kind ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or for a missing CRD:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Message: source CR&amp;#39;s kind is not registered with the apiserver — install the corresponding CRD ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The reconciler logs at warn level and stops engaging backoff for this snippet. The next reconcile happens only when the snippet&amp;rsquo;s spec changes, a referenced library / source CR&amp;rsquo;s status flips, or &lt;code&gt;spec.interval&lt;/code&gt; ticks — so the workqueue isn&amp;rsquo;t burning cycles on a permanently-failing call.&lt;/p&gt;</description></item><item><title>Rendering endpoint</title><link>https://jaas.projects.metio.wtf/usage/rendering-endpoint/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/rendering-endpoint/</guid><description>&lt;p&gt;Send a &lt;code&gt;GET&lt;/code&gt; to the rendering endpoint with a snippet name and JaaS returns the
evaluated Jsonnet as JSON:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl http://127.0.0.1:8080/jsonnet/example1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Jsonnet server binds &lt;code&gt;127.0.0.1:8080&lt;/code&gt; by default (&lt;code&gt;--listen-address&lt;/code&gt;,
&lt;code&gt;--port&lt;/code&gt;). The URL shape is &lt;code&gt;GET /&amp;lt;jsonnet-endpoint-path&amp;gt;/{snippet...}&lt;/code&gt;, where
&lt;code&gt;{snippet...}&lt;/code&gt; is a trailing path segment that may contain slashes. A successful
response carries &lt;code&gt;Content-Type: application/json&lt;/code&gt; and the rendered document.&lt;/p&gt;
&lt;h2 id="the-endpoint-path"&gt;The endpoint path&lt;/h2&gt;
&lt;p&gt;The leading path segment defaults to &lt;code&gt;jsonnet&lt;/code&gt; and is set with
&lt;code&gt;--jsonnet-endpoint-path&lt;/code&gt;. Running with &lt;code&gt;--jsonnet-endpoint-path render&lt;/code&gt; moves the
endpoint to &lt;code&gt;GET /render/{snippet...}&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Self-signed webhook cert renewal failing</title><link>https://jaas.projects.metio.wtf/runbooks/webhook-cert-renewal/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/webhook-cert-renewal/</guid><description>&lt;p&gt;Fires when &lt;code&gt;jaas_webhook_cert_renewal_failures_total&lt;/code&gt; has increased above the configured per-hour threshold. The &lt;code&gt;Renewer&lt;/code&gt; background goroutine rotates the self-signed TLS material every &lt;code&gt;Validity / 3&lt;/code&gt; (typically every few months for a year-long cert). When it can&amp;rsquo;t, the existing cert keeps working until its natural expiry — at which point the apiserver stops trusting the chain and &lt;strong&gt;every JsonnetSnippet admission fails cluster-wide with &lt;code&gt;x509&lt;/code&gt; errors&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JaaSWebhookCertRenewalFailing&lt;/code&gt; alert is firing (severity: critical).&lt;/li&gt;
&lt;li&gt;Operator pod logs carry repeated &lt;code&gt;Self-signed webhook cert renewal failed&lt;/code&gt; warnings at the &lt;code&gt;Renewer.Interval&lt;/code&gt; cadence.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl describe validatingwebhookconfiguration &amp;lt;name&amp;gt;&lt;/code&gt; shows a &lt;code&gt;caBundle&lt;/code&gt; that hasn&amp;rsquo;t rotated since the failures started.&lt;/li&gt;
&lt;li&gt;The pod stays &lt;code&gt;Ready=True&lt;/code&gt; — the renewer&amp;rsquo;s failures don&amp;rsquo;t gate the readiness probe.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;p&gt;The most common causes, in order of frequency:&lt;/p&gt;</description></item><item><title>Service mesh</title><link>https://jaas.projects.metio.wtf/usage/service-mesh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/service-mesh/</guid><description>&lt;p&gt;The Helm chart ships an opt-in service-mesh authorization layer for the JaaS pod.
It is off by default and renders only when &lt;code&gt;serviceMesh.enabled&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;. Where
the &lt;a href="https://jaas.projects.metio.wtf/usage/network-policy/"&gt;network policy&lt;/a&gt;
 operates at L3/L4 — which pods and IP
ranges may reach which ports — the service mesh operates at L7 with &lt;strong&gt;identity-based
authorization&lt;/strong&gt; and &lt;strong&gt;mTLS&lt;/strong&gt;: it asks &lt;em&gt;which mesh identity&lt;/em&gt; is calling, proven by a
cryptographic workload certificate, not merely which IP the packet came from.&lt;/p&gt;</description></item><item><title>ServiceAccountMissing</title><link>https://jaas.projects.metio.wtf/runbooks/serviceaccountmissing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/serviceaccountmissing/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=ServiceAccountMissing&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The snippet omitted &lt;code&gt;spec.serviceAccountName&lt;/code&gt; AND the operator was started without &lt;code&gt;--default-service-account&lt;/code&gt;. The operator refuses to reconcile a snippet with no effective ServiceAccount because every reconcile mints a tenant token from that SA — without one, there&amp;rsquo;s nothing to impersonate.&lt;/p&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get jsonnetsnippet &amp;lt;name&amp;gt; --output &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.spec.serviceAccountName}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Empty? Either the snippet must set it, or the cluster operator must configure a default.&lt;/p&gt;
&lt;h2 id="remediation"&gt;Remediation&lt;/h2&gt;
&lt;p&gt;Pick one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Snippet-side (preferred for multi-tenant setups):&lt;/strong&gt; set &lt;code&gt;spec.serviceAccountName: &amp;lt;existing-sa&amp;gt;&lt;/code&gt; on every snippet. Each tenant uses its own SA → least-privilege impersonation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cluster-side (single-tenant clusters):&lt;/strong&gt; start the operator with &lt;code&gt;--default-service-account=&amp;lt;sa-name&amp;gt;&lt;/code&gt;. Every snippet without an explicit SA impersonates this one. The default SA must exist in &lt;strong&gt;every snippet&amp;rsquo;s namespace&lt;/strong&gt; — the operator looks it up per-reconcile.&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Snippet sources</title><link>https://jaas.projects.metio.wtf/usage/snippet-sources/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/snippet-sources/</guid><description>&lt;p&gt;A &lt;code&gt;JsonnetSnippet&lt;/code&gt; declares exactly one source for its Jsonnet bytes: either
inline &lt;code&gt;spec.files&lt;/code&gt; or a &lt;code&gt;spec.sourceRef&lt;/code&gt; pointing at a Flux source. Admission
rejects a snippet that sets both or neither. The operator resolves the source
into an in-memory file tree, evaluates &lt;code&gt;spec.entryFile&lt;/code&gt; within it, and publishes
the result.&lt;/p&gt;
&lt;h2 id="inline-files"&gt;Inline files&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;spec.files&lt;/code&gt; is a map of filename to Jsonnet source. The operator evaluates the
entry file (&lt;code&gt;spec.entryFile&lt;/code&gt;, default &lt;code&gt;main.jsonnet&lt;/code&gt;) against the rest of the
map. This is the simplest source — the snippet is self-contained, with no
external dependency to fetch:&lt;/p&gt;</description></item><item><title>Snippets and libraries</title><link>https://jaas.projects.metio.wtf/usage/snippets-and-libraries/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/snippets-and-libraries/</guid><description>&lt;p&gt;In renderer mode you declare snippets and libraries on disk through command-line
flags. A snippet becomes reachable at the
&lt;a href="https://jaas.projects.metio.wtf/usage/rendering-endpoint/"&gt;rendering endpoint&lt;/a&gt;
; a library is importable by any
snippet.&lt;/p&gt;
&lt;h2 id="directory-snippets"&gt;Directory snippets&lt;/h2&gt;
&lt;p&gt;Point &lt;code&gt;--snippet-directory&lt;/code&gt; at a directory whose subdirectories each hold a
&lt;code&gt;main.jsonnet&lt;/code&gt;. Each subdirectory name becomes a snippet name:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./jaas --snippet-directory examples/snippets/dashboards
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl http://127.0.0.1:8080/jsonnet/example1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Given this layout, &lt;code&gt;example1&lt;/code&gt; resolves to
&lt;code&gt;examples/snippets/dashboards/example1/main.jsonnet&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;examples/snippets/dashboards
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── example1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── main.jsonnet
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── tla-example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── main.jsonnet
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── multi-tla
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── main.jsonnet
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="file-snippets"&gt;File snippets&lt;/h2&gt;
&lt;p&gt;Point &lt;code&gt;--snippet&lt;/code&gt; at an individual Jsonnet file. The file path becomes the
snippet name:&lt;/p&gt;</description></item><item><title>SourceFetchFailed</title><link>https://jaas.projects.metio.wtf/runbooks/sourcefetchfailed/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/sourcefetchfailed/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=SourceFetchFailed&lt;/code&gt;. The Message describes what went wrong (HTTP error, digest mismatch, tarball too large, etc.).&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The Fetcher resolved the source CR and started downloading the artifact, but the download itself failed. Three subcategories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP failure&lt;/strong&gt; — connection refused, 5xx from the source-controller endpoint, TLS handshake error&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Digest mismatch&lt;/strong&gt; — the bytes don&amp;rsquo;t hash to &lt;code&gt;status.artifact.digest&lt;/code&gt;. Possible truncation or in-flight tampering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tarball oversized&lt;/strong&gt; — extracted bytes exceed &lt;code&gt;MaxArchiveBytes&lt;/code&gt; (default 64 MiB)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;p&gt;Check the source CR&amp;rsquo;s &lt;code&gt;status.artifact.url&lt;/code&gt; is reachable from the operator pod:&lt;/p&gt;</description></item><item><title>SourceNotReady</title><link>https://jaas.projects.metio.wtf/runbooks/sourcenotready/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/sourcenotready/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=SourceNotReady&lt;/code&gt;. The Message names the source CR (&lt;code&gt;GitRepository/foo&lt;/code&gt;, &lt;code&gt;ExternalArtifact/bar&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The Flux source CR the snippet references exists but its own &lt;code&gt;status.conditions[Ready]&lt;/code&gt; is not yet True (or &lt;code&gt;status.artifact&lt;/code&gt; is empty). The operator refuses to fetch from a source it can&amp;rsquo;t trust as ready.&lt;/p&gt;
&lt;p&gt;For chained snippets specifically: the upstream snippet may have failed reconciliation, so its ExternalArtifact is stale or unpopulated.&lt;/p&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe &amp;lt;kind&amp;gt; &amp;lt;source-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Look for the Ready condition and any error messages.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For Flux sources, also check the source-controller logs:&lt;/p&gt;</description></item><item><title>SourceRefNotYetSupported</title><link>https://jaas.projects.metio.wtf/runbooks/sourcerefnotyetsupported/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/sourcerefnotyetsupported/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=SourceRefNotYetSupported&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The snippet sets &lt;code&gt;spec.sourceRef&lt;/code&gt; but the operator was built without a Fetcher wired in. This is a mis-deployment in practice — production binary always wires &lt;code&gt;sources.New()&lt;/code&gt;. Seeing this in a real cluster means you&amp;rsquo;re running:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a test/dev binary&lt;/li&gt;
&lt;li&gt;a custom build where &lt;code&gt;defaultBuilder&lt;/code&gt; was modified&lt;/li&gt;
&lt;li&gt;a future code path that hasn&amp;rsquo;t enabled sourceRef yet&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl logs deploy/jaas &lt;span class="p"&gt;|&lt;/span&gt; grep -i &lt;span class="s2"&gt;&amp;#34;fetcher&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the operator logs no Fetcher initialization, the binary is incomplete.&lt;/p&gt;</description></item><item><title>Storage and high availability</title><link>https://jaas.projects.metio.wtf/usage/storage-and-ha/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/storage-and-ha/</guid><description>&lt;p&gt;In &lt;a href="https://jaas.projects.metio.wtf/usage/operator-mode/"&gt;operator mode&lt;/a&gt;
 JaaS renders each &lt;code&gt;JsonnetSnippet&lt;/code&gt; into
a tar.gz artifact, stores it, and publishes an &lt;code&gt;ExternalArtifact&lt;/code&gt; CR that points a
Flux consumer at the tarball over HTTP. JaaS publishes artifacts through one of two
storage backends — local filesystem or S3-compatible object storage — with optional
leader election for multi-replica high availability and configurable revision
retention.&lt;/p&gt;
&lt;h2 id="serving-the-tarballs"&gt;Serving the tarballs&lt;/h2&gt;
&lt;p&gt;Regardless of backend, the operator runs an HTTP server that Flux consumers fetch
artifacts from. Three flags govern it, and &lt;code&gt;--storage-base-url&lt;/code&gt; and
&lt;code&gt;--storage-path&lt;/code&gt; are required whenever &lt;code&gt;--enable-flux-integration&lt;/code&gt; is set:&lt;/p&gt;</description></item><item><title>Storage backend recovery</title><link>https://jaas.projects.metio.wtf/runbooks/storage-recovery/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/storage-recovery/</guid><description>&lt;p&gt;Not tied to a single &lt;code&gt;Reason&lt;/code&gt; — this page covers what to do when the artifact store itself is degraded (PVC lost, S3 endpoint unavailable, the storage HTTP server is down). Downstream Flux consumers (kustomize-controller, helm-controller, grafana-operator) dereference &lt;code&gt;ExternalArtifact.status.artifact.url&lt;/code&gt; to fetch tarballs; when that URL stops returning bytes, dependent resources stall.&lt;/p&gt;
&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;One or more of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downstream Flux consumers report &lt;code&gt;404 Not Found&lt;/code&gt; or &lt;code&gt;connection refused&lt;/code&gt; against the JaaS storage URL.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl get externalartifact --all-namespaces&lt;/code&gt; shows resources whose URL is unreachable from the consumer pods.&lt;/li&gt;
&lt;li&gt;The operator pod is healthy (Ready=True on snippets), but the storage Service is unresponsive.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm upgrade&lt;/code&gt; of the chart from &lt;code&gt;persistence.enabled: false&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; — or vice versa — caused a gap.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="triage-which-backend-are-you-running"&gt;Triage: which backend are you running?&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl --namespace &amp;lt;jaas-ns&amp;gt; get deploy jaas &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --output &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.spec.template.spec.containers[0].args}&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; tr &lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -E &lt;span class="s1"&gt;&amp;#39;storage-backend|storage-path|s3-endpoint&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--storage-backend=local&lt;/code&gt; → filesystem behind &lt;code&gt;--storage-path&lt;/code&gt;. Either an emptyDir (chart default) or a PVC.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--storage-backend=s3&lt;/code&gt; → an external S3-compatible bucket; the storage HTTP server in-pod is a thin streaming proxy over &lt;code&gt;minio-go&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="filesystem-backend"&gt;Filesystem backend&lt;/h2&gt;
&lt;h3 id="pvc-lost-or-replaced"&gt;PVC lost or replaced&lt;/h3&gt;
&lt;p&gt;Symptom: every &lt;code&gt;ExternalArtifact&lt;/code&gt; URL returns 404 even though the snippet&amp;rsquo;s Ready=True. The Publisher writes idempotently on every reconcile, so making the operator re-render every snippet is the fix:&lt;/p&gt;</description></item><item><title>Suspended</title><link>https://jaas.projects.metio.wtf/runbooks/suspended/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/suspended/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;READY=False&lt;/code&gt;, &lt;code&gt;REASON=Suspended&lt;/code&gt;. The snippet&amp;rsquo;s &lt;code&gt;spec.suspend&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;An operator (or automation) paused reconciliation for this snippet, typically to investigate a downstream issue without the artifact being rewritten underneath them. The previously-published &lt;code&gt;ExternalArtifact&lt;/code&gt; and the on-disk tarball are left intact — downstream Flux consumers continue serving the last successful render.&lt;/p&gt;
&lt;p&gt;This is a normal, intentional state. It is not a failure.&lt;/p&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get jsonnetsnippet &amp;lt;name&amp;gt; --output &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.spec.suspend}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the value is &lt;code&gt;true&lt;/code&gt;, the suspension is set on the spec. Check &lt;code&gt;kubectl describe&lt;/code&gt; for the last condition transition timestamp to see when it happened.&lt;/p&gt;</description></item><item><title>Synced</title><link>https://jaas.projects.metio.wtf/runbooks/synced/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/synced/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;kubectl get jsonnetsnippet&lt;/code&gt; shows &lt;code&gt;READY=True&lt;/code&gt; with &lt;code&gt;REASON=Synced&lt;/code&gt;. This is the healthy state — no action required.&lt;/p&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The most recent reconcile pass completed end-to-end: source resolved, libraries resolved, eval succeeded, tarball published, ExternalArtifact upserted.&lt;/p&gt;
&lt;h2 id="diagnosis"&gt;Diagnosis&lt;/h2&gt;
&lt;p&gt;To inspect the published artifact:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get externalartifact &amp;lt;snippet-name&amp;gt; --output yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;status.artifact.url&lt;/code&gt; points at the operator&amp;rsquo;s storage HTTP server. Curl it from a pod in the cluster to confirm the bytes match:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl run --rm --stdin --tty --restart&lt;span class="o"&gt;=&lt;/span&gt;Never tmp --image&lt;span class="o"&gt;=&lt;/span&gt;docker.io/library/curlimages/curl -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -sL &amp;lt;status.artifact.url&amp;gt; &lt;span class="p"&gt;|&lt;/span&gt; tar tzv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="remediation"&gt;Remediation&lt;/h2&gt;
&lt;p&gt;None — this is the healthy state.&lt;/p&gt;</description></item><item><title>Tenancy and RBAC</title><link>https://jaas.projects.metio.wtf/usage/tenancy-and-rbac/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/tenancy-and-rbac/</guid><description>&lt;p&gt;In &lt;a href="https://jaas.projects.metio.wtf/usage/operator-mode/"&gt;operator mode&lt;/a&gt;
 the JaaS operator never acts with its
own broad privileges when touching tenant resources. Every reconcile of a
&lt;code&gt;JsonnetSnippet&lt;/code&gt; runs against the RBAC of a tenant ServiceAccount, so a snippet
can only reach what its own ServiceAccount is allowed to reach.&lt;/p&gt;
&lt;h2 id="per-snippet-impersonation"&gt;Per-snippet impersonation&lt;/h2&gt;
&lt;p&gt;Each &lt;code&gt;JsonnetSnippet&lt;/code&gt; carries a &lt;code&gt;spec.serviceAccountName&lt;/code&gt;. On every reconcile the
operator mints a short-lived Bearer token for that ServiceAccount through the
Kubernetes TokenRequest API (&lt;code&gt;serviceaccounts/token: create&lt;/code&gt;) and performs all
tenant-side API calls — reading &lt;code&gt;JsonnetLibrary&lt;/code&gt; objects, fetching Flux source
artifacts, and writing the published &lt;code&gt;ExternalArtifact&lt;/code&gt; — as that ServiceAccount.
The operator does not use the &lt;code&gt;impersonate&lt;/code&gt; verb; it uses a real token, so the
apiserver evaluates the tenant&amp;rsquo;s own RBAC.&lt;/p&gt;</description></item><item><title>Tracing</title><link>https://jaas.projects.metio.wtf/usage/tracing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/usage/tracing/</guid><description>&lt;p&gt;The JaaS operator exports OpenTelemetry traces over OTLP gRPC. With an endpoint
configured, each reconcile and the work it fans out into — source fetch, library
resolution, evaluation, publish — becomes a span you can follow in a tracing
backend. When no endpoint is set, the OpenTelemetry SDK runs in no-op mode and
emits nothing, so tracing carries no cost until you opt in.&lt;/p&gt;
&lt;h2 id="the-binary"&gt;The binary&lt;/h2&gt;
&lt;p&gt;Three flags configure the exporter:&lt;/p&gt;</description></item><item><title>Watch-layer silent failure</title><link>https://jaas.projects.metio.wtf/runbooks/operator-watch-silent/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/operator-watch-silent/</guid><description>&lt;p&gt;Not tied to a per-snippet &lt;code&gt;Reason&lt;/code&gt;. This page covers the one RBAC-denial path the reconciler cannot surface itself: when the &lt;strong&gt;operator&amp;rsquo;s own&lt;/strong&gt; ClusterRole is missing a verb on a watched resource kind, controller-runtime&amp;rsquo;s informer fails to start its watch, logs warnings, and retries silently. The reconciler never sees the failure — and no snippet&amp;rsquo;s status condition will tell you about it.&lt;/p&gt;
&lt;p&gt;If a per-snippet runbook (&lt;code&gt;rbacdenied.md&lt;/code&gt;, &lt;code&gt;sourcefetchfailed.md&lt;/code&gt;, &lt;code&gt;sourcenotready.md&lt;/code&gt;) doesn&amp;rsquo;t match the symptoms, this is where to look next.&lt;/p&gt;</description></item><item><title>Workqueue saturation</title><link>https://jaas.projects.metio.wtf/runbooks/workqueue-saturation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jaas.projects.metio.wtf/runbooks/workqueue-saturation/</guid><description>&lt;p&gt;Linked from the &lt;code&gt;JaaSControllerWorkqueueDepthHigh&lt;/code&gt; alert. Fires when the reconciler&amp;rsquo;s workqueue holds more items than the configured threshold (default 50) for the alert window. Not tied to a &lt;code&gt;Reason&lt;/code&gt; constant — workqueue depth is a controller-runtime signal, not a per-snippet status.&lt;/p&gt;
&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ALERTS{alertname=&amp;#34;JaaSControllerWorkqueueDepthHigh&amp;#34;, controller=&amp;#34;jsonnetsnippet&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;New snippet writes settle slowly (status takes minutes to flip, not seconds).&lt;/li&gt;
&lt;li&gt;Existing snippets re-render later than &lt;code&gt;spec.interval&lt;/code&gt; would suggest.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl describe jsonnetsnippet&lt;/code&gt; shows a stale &lt;code&gt;ObservedGeneration&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cause"&gt;Cause&lt;/h2&gt;
&lt;p&gt;The operator is dequeuing reconciles slower than the API server enqueues them. Common causes, in observed-frequency order:&lt;/p&gt;</description></item></channel></rss>