@intentius/chant-lexicon-k8s 0.45.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/components/kubectl-apply.d.ts +5 -0
  2. package/dist/components/kubectl-apply.d.ts.map +1 -1
  3. package/dist/composites/flux-app.d.ts +12 -0
  4. package/dist/composites/flux-app.d.ts.map +1 -1
  5. package/dist/composites/index.d.ts +6 -0
  6. package/dist/composites/index.d.ts.map +1 -1
  7. package/dist/composites/inference-service.d.ts +86 -0
  8. package/dist/composites/inference-service.d.ts.map +1 -0
  9. package/dist/composites/model.d.ts +80 -0
  10. package/dist/composites/model.d.ts.map +1 -0
  11. package/dist/composites/vllm-serving-runtime.d.ts +100 -0
  12. package/dist/composites/vllm-serving-runtime.d.ts.map +1 -0
  13. package/dist/crd/crd-sources.d.ts.map +1 -1
  14. package/dist/deep-observe-hooks.d.ts.map +1 -1
  15. package/dist/describe-resources.d.ts.map +1 -1
  16. package/dist/generated/index.d.ts +167 -40
  17. package/dist/generated/index.d.ts.map +1 -1
  18. package/dist/group-namespace.d.ts.map +1 -1
  19. package/dist/index.d.ts +3 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/integrity.json +13 -5
  22. package/dist/kube/apply.d.ts.map +1 -1
  23. package/dist/lint/audit-catalog.d.ts.map +1 -1
  24. package/dist/lint/post-synth/index.d.ts.map +1 -1
  25. package/dist/lint/post-synth/k8s-helpers.d.ts +28 -1
  26. package/dist/lint/post-synth/k8s-helpers.d.ts.map +1 -1
  27. package/dist/lint/post-synth/sops-helpers.d.ts +38 -0
  28. package/dist/lint/post-synth/sops-helpers.d.ts.map +1 -0
  29. package/dist/lint/post-synth/wk8404.d.ts +12 -0
  30. package/dist/lint/post-synth/wk8404.d.ts.map +1 -0
  31. package/dist/lint/post-synth/wk8405.d.ts +13 -0
  32. package/dist/lint/post-synth/wk8405.d.ts.map +1 -0
  33. package/dist/lint/post-synth/wk8406.d.ts +12 -0
  34. package/dist/lint/post-synth/wk8406.d.ts.map +1 -0
  35. package/dist/lint/post-synth/wk8407.d.ts +21 -0
  36. package/dist/lint/post-synth/wk8407.d.ts.map +1 -0
  37. package/dist/lint/post-synth/wk8503.d.ts +39 -0
  38. package/dist/lint/post-synth/wk8503.d.ts.map +1 -0
  39. package/dist/lint/post-synth/wk8504.d.ts +28 -0
  40. package/dist/lint/post-synth/wk8504.d.ts.map +1 -0
  41. package/dist/lint/post-synth/wk8505.d.ts +28 -0
  42. package/dist/lint/post-synth/wk8505.d.ts.map +1 -0
  43. package/dist/manifest.json +1 -1
  44. package/dist/meta.json +1039 -68
  45. package/dist/okf/index.md +26 -2
  46. package/dist/okf/rules/FLUX002.md +0 -1
  47. package/dist/okf/rules/WK8404.md +11 -0
  48. package/dist/okf/rules/WK8405.md +17 -0
  49. package/dist/okf/rules/WK8406.md +11 -0
  50. package/dist/okf/rules/WK8407.md +15 -0
  51. package/dist/okf/rules/WK8503.md +17 -0
  52. package/dist/okf/rules/WK8504.md +11 -0
  53. package/dist/okf/rules/WK8505.md +15 -0
  54. package/dist/okf/types/AWSClusterControllerIdentity.md +14 -0
  55. package/dist/okf/types/AWSManagedCluster.md +14 -0
  56. package/dist/okf/types/AWSManagedControlPlane.md +14 -0
  57. package/dist/okf/types/AWSManagedMachinePool.md +14 -0
  58. package/dist/okf/types/CAPICluster.md +14 -0
  59. package/dist/okf/types/Certificate.md +1 -0
  60. package/dist/okf/types/ClusterResourceSet.md +14 -0
  61. package/dist/okf/types/ClusterServingRuntime.md +14 -0
  62. package/dist/okf/types/{Cluster.md → CnpgCluster.md} +2 -2
  63. package/dist/okf/types/DBInstance.md +14 -0
  64. package/dist/okf/types/{Bucket.md → FluxBucket.md} +2 -6
  65. package/dist/okf/types/HelmChartProxy.md +14 -0
  66. package/dist/okf/types/IamRole.md +14 -0
  67. package/dist/okf/types/InferenceService.md +19 -0
  68. package/dist/okf/types/InfisicalDynamicSecret.md +4 -0
  69. package/dist/okf/types/InfisicalSecret.md +4 -0
  70. package/dist/okf/types/Kustomization.md +1 -0
  71. package/dist/okf/types/MachinePool.md +14 -0
  72. package/dist/okf/types/PDB.md +1 -0
  73. package/dist/okf/types/PodIdentityAssociation.md +14 -0
  74. package/dist/okf/types/Policy.md +14 -0
  75. package/dist/okf/types/S3Bucket.md +14 -0
  76. package/dist/okf/types/ServingRuntime.md +14 -0
  77. package/dist/okf/types/StatefulSet.md +1 -0
  78. package/dist/okf/types/User.md +14 -0
  79. package/dist/op/activities/ensure-secret.d.ts +41 -0
  80. package/dist/op/activities/ensure-secret.d.ts.map +1 -0
  81. package/dist/op/activities/index.d.ts +2 -0
  82. package/dist/op/activities/index.d.ts.map +1 -1
  83. package/dist/op/activities/kubectl.d.ts +9 -0
  84. package/dist/op/activities/kubectl.d.ts.map +1 -1
  85. package/dist/op/activities/wait-for-ready.d.ts.map +1 -1
  86. package/dist/plugin.d.ts.map +1 -1
  87. package/dist/rules/k8s-helpers.ts +55 -1
  88. package/dist/rules/sops-helpers.ts +76 -0
  89. package/dist/rules/wk8404.ts +55 -0
  90. package/dist/rules/wk8405.ts +88 -0
  91. package/dist/rules/wk8406.ts +60 -0
  92. package/dist/rules/wk8407.ts +71 -0
  93. package/dist/rules/wk8503.ts +220 -0
  94. package/dist/rules/wk8504.ts +51 -0
  95. package/dist/rules/wk8505.ts +63 -0
  96. package/dist/secret-labels.d.ts +31 -0
  97. package/dist/secret-labels.d.ts.map +1 -0
  98. package/dist/secret-store.d.ts +73 -0
  99. package/dist/secret-store.d.ts.map +1 -0
  100. package/dist/serializer.d.ts.map +1 -1
  101. package/dist/sops/encrypted-secret-file.d.ts +52 -0
  102. package/dist/sops/encrypted-secret-file.d.ts.map +1 -0
  103. package/dist/sops/entity.d.ts +31 -0
  104. package/dist/sops/entity.d.ts.map +1 -0
  105. package/dist/teardown.d.ts +46 -0
  106. package/dist/teardown.d.ts.map +1 -0
  107. package/dist/types/index.d.ts +261 -9
  108. package/package.json +4 -4
  109. package/src/components/kubectl-apply.ts +5 -0
  110. package/src/composites/composites.test.ts +273 -0
  111. package/src/composites/flux-app.ts +23 -0
  112. package/src/composites/index.ts +11 -0
  113. package/src/composites/inference-service.ts +140 -0
  114. package/src/composites/model.ts +150 -0
  115. package/src/composites/vllm-serving-runtime.test.ts +167 -0
  116. package/src/composites/vllm-serving-runtime.ts +196 -0
  117. package/src/crd/cnpg.test.ts +8 -3
  118. package/src/crd/crd-sources.ts +183 -0
  119. package/src/crd/parser.test.ts +14 -0
  120. package/src/deep-observe-hooks.ts +24 -0
  121. package/src/deep-observe.test.ts +89 -0
  122. package/src/describe-resources.ts +6 -1
  123. package/src/generated/index.d.ts +261 -9
  124. package/src/generated/index.ts +167 -40
  125. package/src/generated/lexicon-k8s.json +1039 -68
  126. package/src/generated/operations.json +272 -0
  127. package/src/group-namespace.test.ts +31 -0
  128. package/src/group-namespace.ts +13 -0
  129. package/src/index.ts +6 -0
  130. package/src/kube/apply.ts +5 -0
  131. package/src/lifecycle-integration.test.ts +42 -0
  132. package/src/lint/audit-catalog.ts +7 -0
  133. package/src/lint/post-synth/index.ts +14 -0
  134. package/src/lint/post-synth/k8s-helpers.ts +55 -1
  135. package/src/lint/post-synth/post-synth.test.ts +826 -0
  136. package/src/lint/post-synth/sops-helpers.ts +76 -0
  137. package/src/lint/post-synth/wk8404.ts +55 -0
  138. package/src/lint/post-synth/wk8405.ts +88 -0
  139. package/src/lint/post-synth/wk8406.ts +60 -0
  140. package/src/lint/post-synth/wk8407.ts +71 -0
  141. package/src/lint/post-synth/wk8503.ts +220 -0
  142. package/src/lint/post-synth/wk8504.ts +51 -0
  143. package/src/lint/post-synth/wk8505.ts +63 -0
  144. package/src/op/activities/ensure-secret.ts +71 -0
  145. package/src/op/activities/index.ts +7 -0
  146. package/src/op/activities/kubectl.test.ts +32 -0
  147. package/src/op/activities/kubectl.ts +28 -5
  148. package/src/op/activities/wait-for-ready.test.ts +34 -0
  149. package/src/op/activities/wait-for-ready.ts +26 -0
  150. package/src/plugin.ts +34 -2
  151. package/src/secret-labels.ts +36 -0
  152. package/src/secret-store.test.ts +184 -0
  153. package/src/secret-store.ts +165 -0
  154. package/src/serializer.ts +33 -2
  155. package/src/sops/encrypted-secret-file.test.ts +333 -0
  156. package/src/sops/encrypted-secret-file.ts +264 -0
  157. package/src/sops/entity.ts +53 -0
  158. package/src/teardown.test.ts +240 -0
  159. package/src/teardown.ts +239 -0
  160. package/src/testdata/sops/db-credentials.sops.yaml +23 -0
