Reference / Policy and runtime contracts
Validation checks
Whenever you compile or deploy a cluster, Kix runs 24 cross-resource validation checks across all rendered manifests to catch wiring mistakes that would otherwise fail in Kubernetes. Because these checks guard against broken references (such as a Service pointing to a missing port, a workload mounting a nonexistent ConfigMap, or a custom resource deployed before its CRD), they are always active and cannot be turned off.
This page is maintained alongside kix/lib/eval/validate.nix. The test suites in kix/lib/tests/validate.nix (checks 1 through 18) and kix/lib/tests/units/lib-reference-checks.nix (checks 18 through 24) contain passing and failing cases for each check.
Checks
Section titled “Checks”| # | Check | Skips |
|---|---|---|
| 1 | A workload’s serviceAccountName names a ServiceAccount in its namespace. | default; unset |
| 2 | A RoleBinding or ClusterRoleBinding roleRef names a Role or ClusterRole in the build. | Names starting with system: or ending with -psp; admin, edit, view, cluster-admin, and extension-apiserver-authentication-reader |
| 3 | volumes[].configMap.name names a ConfigMap in the workload’s namespace. | Secret volumes (often provisioned externally) |
| 4 | A Service selector matches at least one workload pod template in its namespace. | Services with no selector; control-plane scrape selectors (kube-dns, kube-scheduler, and similar); Services carrying kix.run/operand-of |
| 5 | A HorizontalPodAutoscaler scaleTargetRef names a workload in its namespace. | |
| 6 | A StatefulSet serviceName names a headless Service (clusterIP: None) in its namespace. | Unset serviceName |
| 7 | No two resources share the same kind, namespace, and name. | |
| 8 | Every resource carries app.kubernetes.io/managed-by: kix in metadata.labels. | |
| 9 | A workload’s selector.matchLabels is a subset of its pod template labels (Deployment, StatefulSet, DaemonSet, Job). | |
| 10 | Ingress backends name a Service in the same namespace. | |
| 11 | Every metadata.namespace has a corresponding Namespace resource in the build. | _-prefixed sentinel namespaces used internally for cluster-scoped instances |
| 12 | A NetworkPolicy podSelector matches at least one pod template in its namespace. | Empty podSelector ({ }) |
| 13 | A named Service targetPort exists on a container of a selected pod template. | Numeric targetPort values; Services that match no pod template in the build |
| 14 | A PodDisruptionBudget selector.matchLabels matches at least one pod template in its namespace. | PDBs without matchLabels; PDBs carrying kix.run/operand-of |
| 15 | A Job’s pod template labels do not satisfy any Service selector in its namespace (which would route live service traffic to a batch Job pod). | Services without a selector |
| 16 | The kix.run/rerun annotation appears only on Job resources and only with the value "on-change". | |
| 17 | A workload’s priorityClassName names a PriorityClass in the build. | system-cluster-critical and system-node-critical |
| 18 | A resource of a namespaced kind sets metadata.namespace. | Custom kinds whose scope is not declared in the build (verified against cluster API discovery by kix deploy instead) |
| 19 | A workload’s volumes[].persistentVolumeClaim.claimName names a PersistentVolumeClaim in its namespace. | |
| 20 | The storageClassName on a PersistentVolumeClaim, on a StatefulSet’s volumeClaimTemplates, or on an ephemeral volume’s volumeClaimTemplate names a StorageClass in the build (or a platform import), or matches a PersistentVolume in the build. | Unset or ""; storage class names embedded inside operator custom resource specs |
| 21 | Every custom resource outside the built-in Kubernetes API groups depends (directly or transitively via kix.run/depends-on) on a CustomResourceDefinition, a CRD ref marker, or an APIService for its group and kind that serves its version. | Built-in control-plane API groups ("", apps, batch, autoscaling, policy, networking.k8s.io, rbac.authorization.k8s.io, storage.k8s.io, scheduling.k8s.io, admissionregistration.k8s.io, apiextensions.k8s.io, apiregistration.k8s.io, coordination.k8s.io, discovery.k8s.io, events.k8s.io, node.k8s.io, certificates.k8s.io, flowcontrol.apiserver.k8s.io, resource.k8s.io, authentication.k8s.io, authorization.k8s.io, internal.apiserver.k8s.io, storagemigration.k8s.io, and kix.run); extension groups under *.k8s.io such as gateway.networking.k8s.io, snapshot.storage.k8s.io, and metrics.k8s.io are not skipped |
| 22 | Every key a container reads by name from a Secret or ConfigMap (secretKeyRef, configMapKeyRef, volume or projected items[].key, or the first path segment of a subPath mount without items) is declared on that Secret or present in that ConfigMap. | References with optional = true; whole-object mounts (envFrom, volumes without items or subPath); names wrapped in kix.untrackedRef; Secrets without kix.run/secret-keys (such as raw Helm chart Secrets); objects ceding data, binaryData, or stringData via kix.run/cede-fields |
| 23 | At most one PriorityClass sets globalDefault: true, at most one StorageClass carries storageclass.kubernetes.io/is-default-class: "true" (or the beta storageclass.beta.kubernetes.io/is-default-class), and at most one IngressClass carries ingressclass.kubernetes.io/is-default-class: "true". | |
| 24 | A Job does not set spec.ttlSecondsAfterFinished (so Kubernetes does not delete finished Jobs behind Kix’s back and cause later deploys to re-run them). | CronJob spec.jobTemplate specs (whose Jobs are created and cleaned up by Kubernetes) |
Check 18 and deploy-time scope verification
Section titled “Check 18 and deploy-time scope verification”Check 18 verifies all built-in namespaced kinds, custom kinds whose CustomResourceDefinition in the build sets spec.scope: Namespaced, and custom kinds declared via scope.mkCRDRef with crdScope = "Namespaced" (the default). For example, building a ConfigMap with scope.mkClusterResource fails check 18 at evaluation time.
If a custom resource’s scope is not known at build time, kix deploy queries the target cluster’s API discovery before applying any wave and aborts the plan if a namespaced resource omits metadata.namespace.
Check 21: custom resources and their CRD declarations
Section titled “Check 21: custom resources and their CRD declarations”The Kubernetes API server rejects custom resources whose kind is not yet established, so kix deploy must order every custom resource after its CRD. Check 21 verifies that every resource outside the built-in API groups has a dependency path in kix.run/depends-on to one of:
- a
CustomResourceDefinitionin the build (scope.mkCRD, vendored CRDs, or chart CRDs); - a CRD reference marker (
scope.mkCRDReforkix.mkImport { crds = [ ... ]; }); or - an
APIServicefor its API group.
Check 21 also verifies that the custom resource’s API version is served by the declaration (served = true in spec.versions on a CRD, or listed in servedVersions on a mkCRDRef). Building custom resources with scope.mkCR dep.out.crds.<Kind> wires this dependency automatically.
When a Helm chart renders a custom resource belonging to another package’s CRD (such as a ServiceMonitor enabled via chart values), check 21 fails because the chart resource has no dependency link to the prometheus-operator CRD marker. Disable the chart’s built-in toggle and use kix.monitor or scope.mkCR in the Kix package instead.
1- [validate] ServiceMonitor apps/web (instance apps/web) does not depend on the CRD ref crd-ref-prometheus-operator-servicemonitors-monitoring-coreos-com for servicemonitors.monitoring.coreos.com (instance monitoring/prometheus-operator), which declares its kind, so the deploy can apply it before the CRD is served. ...Check 22: named Secret and ConfigMap keys
Section titled “Check 22: named Secret and ConfigMap keys”Check 22 compares named key reads against:
kix.run/secret-keyson Secrets built withscope.mkSecret;kix.run/import-secret-keyson external Secret markers built withscope.mkSecretRef; anddataandbinaryDatakeys onConfigMapresources in the build.
If a scope.mkSecretRef sets keys = [ ], any container that references a specific key by name fails check 22. List the expected keys in keys so both check 22 and kix deploy’s pre-wave readiness check can verify them.
How names and selectors resolve
Section titled “How names and selectors resolve”References are resolved in the referring resource’s own namespace, except for built-in cluster-scoped kinds (such as a RoleBinding referencing a ClusterRole or a PersistentVolumeClaim referencing a StorageClass). Selectors on Service, NetworkPolicy, and PodDisruptionBudget resources match pod templates in the same namespace only.
The following markers satisfy existence and selector checks:
- Untracked references: A name wrapped in
kix.untrackedRefsatisfies name checks in the referring resource’s namespace regardless of kind. - Service and Secret ref markers:
scope.mkServiceRefandscope.mkSecretRefsatisfyServiceandSecretchecks in their namespace, and theselectordeclared onscope.mkServiceRefcounts as a pod template for checks 4 and 12. (Check 6 still requires a renderedServicewithclusterIP: None.) - Platform and service imports:
kix.mkImportmarkers withkindandout.namesatisfy checks for that kind. Built-in cluster-scoped kinds (StorageClass,IngressClass) imported viakix.mkImportsatisfy references across all namespaces. - Operator-managed pods: A
Service(check 4) orPodDisruptionBudget(check 14) that selects pods created at runtime by an operator can setmetadata.annotations."kix.run/operand-of" = "<group>/<Kind>/<name>". The check passes when that custom resource exists in the same namespace in the build.
Failure output
Section titled “Failure output”When one or more checks fail, cluster evaluation stops and lists every violation:
1error: Cluster 'homelab-kind' has 1 failed assertion(s):2 - [validate] Service data/postgres-metrics selector {"app":"pg"} matches no workload pod template