Skip to content
Install the CLI

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.

#CheckSkips
1A workload’s serviceAccountName names a ServiceAccount in its namespace.default; unset
2A 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
3volumes[].configMap.name names a ConfigMap in the workload’s namespace.Secret volumes (often provisioned externally)
4A 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
5A HorizontalPodAutoscaler scaleTargetRef names a workload in its namespace.
6A StatefulSet serviceName names a headless Service (clusterIP: None) in its namespace.Unset serviceName
7No two resources share the same kind, namespace, and name.
8Every resource carries app.kubernetes.io/managed-by: kix in metadata.labels.
9A workload’s selector.matchLabels is a subset of its pod template labels (Deployment, StatefulSet, DaemonSet, Job).
10Ingress backends name a Service in the same namespace.
11Every metadata.namespace has a corresponding Namespace resource in the build._-prefixed sentinel namespaces used internally for cluster-scoped instances
12A NetworkPolicy podSelector matches at least one pod template in its namespace.Empty podSelector ({ })
13A named Service targetPort exists on a container of a selected pod template.Numeric targetPort values; Services that match no pod template in the build
14A PodDisruptionBudget selector.matchLabels matches at least one pod template in its namespace.PDBs without matchLabels; PDBs carrying kix.run/operand-of
15A 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
16The kix.run/rerun annotation appears only on Job resources and only with the value "on-change".
17A workload’s priorityClassName names a PriorityClass in the build.system-cluster-critical and system-node-critical
18A 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)
19A workload’s volumes[].persistentVolumeClaim.claimName names a PersistentVolumeClaim in its namespace.
20The 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
21Every 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
22Every 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
23At 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".
24A 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 CustomResourceDefinition in the build (scope.mkCRD, vendored CRDs, or chart CRDs);
  • a CRD reference marker (scope.mkCRDRef or kix.mkImport { crds = [ ... ]; }); or
  • an APIService for 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.

- [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 compares named key reads against:

  • kix.run/secret-keys on Secrets built with scope.mkSecret;
  • kix.run/import-secret-keys on external Secret markers built with scope.mkSecretRef; and
  • data and binaryData keys on ConfigMap resources 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.

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.untrackedRef satisfies name checks in the referring resource’s namespace regardless of kind.
  • Service and Secret ref markers: scope.mkServiceRef and scope.mkSecretRef satisfy Service and Secret checks in their namespace, and the selector declared on scope.mkServiceRef counts as a pod template for checks 4 and 12. (Check 6 still requires a rendered Service with clusterIP: None.)
  • Platform and service imports: kix.mkImport markers with kind and out.name satisfy checks for that kind. Built-in cluster-scoped kinds (StorageClass, IngressClass) imported via kix.mkImport satisfy references across all namespaces.
  • Operator-managed pods: A Service (check 4) or PodDisruptionBudget (check 14) that selects pods created at runtime by an operator can set metadata.annotations."kix.run/operand-of" = "<group>/<Kind>/<name>". The check passes when that custom resource exists in the same namespace in the build.

When one or more checks fail, cluster evaluation stops and lists every violation:

error: Cluster 'homelab-kind' has 1 failed assertion(s):
- [validate] Service data/postgres-metrics selector {"app":"pg"} matches no workload pod template