@@ -19,6 +19,8 @@
19
19
  */
20
20
  export { kubectlApply, applyManifest, readManifestDocuments } from "./kubectl.js";
21
21
  export type { KubectlApplyArgs, ApplyManifestResult, AppliedRef, ApplyDeleteMode } from "./kubectl.js";
22
+ export { ensureSecret } from "./ensure-secret.js";
23
+ export type { EnsureSecretActivityArgs } from "./ensure-secret.js";
22
24
  export { waitForArgoSync, defaultArgoStatusFetcher, ArgoSyncFailedError } from "./argo.js";
23
25
  export type { WaitForArgoSyncArgs, ArgoAppStatus, ArgoStatusFetcher } from "./argo.js";
24
26
  export { waitForReady, defaultResourceFetcher, apiResourceFetcher, ReadinessFailedError, readinessFor, isReady, firstTerminal, DEFAULT_READINESS, READINESS_OVERRIDES, } from "./wait-for-ready.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/op/activities/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC/E,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEpG,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AACxF,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAEpF,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,cAAc,EACd,cAAc,EACd,SAAS,GACV,MAAM,kBAAkB,CAAC;AAW1B,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AACrG,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/op/activities/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC/E,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAMpG,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAEhE,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AACxF,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAEpF,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,cAAc,EACd,cAAc,EACd,SAAS,GACV,MAAM,kBAAkB,CAAC;AAW1B,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AACrG,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC"}
