The decision table lives in scripts/generate-flattening-safety-verdicts.mjs. Nothing here is automatic, and the reason is written into the first rule below.
These six rules are the chart-specific application of rule 10 of the doctrine, which states the general form: a claim must be openable, evidence is not a decision, and one scanner never settles everything.
The four lanes
- safe-to-flatten. Nothing the audited base renders is discharged at render time.
- flatten-with-routes. Flattening is safe once named companion artifacts travel with the bundle.
- unsafe-to-flatten. Something the base renders cannot be recovered by any companion the catalog can emit. The installer package is that chart's certified route.
- born-flattened. The source never rendered in the first place.
A lane is decided per base, not per chart. The same chart with auth.existingSecret set is a different question from the same chart without it, and variantScope records which values move the answer.
Rule one: the scan says what a chart contains, not what it calls
This is the rule that produces the most wrong drafts, so it comes first.
A packaged chart carries its whole dependency tree, including library charts it may never invoke. All six bitnami charts in the catalog package charts/common/templates/_secrets.tpl, whose lookup-or-generate credential helper is the classic unsafe-to-flatten finding. Five of them call it. nginx does not - it has no credential to manage at all, and its real hazard is genCA in templates/tls-secret.yaml. A first pass gave all six the same rationale, and five of them were right for the wrong reason.
Before citing a finding, check whether it is in the chart's own templates/ or only in a vendored subchart, and if only vendored, find the call site or drop the claim.
Rule two: read the defaults before calling anything gated
present-gated is the most attractive finding to write and the easiest to get backwards. It means the construct exists and the audited base does not render it.
The prometheus chart's four subcharts look optional and are enabled: true. A first pass recorded a variant note saying enabling them would widen the scan, when the scan had already covered them. Loki's minio and rollout-operator subcharts genuinely are off, and its enterprise provisioner is gated behind two values at once.
Open the chart's values.yaml and confirm the gate's default before writing present-gated.
Rule three: an empty shell is not a frozen secret
Two constructs look alarming and are often resolutions rather than debts.
An empty caBundle usually means a flattened bundle ships admission that nothing can satisfy, which is what decides ingress-nginx and kube-prometheus-stack. It means the opposite when the controller that fills it travels inside the same bundle. Vault's injector is named in AGENT_INJECT_TLS_AUTO and holds patch on mutatingwebhookconfigurations; cert-manager's cainjector, external-secrets' cert-controller and gatekeeper's controller are the same pattern. Establish which by reading the RBAC, not by seeing the empty string.
A Secret with no data is a placeholder a controller writes into, not a credential frozen into a public artifact. Gatekeeper ships exactly that and a live run recorded it being populated.
Rule four: decide the version the catalog supports
The supported version is usually older than the newest one available, and its findings can differ. The top-20 batch decided redis 25.5.3, not the 27.0.0 audited earlier, and cert-manager v1.20.2 rather than v1.21.0. Deciding the wrong version produces a verdict no chart page can cite.
Rule five: adversarially verify before landing
Every batch so far has produced errors that survived drafting and died on contact with the chart source. Draft the lane from the witness, then open the packaged chart and try to refute each finding. Rules one through three exist because that pass caught them.
scripts/scan-flattening-witnesses-all.mjs keeps its downloads in a cache, so the tarball is usually already on disk.
Cross-check the render too, not just the chart. The witness looks for generated credentials, so a Secret whose data is written straight from values is invisible to it. The minio tenant chart renders MINIO_ROOT_PASSWORD as a literal and its witness reports no generated secrets at all. npm run verdict-render-parity:verify now refuses any verdict calling a class absent that its own render contains, across hooks, keep policy, CRDs, webhooks and Secrets carrying data.
Rule six: a lane that needs a companion must be able to get one
flatten-with-routes names artifacts that have to exist. Before choosing it, check that the class has a route kind that can discharge it: hooks take lifecycle-job, keep policy takes prune-protection, CRDs take apply-ordering, credentials take external-secret-reference.
A chart whose hooks cannot be expressed as an ordered set of actions is unsafe-to-flatten, not flatten-with-routes with an aspiration. Consul's forty-four hook objects across nine phase combinations are the current example.
What the verdict must say
Every entry carries a rationale in plain sentences saying what decided it, and a variantScope naming the values that would move it. Findings that the render cannot settle stay not-evaluated with the reason, never absent by omission.
Then regenerate in this order, because the site reads the catalog and the catalog reads the verdicts:
npm run flattening-safety -- --generate && npm run flattening-evidence -- --generate && npm run certified-bundles -- --generate && npm run npm-scripts:catalog -- --generate && npm run data:index -- --generate && npm run site:generate
Then npm run chart-claim-integrity:verify, which refuses any page asserting a flattening claim its verdict does not back.
What a decided lane does not mean
It does not mean a bundle exists, and it does not mean one will. Publication is a separate step gated on the lane permitting it. An unsafe-to-flatten entry must never carry a certified-bundle receipt, and scripts/publish-certified-bundles.mjs refuses to produce one.
It also says nothing about runtime health. The verdict weighs what flattening loses at render time, not whether the result converges on a cluster.