@@ -116,6 +116,15 @@ export interface ApplyManifestResult {
116
116
  applied: AppliedRef[];
117
117
  /** Objects deleted because they carried chant's marker and are no longer declared. */
118
118
  pruned: AppliedRef[];
119
+ /**
120
+ * Owned, no longer declared, and deliberately KEPT (#1830, epic #1365
121
+ * decision 5): Secrets carrying the `chant.intentius.io/generated-once`
122
+ * label never enter the prunable set — the stored bytes are the only copy
123
+ * of material chant never held. Reported loudly here and on the console;
124
+ * deletion is an explicit act (`kubectl delete`, or a future gated op),
125
+ * never a prune's. Present whenever the prune ran.
126
+ */
127
+ retained?: AppliedRef[];
119
128
  }
120
129
  /** Read a manifest path — one file, or every YAML/JSON file in a directory. */
121
130
  export declare function readManifestDocuments(path: string): Record<string, unknown>[];
@@ -1 +1 @@
1
- {"version":3,"file":"kubectl.d.ts","sourceRoot":"","sources":["../../../src/op/activities/kubectl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAYH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAI3E;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sCAAsC;AACtC,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sFAAsF;IACtF,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAiB7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjF;AAqHD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CAkE9B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAEf"}
1
+ {"version":3,"file":"kubectl.d.ts","sourceRoot":"","sources":["../../../src/op/activities/kubectl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAYH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAK3E;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sCAAsC;AACtC,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sFAAsF;IACtF,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAiB7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjF;AAqHD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CAkE9B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAEf"}
@@ -1 +1 @@
1
- {"version":3,"file":"wait-for-ready.d.ts","sourceRoot":"","sources":["../../../src/op/activities/wait-for-ready.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wait-for-ready.d.ts","sourceRoot":"","sources":["../../../src/op/activities/wait-for-ready.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAqC,MAAM,0BAA0B,CAAC;AA2BjG,eAAO,MAAM,SAAS,EAAE,aAqqBvB,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAqC,MAAM,0BAA0B,CAAC;AA6BjG,eAAO,MAAM,SAAS,EAAE,aAmsBvB,CAAC"}
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { parseYAML } from "@intentius/chant/yaml";
9
- export { getPrimaryOutput } from "@intentius/chant/lint/post-synth";
9
+ export { getPrimaryOutput, getAdditionalFiles } from "@intentius/chant/lint/post-synth";
10
10
 
11
11
  /**
12
12
  * A parsed Kubernetes manifest (loosely typed).
@@ -103,6 +103,13 @@ export function extractPodSpec(
103
103
  return (template?.spec as Record<string, unknown>) ?? null;
104
104
  }
105
105
 
106
+ // KServe ServingRuntime/ClusterServingRuntime carry `containers` and
107
+ // `tolerations` directly on spec — no template wrapper, unlike the
108
+ // built-in workload kinds above.
109
+ case "ServingRuntime":
110
+ case "ClusterServingRuntime":
111
+ return spec;
112
+
106
113
  default:
107
114
  return null;
108
115
  }
@@ -148,6 +155,53 @@ export const WORKLOAD_KINDS = new Set([
148
155
  "CronJob",
149
156
  ]);
150
157
 
158
+ /**
159
+ * Workload kinds that can request `nvidia.com/gpu` — the built-in workload
160
+ * kinds plus KServe's ServingRuntime/ClusterServingRuntime, which declare
161
+ * `containers`/`tolerations` directly on spec (see `extractPodSpec`).
162
+ */
163
+ export const GPU_POD_KINDS = new Set([
164
+ ...WORKLOAD_KINDS,
165
+ "ServingRuntime",
166
+ "ClusterServingRuntime",
167
+ ]);
168
+
169
+ /** A Kubernetes toleration (loosely typed). */
170
+ export interface K8sToleration {
171
+ key?: string;
172
+ operator?: "Exists" | "Equal";
173
+ value?: string;
174
+ effect?: string;
175
+ [key: string]: unknown;
176
+ }
177
+
178
+ /** Resource name used for GPU requests/limits across the lexicon. */
179
+ export const NVIDIA_GPU_RESOURCE = "nvidia.com/gpu";
180
+
181
+ /**
182
+ * True when any container in `containers` requests or limits
183
+ * `nvidia.com/gpu`.
184
+ */
185
+ export function requestsGpu(containers: K8sContainer[]): boolean {
186
+ return containers.some((c) => {
187
+ const requests = c.resources?.requests as Record<string, unknown> | undefined;
188
+ const limits = c.resources?.limits as Record<string, unknown> | undefined;
189
+ return Boolean(requests?.[NVIDIA_GPU_RESOURCE]) || Boolean(limits?.[NVIDIA_GPU_RESOURCE]);
190
+ });
191
+ }
192
+
193
+ /**
194
+ * True when `tolerations` includes one that tolerates the `nvidia.com/gpu`
195
+ * taint — either an explicit `key: "nvidia.com/gpu"` toleration, or a
196
+ * wildcard `{ operator: "Exists" }` with no key (tolerates every taint).
197
+ */
198
+ export function toleratesGpu(tolerations: unknown): boolean {
199
+ if (!Array.isArray(tolerations)) return false;
200
+ return (tolerations as K8sToleration[]).some(
201
+ (t) => t.key === NVIDIA_GPU_RESOURCE || (t.operator === "Exists" && !t.key),
202
+ );
203
+ }
204
+
151
205
  /**
152
206
  * Parse a Kubernetes memory string to bytes.
153
207
  *
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared helper for the committed-encrypted secret checks: join each
3
+ * `declareSecret({ provenance: "committed-encrypted" })` in the entity map to
4
+ * the sidecar file the build emitted for it.
5
+ *
6
+ * The join has to go through `output.files`, not `getPrimaryOutput`. The
7
+ * ciphertext is deliberately kept OUT of the primary output — that is what
8
+ * makes applying an undecrypted Secret structurally impossible — so a check
9
+ * that reads only the primary output sees nothing at all here.
10
+ *
11
+ * Excluded from check auto-discovery by the "helper" filename filter.
12
+ */
13
+ import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
14
+ import type { CommittedEncryptedSecretDeclaration } from "@intentius/chant/secret-provenance";
15
+ import { getAdditionalFiles } from "./k8s-helpers";
16
+ import { committedEncryptedDeclarations, sidecarFilename, validateEncryptedSecretDocument, type EncryptedSecretDocument } from "../../sops/encrypted-secret-file";
17
+
18
+ /** One committed-encrypted declaration, joined to what the build emitted for it. */
19
+ export interface ResolvedEncryptedSecret {
20
+ /** The entity-map key the declaration was discovered under. */
21
+ entityName: string;
22
+ declaration: CommittedEncryptedSecretDeclaration;
23
+ /** The sidecar filename the declaration's path maps to. */
24
+ filename: string;
25
+ /** Undefined when the build emitted no such sidecar — the file did not resolve. */
26
+ text?: string;
27
+ /** Everything wrong with the emitted document. Empty when it is clean. */
28
+ problems: string[];
29
+ /** Parsed structure, when the document parsed at all. */
30
+ document?: EncryptedSecretDocument;
31
+ }
32
+
33
+ /** Every sidecar file across every lexicon output, keyed by filename. */
34
+ export function allAdditionalFiles(ctx: PostSynthContext): Map<string, string> {
35
+ const files = new Map<string, string>();
36
+ for (const [, output] of ctx.outputs) {
37
+ for (const [filename, content] of Object.entries(getAdditionalFiles(output))) {
38
+ files.set(filename, content);
39
+ }
40
+ }
41
+ return files;
42
+ }
43
+
44
+ /**
45
+ * Join every committed-encrypted declaration to its emitted sidecar,
46
+ * validating the document as it goes. A declaration with no sidecar comes
47
+ * back with `text` undefined and one problem naming the missing file.
48
+ */
49
+ export function resolveEncryptedSecretClaims(ctx: PostSynthContext): ResolvedEncryptedSecret[] {
50
+ const declarations = committedEncryptedDeclarations(ctx.entities);
51
+ if (declarations.size === 0) return [];
52
+
53
+ const files = allAdditionalFiles(ctx);
54
+ const resolved: ResolvedEncryptedSecret[] = [];
55
+
56
+ for (const [entityName, declaration] of declarations) {
57
+ const filename = sidecarFilename(declaration.file);
58
+ const text = files.get(filename);
59
+ if (text === undefined) {
60
+ resolved.push({
61
+ entityName,
62
+ declaration,
63
+ filename,
64
+ problems: [
65
+ `the build emitted no file for "${declaration.file}" — ` +
66
+ `the declared ciphertext did not resolve`,
67
+ ],
68
+ });
69
+ continue;
70
+ }
71
+ const { problems, document } = validateEncryptedSecretDocument(text, declaration);
72
+ resolved.push({ entityName, declaration, filename, text, problems, document });
73
+ }
74
+
75
+ return resolved;
76
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * WK8404: GPU request without a matching toleration
3
+ *
4
+ * A pod that requests `nvidia.com/gpu` but carries no toleration for the
5
+ * `nvidia.com/gpu` taint won't schedule onto a tainted GPU node pool (see
6
+ * `GpuNodePool`, #987, which taints its nodes `nvidia.com/gpu=present` by
7
+ * default). The pod sits `Pending` with `0/N nodes are available` until a
8
+ * toleration is added.
9
+ */
10
+
11
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
12
+ import {
13
+ getPrimaryOutput,
14
+ parseK8sManifests,
15
+ extractContainers,
16
+ extractPodSpec,
17
+ requestsGpu,
18
+ toleratesGpu,
19
+ GPU_POD_KINDS,
20
+ } from "./k8s-helpers";
21
+
22
+ export const wk8404: PostSynthCheck = {
23
+ id: "WK8404",
24
+ description: "GPU request without a matching nvidia.com/gpu toleration",
25
+
26
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
27
+ const diagnostics: PostSynthDiagnostic[] = [];
28
+
29
+ for (const [, output] of ctx.outputs) {
30
+ const yaml = getPrimaryOutput(output);
31
+ const manifests = parseK8sManifests(yaml);
32
+
33
+ for (const manifest of manifests) {
34
+ if (!manifest.kind || !GPU_POD_KINDS.has(manifest.kind)) continue;
35
+
36
+ const containers = extractContainers(manifest);
37
+ if (!requestsGpu(containers)) continue;
38
+
39
+ const podSpec = extractPodSpec(manifest);
40
+ if (toleratesGpu(podSpec?.tolerations)) continue;
41
+
42
+ const resourceName = manifest.metadata?.name ?? manifest.kind;
43
+ diagnostics.push({
44
+ checkId: "WK8404",
45
+ severity: "error",
46
+ message: `${manifest.kind} "${resourceName}" requests nvidia.com/gpu but has no toleration for the nvidia.com/gpu taint — it will not schedule onto a tainted GPU node pool`,
47
+ entity: resourceName,
48
+ lexicon: "k8s",
49
+ });
50
+ }
51
+ }
52
+
53
+ return diagnostics;
54
+ },
55
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * WK8405: Serving workload without a PodDisruptionBudget
3
+ *
4
+ * An InferenceService (or a Deployment carrying a serving
5
+ * `app.kubernetes.io/component` label — e.g. `inference-service` or
6
+ * `vllm-serving-runtime`) with no PodDisruptionBudget selecting it can lose
7
+ * every replica at once during a voluntary disruption (node drain, cluster
8
+ * upgrade), taking the model endpoint down. Serving workloads should ship
9
+ * with a PDB.
10
+ */
11
+
12
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
13
+ import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
14
+ import type { K8sManifest } from "./k8s-helpers";
15
+
16
+ /** `app.kubernetes.io/component` values this lexicon's composites use for serving workloads. */
17
+ const SERVING_COMPONENTS = new Set(["inference-service", "vllm-serving-runtime", "model-serving"]);
18
+
19
+ function isServingWorkload(manifest: K8sManifest): boolean {
20
+ if (manifest.kind === "InferenceService") return true;
21
+ if (manifest.kind !== "Deployment" && manifest.kind !== "StatefulSet") return false;
22
+ const component = manifest.metadata?.labels?.["app.kubernetes.io/component"];
23
+ return component !== undefined && SERVING_COMPONENTS.has(component);
24
+ }
25
+
26
+ export const wk8405: PostSynthCheck = {
27
+ id: "WK8405",
28
+ description: "Serving workload (InferenceService / serving Deployment) has no PodDisruptionBudget",
29
+
30
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
31
+ const diagnostics: PostSynthDiagnostic[] = [];
32
+
33
+ for (const [, output] of ctx.outputs) {
34
+ const yaml = getPrimaryOutput(output);
35
+ const manifests = parseK8sManifests(yaml);
36
+
37
+ const pdbSelectors = collectPdbSelectors(manifests);
38
+
39
+ for (const manifest of manifests) {
40
+ if (!isServingWorkload(manifest)) continue;
41
+
42
+ const labels = manifest.metadata?.labels;
43
+ const resourceName = manifest.metadata?.name ?? manifest.kind ?? "workload";
44
+
45
+ if (!labels || !hasCoveringPdb(labels, pdbSelectors)) {
46
+ diagnostics.push({
47
+ checkId: "WK8405",
48
+ severity: "info",
49
+ message: `${manifest.kind} "${resourceName}" is a serving workload with no PodDisruptionBudget — add a PDB so a node drain or cluster upgrade cannot take down every replica at once`,
50
+ entity: resourceName,
51
+ lexicon: "k8s",
52
+ });
53
+ }
54
+ }
55
+ }
56
+
57
+ return diagnostics;
58
+ },
59
+ };
60
+
61
+ function collectPdbSelectors(manifests: K8sManifest[]): Array<Record<string, string>> {
62
+ const selectors: Array<Record<string, string>> = [];
63
+
64
+ for (const manifest of manifests) {
65
+ if (manifest.kind !== "PodDisruptionBudget") continue;
66
+
67
+ const spec = manifest.spec;
68
+ if (!spec) continue;
69
+
70
+ const selector = spec.selector as Record<string, unknown> | undefined;
71
+ const matchLabels = selector?.matchLabels as Record<string, string> | undefined;
72
+
73
+ if (matchLabels && typeof matchLabels === "object") {
74
+ selectors.push(matchLabels);
75
+ }
76
+ }
77
+
78
+ return selectors;
79
+ }
80
+
81
+ function hasCoveringPdb(
82
+ workloadLabels: Record<string, string>,
83
+ pdbSelectors: Array<Record<string, string>>,
84
+ ): boolean {
85
+ return pdbSelectors.some((pdbLabels) =>
86
+ Object.entries(pdbLabels).every(([key, value]) => workloadLabels[key] === value),
87
+ );
88
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * WK8406: No resource limits on a GPU pod
3
+ *
4
+ * A container that requests `nvidia.com/gpu` but sets no cpu/memory limits
5
+ * can starve other pods on the same (expensive, scarce) GPU node of CPU and
6
+ * memory headroom. Unlike WK8201's general resource-limits guidance, this
7
+ * check is scoped to GPU-requesting containers specifically, since GPU node
8
+ * capacity is costlier and more contended than a general-purpose pool.
9
+ */
10
+
11
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
12
+ import { getPrimaryOutput, parseK8sManifests, extractContainers, GPU_POD_KINDS, NVIDIA_GPU_RESOURCE } from "./k8s-helpers";
13
+
14
+ export const wk8406: PostSynthCheck = {
15
+ id: "WK8406",
16
+ description: "GPU-requesting container has no cpu/memory resource limits",
17
+
18
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
19
+ const diagnostics: PostSynthDiagnostic[] = [];
20
+
21
+ for (const [, output] of ctx.outputs) {
22
+ const yaml = getPrimaryOutput(output);
23
+ const manifests = parseK8sManifests(yaml);
24
+
25
+ for (const manifest of manifests) {
26
+ if (!manifest.kind || !GPU_POD_KINDS.has(manifest.kind)) continue;
27
+
28
+ const containers = extractContainers(manifest);
29
+ const resourceName = manifest.metadata?.name ?? manifest.kind;
30
+
31
+ for (const container of containers) {
32
+ const requests = container.resources?.requests as Record<string, unknown> | undefined;
33
+ const limits = container.resources?.limits as Record<string, unknown> | undefined;
34
+ const requestsGpu = Boolean(requests?.[NVIDIA_GPU_RESOURCE]) || Boolean(limits?.[NVIDIA_GPU_RESOURCE]);
35
+ if (!requestsGpu) continue;
36
+
37
+ const missing: string[] = [];
38
+ if (!limits || typeof limits !== "object") {
39
+ missing.push("cpu", "memory");
40
+ } else {
41
+ if (!limits.cpu) missing.push("cpu");
42
+ if (!limits.memory) missing.push("memory");
43
+ }
44
+
45
+ if (missing.length > 0) {
46
+ diagnostics.push({
47
+ checkId: "WK8406",
48
+ severity: "warning",
49
+ message: `GPU container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" is missing resource limits for ${missing.join(", ")} — a GPU pod without cpu/memory limits can starve its (scarce, expensive) node`,
50
+ entity: resourceName,
51
+ lexicon: "k8s",
52
+ });
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ return diagnostics;
59
+ },
60
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * WK8407: Unpinned model version
3
+ *
4
+ * KServe's InferenceService predictor pins a model by `storageUri`. The
5
+ * `Model` composite (#986) always appends `version` as the trailing path
6
+ * segment (`scheme://id/version`), so a resolved `Model` reference is
7
+ * always pinned — but `InferenceService.model` also accepts a raw
8
+ * `storageUri` string (#985), which can bypass that guarantee (e.g.
9
+ * `"gs://bucket/model"` with no version segment, or a floating tag like
10
+ * `"latest"`/`"main"`). An unpinned reference means a redeploy of the same
11
+ * InferenceService can silently pick up different weights.
12
+ */
13
+
14
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
15
+ import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
16
+
17
+ /** Trailing path segments that mean "no explicit version" even though present. */
18
+ const FLOATING_TAGS = new Set(["latest", "main", "master", "head"]);
19
+
20
+ /**
21
+ * True when `storageUri` has an explicit, non-floating version segment:
22
+ * `scheme://path/version` with at least two path segments after the
23
+ * scheme, and the last segment isn't a known floating tag.
24
+ */
25
+ export function isPinnedStorageUri(storageUri: string): boolean {
26
+ const m = /^[a-z][a-z0-9+.-]*:\/\/(.+)$/i.exec(storageUri.trim());
27
+ if (!m) return false;
28
+
29
+ const segments = m[1].split("/").filter((s) => s.length > 0);
30
+ if (segments.length < 2) return false;
31
+
32
+ const last = segments[segments.length - 1].toLowerCase();
33
+ return !FLOATING_TAGS.has(last);
34
+ }
35
+
36
+ export const wk8407: PostSynthCheck = {
37
+ id: "WK8407",
38
+ description: "InferenceService model storageUri has no explicit version segment",
39
+
40
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
41
+ const diagnostics: PostSynthDiagnostic[] = [];
42
+
43
+ for (const [, output] of ctx.outputs) {
44
+ const yaml = getPrimaryOutput(output);
45
+ const manifests = parseK8sManifests(yaml);
46
+
47
+ for (const manifest of manifests) {
48
+ if (manifest.kind !== "InferenceService") continue;
49
+
50
+ const spec = manifest.spec as Record<string, unknown> | undefined;
51
+ const predictor = spec?.predictor as Record<string, unknown> | undefined;
52
+ const model = predictor?.model as Record<string, unknown> | undefined;
53
+ const storageUri = model?.storageUri as string | undefined;
54
+ if (!storageUri) continue;
55
+
56
+ if (!isPinnedStorageUri(storageUri)) {
57
+ const resourceName = manifest.metadata?.name ?? "InferenceService";
58
+ diagnostics.push({
59
+ checkId: "WK8407",
60
+ severity: "warning",
61
+ message: `InferenceService "${resourceName}": model storageUri "${storageUri}" has no explicit version segment — pin it (e.g. via the Model composite's required \`version\`) so a redeploy can't silently pick up different weights`,
62
+ entity: resourceName,
63
+ lexicon: "k8s",
64
+ });
65
+ }
66
+ }
67
+ }
68
+
69
+ return diagnostics;
70
+ },
71
+ };