@intentius/chant-lexicon-k8s 0.41.20 → 0.44.1

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 (365) hide show
  1. package/dist/api/classify.d.ts.map +1 -1
  2. package/dist/api/connect.d.ts.map +1 -1
  3. package/dist/codegen/docs.d.ts.map +1 -1
  4. package/dist/components/argo-app.d.ts +104 -0
  5. package/dist/components/argo-app.d.ts.map +1 -0
  6. package/dist/components/builders.d.ts +8 -0
  7. package/dist/components/builders.d.ts.map +1 -1
  8. package/dist/components/capability-plugin.d.ts +1 -0
  9. package/dist/components/capability-plugin.d.ts.map +1 -1
  10. package/dist/components/flux-reconcile.d.ts +84 -0
  11. package/dist/components/flux-reconcile.d.ts.map +1 -0
  12. package/dist/components/index.d.ts +2 -0
  13. package/dist/components/index.d.ts.map +1 -1
  14. package/dist/components/kustomize-apply.d.ts +29 -7
  15. package/dist/components/kustomize-apply.d.ts.map +1 -1
  16. package/dist/composites/flux-app.d.ts +132 -0
  17. package/dist/composites/flux-app.d.ts.map +1 -0
  18. package/dist/composites/index.d.ts +2 -0
  19. package/dist/composites/index.d.ts.map +1 -1
  20. package/dist/config-schema.d.ts +3 -0
  21. package/dist/config-schema.d.ts.map +1 -1
  22. package/dist/config.d.ts +32 -5
  23. package/dist/config.d.ts.map +1 -1
  24. package/dist/crd/crd-sources.d.ts.map +1 -1
  25. package/dist/crd/parser.d.ts.map +1 -1
  26. package/dist/deep-observe-hooks.d.ts +8 -0
  27. package/dist/deep-observe-hooks.d.ts.map +1 -1
  28. package/dist/describe-resources.d.ts +10 -8
  29. package/dist/describe-resources.d.ts.map +1 -1
  30. package/dist/generated/index.d.ts +15 -4
  31. package/dist/generated/index.d.ts.map +1 -1
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/integrity.json +10 -6
  35. package/dist/kube/connect.d.ts +5 -4
  36. package/dist/kube/connect.d.ts.map +1 -1
  37. package/dist/kube/get.d.ts +4 -4
  38. package/dist/kustomize/render.d.ts +15 -0
  39. package/dist/kustomize/render.d.ts.map +1 -0
  40. package/dist/kustomize/rendered-entity.d.ts +39 -0
  41. package/dist/kustomize/rendered-entity.d.ts.map +1 -0
  42. package/dist/kustomize/root.d.ts +19 -0
  43. package/dist/kustomize/root.d.ts.map +1 -0
  44. package/dist/lint/audit-catalog.d.ts.map +1 -1
  45. package/dist/lint/post-synth/argo-helpers.d.ts +2 -0
  46. package/dist/lint/post-synth/argo-helpers.d.ts.map +1 -1
  47. package/dist/lint/post-synth/flux002.d.ts +14 -0
  48. package/dist/lint/post-synth/flux002.d.ts.map +1 -0
  49. package/dist/lint/post-synth/flux003.d.ts +17 -0
  50. package/dist/lint/post-synth/flux003.d.ts.map +1 -0
  51. package/dist/lint/post-synth/index.d.ts.map +1 -1
  52. package/dist/lint/rules/flux-source-ref-pin.d.ts +16 -0
  53. package/dist/lint/rules/flux-source-ref-pin.d.ts.map +1 -0
  54. package/dist/manifest.json +1 -1
  55. package/dist/meta.json +92 -16
  56. package/dist/okf/index.md +256 -0
  57. package/dist/okf/rules/ARGO001.md +16 -0
  58. package/dist/okf/rules/ARGO002.md +16 -0
  59. package/dist/okf/rules/ARGO003.md +15 -0
  60. package/dist/okf/rules/ARGO004.md +17 -0
  61. package/dist/okf/rules/ARGO005.md +15 -0
  62. package/dist/okf/rules/FLUX001.md +17 -0
  63. package/dist/okf/rules/FLUX002.md +19 -0
  64. package/dist/okf/rules/FLUX003.md +15 -0
  65. package/dist/okf/rules/WK8001.md +16 -0
  66. package/dist/okf/rules/WK8002.md +20 -0
  67. package/dist/okf/rules/WK8003.md +19 -0
  68. package/dist/okf/rules/WK8005.md +11 -0
  69. package/dist/okf/rules/WK8006.md +11 -0
  70. package/dist/okf/rules/WK8041.md +11 -0
  71. package/dist/okf/rules/WK8042.md +11 -0
  72. package/dist/okf/rules/WK8101.md +11 -0
  73. package/dist/okf/rules/WK8102.md +11 -0
  74. package/dist/okf/rules/WK8103.md +11 -0
  75. package/dist/okf/rules/WK8104.md +15 -0
  76. package/dist/okf/rules/WK8105.md +11 -0
  77. package/dist/okf/rules/WK8201.md +11 -0
  78. package/dist/okf/rules/WK8202.md +11 -0
  79. package/dist/okf/rules/WK8203.md +11 -0
  80. package/dist/okf/rules/WK8204.md +11 -0
  81. package/dist/okf/rules/WK8205.md +11 -0
  82. package/dist/okf/rules/WK8207.md +11 -0
  83. package/dist/okf/rules/WK8208.md +11 -0
  84. package/dist/okf/rules/WK8209.md +11 -0
  85. package/dist/okf/rules/WK8301.md +16 -0
  86. package/dist/okf/rules/WK8302.md +11 -0
  87. package/dist/okf/rules/WK8303.md +15 -0
  88. package/dist/okf/rules/WK8304.md +15 -0
  89. package/dist/okf/rules/WK8305.md +15 -0
  90. package/dist/okf/rules/WK8306.md +11 -0
  91. package/dist/okf/rules/WK8401.md +15 -0
  92. package/dist/okf/rules/WK8402.md +15 -0
  93. package/dist/okf/rules/WK8403.md +15 -0
  94. package/dist/okf/types/APIGroup.md +16 -0
  95. package/dist/okf/types/APIGroupList.md +13 -0
  96. package/dist/okf/types/APIResourceList.md +14 -0
  97. package/dist/okf/types/APIService.md +14 -0
  98. package/dist/okf/types/APIServiceList.md +14 -0
  99. package/dist/okf/types/APIVersions.md +14 -0
  100. package/dist/okf/types/Addon.md +14 -0
  101. package/dist/okf/types/Alert.md +14 -0
  102. package/dist/okf/types/AppProject.md +19 -0
  103. package/dist/okf/types/Application.md +23 -0
  104. package/dist/okf/types/ApplicationSet.md +18 -0
  105. package/dist/okf/types/Backup.md +14 -0
  106. package/dist/okf/types/BatchJob.md +14 -0
  107. package/dist/okf/types/Binding.md +14 -0
  108. package/dist/okf/types/Bucket.md +18 -0
  109. package/dist/okf/types/CM.md +16 -0
  110. package/dist/okf/types/CSIDriver.md +14 -0
  111. package/dist/okf/types/CSIDriverList.md +14 -0
  112. package/dist/okf/types/CSINode.md +14 -0
  113. package/dist/okf/types/CSINodeList.md +14 -0
  114. package/dist/okf/types/CSIStorageCapacity.md +17 -0
  115. package/dist/okf/types/CSIStorageCapacityList.md +14 -0
  116. package/dist/okf/types/Certificate.md +18 -0
  117. package/dist/okf/types/CertificateRequest.md +14 -0
  118. package/dist/okf/types/CertificateSigningRequest.md +14 -0
  119. package/dist/okf/types/CertificateSigningRequestList.md +14 -0
  120. package/dist/okf/types/Challenge.md +14 -0
  121. package/dist/okf/types/Cluster.md +14 -0
  122. package/dist/okf/types/ClusterIssuer.md +14 -0
  123. package/dist/okf/types/ClusterRole.md +15 -0
  124. package/dist/okf/types/ClusterRoleBinding.md +15 -0
  125. package/dist/okf/types/ClusterRoleBindingList.md +14 -0
  126. package/dist/okf/types/ClusterRoleList.md +14 -0
  127. package/dist/okf/types/ClusterTrustBundle.md +14 -0
  128. package/dist/okf/types/ClusterTrustBundleList.md +14 -0
  129. package/dist/okf/types/ComponentStatus.md +14 -0
  130. package/dist/okf/types/ComponentStatusList.md +14 -0
  131. package/dist/okf/types/ConfigMapList.md +14 -0
  132. package/dist/okf/types/ControllerRevision.md +15 -0
  133. package/dist/okf/types/ControllerRevisionList.md +14 -0
  134. package/dist/okf/types/CoreEventList.md +14 -0
  135. package/dist/okf/types/CrdbCluster.md +14 -0
  136. package/dist/okf/types/CronJob.md +20 -0
  137. package/dist/okf/types/CronJobList.md +14 -0
  138. package/dist/okf/types/CustomResourceDefinition.md +14 -0
  139. package/dist/okf/types/CustomResourceDefinitionList.md +14 -0
  140. package/dist/okf/types/DaemonSet.md +19 -0
  141. package/dist/okf/types/DaemonSetList.md +14 -0
  142. package/dist/okf/types/DeleteOptions.md +18 -0
  143. package/dist/okf/types/Deploy.md +14 -0
  144. package/dist/okf/types/DeploymentList.md +14 -0
  145. package/dist/okf/types/DeviceClass.md +14 -0
  146. package/dist/okf/types/DeviceClassList.md +14 -0
  147. package/dist/okf/types/DeviceTaintRule.md +14 -0
  148. package/dist/okf/types/DeviceTaintRuleList.md +14 -0
  149. package/dist/okf/types/EndpointSlice.md +16 -0
  150. package/dist/okf/types/EndpointSliceList.md +14 -0
  151. package/dist/okf/types/Endpoints.md +14 -0
  152. package/dist/okf/types/EndpointsList.md +14 -0
  153. package/dist/okf/types/Event.md +27 -0
  154. package/dist/okf/types/EventsEvent.md +27 -0
  155. package/dist/okf/types/EventsEventList.md +14 -0
  156. package/dist/okf/types/Eviction.md +14 -0
  157. package/dist/okf/types/FlowSchema.md +14 -0
  158. package/dist/okf/types/FlowSchemaList.md +14 -0
  159. package/dist/okf/types/FluxHelmChart.md +14 -0
  160. package/dist/okf/types/FluxInstance.md +14 -0
  161. package/dist/okf/types/FluxReport.md +14 -0
  162. package/dist/okf/types/GRPCRoute.md +14 -0
  163. package/dist/okf/types/Gateway.md +14 -0
  164. package/dist/okf/types/GatewayClass.md +14 -0
  165. package/dist/okf/types/GitRepository.md +19 -0
  166. package/dist/okf/types/HTTPRoute.md +14 -0
  167. package/dist/okf/types/HelmChartConfig.md +14 -0
  168. package/dist/okf/types/HelmRelease.md +14 -0
  169. package/dist/okf/types/HelmRepository.md +14 -0
  170. package/dist/okf/types/HorizontalPodAutoscaler.md +14 -0
  171. package/dist/okf/types/HorizontalPodAutoscalerList.md +14 -0
  172. package/dist/okf/types/IPAddress.md +14 -0
  173. package/dist/okf/types/IPAddressList.md +14 -0
  174. package/dist/okf/types/ImagePolicy.md +14 -0
  175. package/dist/okf/types/ImageRepository.md +14 -0
  176. package/dist/okf/types/ImageUpdateAutomation.md +14 -0
  177. package/dist/okf/types/InfisicalDynamicSecret.md +14 -0
  178. package/dist/okf/types/InfisicalPushSecret.md +14 -0
  179. package/dist/okf/types/InfisicalSecret.md +14 -0
  180. package/dist/okf/types/Ing.md +14 -0
  181. package/dist/okf/types/IngressClass.md +14 -0
  182. package/dist/okf/types/IngressClassList.md +14 -0
  183. package/dist/okf/types/IngressList.md +14 -0
  184. package/dist/okf/types/IngressRoute.md +14 -0
  185. package/dist/okf/types/IngressRouteTCP.md +14 -0
  186. package/dist/okf/types/IngressRouteUDP.md +14 -0
  187. package/dist/okf/types/Issuer.md +14 -0
  188. package/dist/okf/types/JobList.md +14 -0
  189. package/dist/okf/types/K3sHelmChart.md +14 -0
  190. package/dist/okf/types/Kustomization.md +20 -0
  191. package/dist/okf/types/Lease.md +14 -0
  192. package/dist/okf/types/LeaseCandidate.md +14 -0
  193. package/dist/okf/types/LeaseCandidateList.md +14 -0
  194. package/dist/okf/types/LeaseList.md +14 -0
  195. package/dist/okf/types/LimitRange.md +14 -0
  196. package/dist/okf/types/LimitRangeList.md +14 -0
  197. package/dist/okf/types/LocalSubjectAccessReview.md +14 -0
  198. package/dist/okf/types/MicroVM.md +14 -0
  199. package/dist/okf/types/MicroVMClass.md +14 -0
  200. package/dist/okf/types/MicroVMImage.md +14 -0
  201. package/dist/okf/types/MicroVMNetwork.md +14 -0
  202. package/dist/okf/types/MicroVMReplicaSet.md +14 -0
  203. package/dist/okf/types/Middleware.md +14 -0
  204. package/dist/okf/types/MiddlewareTCP.md +14 -0
  205. package/dist/okf/types/MutatingAdmissionPolicy.md +14 -0
  206. package/dist/okf/types/MutatingAdmissionPolicyBinding.md +14 -0
  207. package/dist/okf/types/MutatingAdmissionPolicyBindingList.md +14 -0
  208. package/dist/okf/types/MutatingAdmissionPolicyList.md +14 -0
  209. package/dist/okf/types/MutatingWebhookConfiguration.md +14 -0
  210. package/dist/okf/types/MutatingWebhookConfigurationList.md +14 -0
  211. package/dist/okf/types/Namespace.md +19 -0
  212. package/dist/okf/types/NamespaceList.md +14 -0
  213. package/dist/okf/types/NetPol.md +14 -0
  214. package/dist/okf/types/NetworkPolicyList.md +14 -0
  215. package/dist/okf/types/Node.md +22 -0
  216. package/dist/okf/types/NodeList.md +14 -0
  217. package/dist/okf/types/OCIRepository.md +18 -0
  218. package/dist/okf/types/ObjectStore.md +14 -0
  219. package/dist/okf/types/Order.md +14 -0
  220. package/dist/okf/types/PDB.md +18 -0
  221. package/dist/okf/types/PersistentVolume.md +14 -0
  222. package/dist/okf/types/PersistentVolumeClaim.md +14 -0
  223. package/dist/okf/types/PersistentVolumeClaimList.md +14 -0
  224. package/dist/okf/types/PersistentVolumeList.md +14 -0
  225. package/dist/okf/types/Pod.md +18 -0
  226. package/dist/okf/types/PodCertificateRequest.md +14 -0
  227. package/dist/okf/types/PodCertificateRequestList.md +14 -0
  228. package/dist/okf/types/PodDisruptionBudgetList.md +14 -0
  229. package/dist/okf/types/PodList.md +14 -0
  230. package/dist/okf/types/PodTemplate.md +14 -0
  231. package/dist/okf/types/PodTemplateList.md +14 -0
  232. package/dist/okf/types/Pooler.md +14 -0
  233. package/dist/okf/types/PriorityClass.md +17 -0
  234. package/dist/okf/types/PriorityClassList.md +14 -0
  235. package/dist/okf/types/PriorityLevelConfiguration.md +14 -0
  236. package/dist/okf/types/PriorityLevelConfigurationList.md +14 -0
  237. package/dist/okf/types/PrometheusRule.md +14 -0
  238. package/dist/okf/types/Provider.md +14 -0
  239. package/dist/okf/types/RayCluster.md +20 -0
  240. package/dist/okf/types/RayJob.md +14 -0
  241. package/dist/okf/types/RayService.md +14 -0
  242. package/dist/okf/types/Receiver.md +14 -0
  243. package/dist/okf/types/ReferenceGrant.md +14 -0
  244. package/dist/okf/types/ReplicaSet.md +19 -0
  245. package/dist/okf/types/ReplicaSetList.md +14 -0
  246. package/dist/okf/types/ReplicationController.md +14 -0
  247. package/dist/okf/types/ReplicationControllerList.md +14 -0
  248. package/dist/okf/types/ResourceClaim.md +14 -0
  249. package/dist/okf/types/ResourceClaimList.md +14 -0
  250. package/dist/okf/types/ResourceClaimTemplate.md +14 -0
  251. package/dist/okf/types/ResourceClaimTemplateList.md +14 -0
  252. package/dist/okf/types/ResourceQuota.md +14 -0
  253. package/dist/okf/types/ResourceQuotaList.md +14 -0
  254. package/dist/okf/types/ResourceSet.md +14 -0
  255. package/dist/okf/types/ResourceSetInputProvider.md +14 -0
  256. package/dist/okf/types/ResourceSlice.md +14 -0
  257. package/dist/okf/types/ResourceSliceList.md +14 -0
  258. package/dist/okf/types/Role.md +14 -0
  259. package/dist/okf/types/RoleBinding.md +15 -0
  260. package/dist/okf/types/RoleBindingList.md +14 -0
  261. package/dist/okf/types/RoleList.md +14 -0
  262. package/dist/okf/types/RuntimeClass.md +16 -0
  263. package/dist/okf/types/RuntimeClassList.md +14 -0
  264. package/dist/okf/types/SA.md +16 -0
  265. package/dist/okf/types/Scale.md +14 -0
  266. package/dist/okf/types/ScheduledBackup.md +14 -0
  267. package/dist/okf/types/Sec.md +17 -0
  268. package/dist/okf/types/SecretList.md +14 -0
  269. package/dist/okf/types/SelfSubjectAccessReview.md +14 -0
  270. package/dist/okf/types/SelfSubjectReview.md +13 -0
  271. package/dist/okf/types/SelfSubjectRulesReview.md +14 -0
  272. package/dist/okf/types/ServersTransport.md +14 -0
  273. package/dist/okf/types/ServersTransportTCP.md +14 -0
  274. package/dist/okf/types/Service.md +20 -0
  275. package/dist/okf/types/ServiceAccountList.md +14 -0
  276. package/dist/okf/types/ServiceCIDR.md +14 -0
  277. package/dist/okf/types/ServiceCIDRList.md +14 -0
  278. package/dist/okf/types/ServiceList.md +14 -0
  279. package/dist/okf/types/ServiceMonitor.md +14 -0
  280. package/dist/okf/types/StatefulSet.md +19 -0
  281. package/dist/okf/types/StatefulSetList.md +14 -0
  282. package/dist/okf/types/Status.md +17 -0
  283. package/dist/okf/types/StorageClass.md +20 -0
  284. package/dist/okf/types/StorageClassList.md +14 -0
  285. package/dist/okf/types/StorageVersionMigration.md +14 -0
  286. package/dist/okf/types/StorageVersionMigrationList.md +14 -0
  287. package/dist/okf/types/SubjectAccessReview.md +14 -0
  288. package/dist/okf/types/TLSOption.md +14 -0
  289. package/dist/okf/types/TLSStore.md +14 -0
  290. package/dist/okf/types/TokenRequest.md +14 -0
  291. package/dist/okf/types/TokenReview.md +14 -0
  292. package/dist/okf/types/TraefikService.md +14 -0
  293. package/dist/okf/types/ValidatingAdmissionPolicy.md +14 -0
  294. package/dist/okf/types/ValidatingAdmissionPolicyBinding.md +14 -0
  295. package/dist/okf/types/ValidatingAdmissionPolicyBindingList.md +14 -0
  296. package/dist/okf/types/ValidatingAdmissionPolicyList.md +14 -0
  297. package/dist/okf/types/ValidatingWebhookConfiguration.md +14 -0
  298. package/dist/okf/types/ValidatingWebhookConfigurationList.md +14 -0
  299. package/dist/okf/types/VolumeAttachment.md +14 -0
  300. package/dist/okf/types/VolumeAttachmentList.md +14 -0
  301. package/dist/okf/types/VolumeAttributesClass.md +15 -0
  302. package/dist/okf/types/VolumeAttributesClassList.md +14 -0
  303. package/dist/okf/types/WatchEvent.md +14 -0
  304. package/dist/plugin.d.ts.map +1 -1
  305. package/dist/rules/argo-helpers.ts +2 -0
  306. package/dist/rules/flux-source-ref-pin.ts +52 -0
  307. package/dist/rules/flux002.ts +77 -0
  308. package/dist/rules/flux003.ts +75 -0
  309. package/dist/serializer.d.ts.map +1 -1
  310. package/dist/skills/chant-k8s-flux.md +162 -0
  311. package/dist/types/index.d.ts +41 -3
  312. package/dist/validate.d.ts.map +1 -1
  313. package/package.json +3 -3
  314. package/src/api/classify.test.ts +20 -0
  315. package/src/api/classify.ts +8 -1
  316. package/src/api/connect.ts +10 -0
  317. package/src/codegen/docs.ts +11 -0
  318. package/src/components/argo-app.test.ts +137 -0
  319. package/src/components/argo-app.ts +163 -0
  320. package/src/components/builders.ts +8 -0
  321. package/src/components/capability-plugin.ts +11 -1
  322. package/src/components/flux-reconcile.test.ts +145 -0
  323. package/src/components/flux-reconcile.ts +159 -0
  324. package/src/components/index.ts +13 -0
  325. package/src/components/kustomize-apply.ts +12 -41
  326. package/src/composites/composites.test.ts +122 -0
  327. package/src/composites/flux-app.ts +274 -0
  328. package/src/composites/index.ts +9 -0
  329. package/src/config-augmentation.test.ts +72 -0
  330. package/src/config-schema.ts +5 -0
  331. package/src/config.ts +33 -5
  332. package/src/crd/crd-sources.ts +37 -0
  333. package/src/crd/parser.ts +6 -0
  334. package/src/deep-observe-hooks.ts +60 -0
  335. package/src/deep-observe.test.ts +114 -0
  336. package/src/describe-resources.test.ts +215 -6
  337. package/src/describe-resources.ts +197 -118
  338. package/src/generated/index.d.ts +41 -3
  339. package/src/generated/index.ts +15 -4
  340. package/src/generated/lexicon-k8s.json +92 -16
  341. package/src/generated/operations.json +48 -0
  342. package/src/index.ts +4 -0
  343. package/src/kube/connect.ts +5 -4
  344. package/src/kube/get.ts +4 -4
  345. package/src/kustomize/render.ts +84 -0
  346. package/src/kustomize/rendered-entity.ts +72 -0
  347. package/src/kustomize/root.test.ts +268 -0
  348. package/src/kustomize/root.ts +109 -0
  349. package/src/lint/audit-catalog.ts +2 -0
  350. package/src/lint/post-synth/argo-helpers.ts +2 -0
  351. package/src/lint/post-synth/flux002.ts +77 -0
  352. package/src/lint/post-synth/flux003.ts +75 -0
  353. package/src/lint/post-synth/index.ts +4 -0
  354. package/src/lint/post-synth/post-synth.test.ts +132 -0
  355. package/src/lint/rules/flux-source-ref-pin.ts +52 -0
  356. package/src/lint/rules/rules.test.ts +51 -0
  357. package/src/plugin.test.ts +2 -0
  358. package/src/plugin.ts +47 -0
  359. package/src/serializer.ts +34 -4
  360. package/src/skills/chant-k8s-flux.md +162 -0
  361. package/src/testdata/kustomize-root/base/deployment.yaml +17 -0
  362. package/src/testdata/kustomize-root/base/kustomization.yaml +3 -0
  363. package/src/testdata/kustomize-root/base/service.yaml +9 -0
  364. package/src/testdata/kustomize-root/overlays/prod/kustomization.yaml +6 -0
  365. package/src/validate.ts +4 -0
@@ -277,6 +277,40 @@ const FLUX_OPERATOR_INSTALL = `https://github.com/controlplaneio-fluxcd/flux-ope
277
277
  const KUBEMICROVM_CHART = "oci://ghcr.io/codriverlabs/helm/kube-microvm-operator";
278
278
  const KUBEMICROVM_VERSION = "1.0.11";
279
279
 
280
+ /**
281
+ * k3s bundled-controller CRDs — helm.cattle.io/v1 + k3s.cattle.io/v1
282
+ *
283
+ * k3s's manifest auto-deploy machinery speaks these CRDs: drop a HelmChart
284
+ * into /var/lib/rancher/k3s/server/manifests and the embedded helm-controller
285
+ * installs the chart; every auto-deployed manifest is tracked by an Addon.
286
+ * Typed here for the same reason the Flux toolkit kinds are — they are what a
287
+ * k3s estate's GitOps surface is written in.
288
+ *
289
+ * Produces (both groups map to the `K3s` namespace — see
290
+ * GROUP_NAMESPACE_OVERRIDES in crd/parser.ts):
291
+ * K8s::K3s::HelmChart → apiVersion: helm.cattle.io/v1, kind: HelmChart
292
+ * K8s::K3s::HelmChartConfig → apiVersion: helm.cattle.io/v1, kind: HelmChartConfig
293
+ * K8s::K3s::Addon → apiVersion: k3s.cattle.io/v1, kind: Addon
294
+ *
295
+ * The pin is k3s v1.36.3+k3s1 (the release the k3s lexicon pins), but the k3s
296
+ * repo itself publishes no CRD YAMLs — its manifests/ directory carries only
297
+ * the bundled charts, and the API types live in dependency repos. The URLs
298
+ * below point at the exact versions that release vendors in its go.mod:
299
+ * github.com/k3s-io/helm-controller v0.17.7 (helm.cattle.io CRDs)
300
+ * github.com/k3s-io/api v0.1.4 (k3s.cattle.io CRDs)
301
+ * When bumping, re-read go.mod at the new k3s tag and move both pins together.
302
+ *
303
+ * k3s-io/api also ships an ETCDSnapshotFile CRD — deliberately left out. It is
304
+ * status-only controller bookkeeping (the server writes them to describe
305
+ * snapshots it took); nothing an author would declare.
306
+ *
307
+ * Controller install: none — both controllers are embedded in the k3s binary.
308
+ */
309
+ const K3S_HELM_CONTROLLER_VERSION = "v0.17.7"; // vendored by k3s v1.36.3+k3s1
310
+ const K3S_API_VERSION = "v0.1.4"; // vendored by k3s v1.36.3+k3s1
311
+ const K3S_HELM_CONTROLLER_CRD_BASE = `https://raw.githubusercontent.com/k3s-io/helm-controller/${K3S_HELM_CONTROLLER_VERSION}/pkg/crds/yaml/generated`;
312
+ const K3S_API_CRD_BASE = `https://raw.githubusercontent.com/k3s-io/api/${K3S_API_VERSION}/pkg/crds/yaml/generated`;
313
+
280
314
  export const CRD_SOURCES: CRDSource[] = [
281
315
  { type: "url", url: `${KUBERAY_CRD_BASE}/ray.io_rayclusters.yaml` },
282
316
  { type: "url", url: `${KUBERAY_CRD_BASE}/ray.io_rayjobs.yaml` },
@@ -335,6 +369,9 @@ export const CRD_SOURCES: CRDSource[] = [
335
369
  url: FLUX_OPERATOR_INSTALL,
336
370
  kinds: ["FluxInstance", "FluxReport", "ResourceSet", "ResourceSetInputProvider"],
337
371
  },
372
+ { type: "url", url: `${K3S_HELM_CONTROLLER_CRD_BASE}/helm.cattle.io_helmcharts.yaml` },
373
+ { type: "url", url: `${K3S_HELM_CONTROLLER_CRD_BASE}/helm.cattle.io_helmchartconfigs.yaml` },
374
+ { type: "url", url: `${K3S_API_CRD_BASE}/k3s.cattle.io_addons.yaml` },
338
375
  {
339
376
  type: "helm",
340
377
  chart: KUBEMICROVM_CHART,
package/src/crd/parser.ts CHANGED
@@ -52,6 +52,12 @@ const GROUP_NAMESPACE_OVERRIDES: Record<string, string> = {
52
52
  // Not `Secrets`: `K8s::Secrets::InfisicalSecret` reads like a core Secret,
53
53
  // and `K8s::Core::Secret` is right there to be confused with.
54
54
  "secrets.infisical.com": "Infisical",
55
+ // k3s's bundled controllers ship under two groups but are one thing to an
56
+ // author — the k3s auto-deploy surface. The first-segment rule would give
57
+ // `K8s::Helm::HelmChart`, which reads like it belongs to the helm lexicon,
58
+ // and would split HelmChart from the Addon that tracks its deployment.
59
+ "helm.cattle.io": "K3s",
60
+ "k3s.cattle.io": "K3s",
55
61
  };
56
62
 
57
63
  /**
@@ -44,6 +44,7 @@
44
44
  import { createRequire } from "module";
45
45
  import type { DeepArrayElement, DeepNode, DeepNormalizationHooks } from "@intentius/chant/lexicon";
46
46
  import { K8S_OBJECT_ENVELOPE_PRUNE_PATTERNS, k8sListMapOrderKey } from "@intentius/chant/managed-fields";
47
+ import { LABEL_OWNERSHIP_KEYS, OWNERSHIP_MANAGED_BY_VALUE } from "@intentius/chant/ownership";
47
48
 
48
49
  // This module ships as ESM (tsx strips types from src/ directly), where a bare
49
50
  // `require` is not defined — the same reason serializer.ts and the LSP modules
@@ -86,13 +87,64 @@ export const K8S_SERVICE_DEFAULTS: Record<string, Record<string, unknown>> = {
86
87
  "spec.template.spec.restartPolicy": "Always",
87
88
  "spec.template.spec.terminationGracePeriodSeconds": 30,
88
89
  "spec.template.spec.schedulerName": "default-scheduler",
90
+ // Widened on the CC canonical estate (#1214): a container declaring an
91
+ // httpGet probe, a named port, or nothing about termination gets exactly
92
+ // these from the API server. SSA attributes a server-defaulted field to no
93
+ // manager, so the managed-fields prune deliberately leaves them diffable
94
+ // (see ./deep-observe.ts) — this table is where they subtract.
95
+ "spec.template.spec.containers[].livenessProbe.failureThreshold": 3,
96
+ "spec.template.spec.containers[].livenessProbe.periodSeconds": 10,
97
+ "spec.template.spec.containers[].livenessProbe.successThreshold": 1,
98
+ "spec.template.spec.containers[].livenessProbe.timeoutSeconds": 1,
99
+ "spec.template.spec.containers[].livenessProbe.httpGet.scheme": "HTTP",
100
+ "spec.template.spec.containers[].readinessProbe.failureThreshold": 3,
101
+ "spec.template.spec.containers[].readinessProbe.periodSeconds": 10,
102
+ "spec.template.spec.containers[].readinessProbe.successThreshold": 1,
103
+ "spec.template.spec.containers[].readinessProbe.timeoutSeconds": 1,
104
+ "spec.template.spec.containers[].readinessProbe.httpGet.scheme": "HTTP",
105
+ "spec.template.spec.containers[].ports[].protocol": "TCP",
106
+ "spec.template.spec.containers[].terminationMessagePath": "/dev/termination-log",
107
+ "spec.template.spec.containers[].terminationMessagePolicy": "File",
108
+ "spec.template.spec.securityContext": {},
89
109
  },
90
110
  "K8s::Core::Service": {
91
111
  "spec.sessionAffinity": "None",
92
112
  "spec.type": "ClusterIP",
113
+ "spec.internalTrafficPolicy": "Cluster",
114
+ "spec.ipFamilyPolicy": "SingleStack",
115
+ "spec.ports[].protocol": "TCP",
93
116
  },
94
117
  };
95
118
 
119
+ /**
120
+ * Server-ASSIGNED fields — populated by the API server with a value that
121
+ * varies per object (an allocated ClusterIP, the resolved IP family list), so
122
+ * unlike {@link K8S_SERVICE_DEFAULTS} there is no fixed value to compare
123
+ * against. Counterpart-gated like the defaults table: a declared `clusterIP`
124
+ * is still compared, only the purely server-filled appearance is noise.
125
+ */
126
+ export const K8S_SERVER_ASSIGNED_PATTERNS: Record<string, ReadonlySet<string>> = {
127
+ "K8s::Core::Service": new Set(["spec.clusterIP", "spec.clusterIPs", "spec.ipFamilies"]),
128
+ };
129
+
130
+ /**
131
+ * chant's own ownership marker, as label paths (#1214 — the k8s edition of
132
+ * azure's #1213 rule). The build stamps `app.kubernetes.io/managed-by` +
133
+ * `chant.intentius.io/{stack,env}` into every manifest it emits, so a managed
134
+ * object always carries them live while the declared *source* the diff
135
+ * compares does not — chant reading its own signature back as drift. The
136
+ * managed-fields prune cannot subtract them: they are in the applied config,
137
+ * so chant's field manager owns them, and chant-owned is always diffable.
138
+ * Counterpart-gated at the call site; managed-by is additionally gated on the
139
+ * value `"chant"`, so `managed-by: helm` appearing out of band still surfaces.
140
+ */
141
+ const K8S_OWNERSHIP_LABEL_PATTERNS: ReadonlySet<string> = new Set([
142
+ `metadata.labels.${LABEL_OWNERSHIP_KEYS.stack}`,
143
+ `metadata.labels.${LABEL_OWNERSHIP_KEYS.env}`,
144
+ ]);
145
+
146
+ const K8S_MANAGED_BY_LABEL_PATTERN = `metadata.labels.${LABEL_OWNERSHIP_KEYS.managedBy}`;
147
+
96
148
  /** Stable JSON with sorted keys — the fallback ordering key for a set-like array without a natural identity field. */
97
149
  function canonicalJson(value: unknown): string {
98
150
  return (
@@ -206,6 +258,14 @@ export const k8sDeepNormalizationHooks: DeepNormalizationHooks = {
206
258
  if (K8S_OBJECT_ENVELOPE_PRUNE_PATTERNS.has(node.pattern)) return true;
207
259
 
208
260
  if (node.side !== "live" || node.counterpart !== "absent") return false;
261
+
262
+ // chant's own ownership marker is not drift (see the sets' docs).
263
+ if (K8S_OWNERSHIP_LABEL_PATTERNS.has(node.pattern)) return true;
264
+ if (node.pattern === K8S_MANAGED_BY_LABEL_PATTERN && node.value === OWNERSHIP_MANAGED_BY_VALUE) return true;
265
+
266
+ // A server-assigned value nobody declared is allocation, not drift.
267
+ if (K8S_SERVER_ASSIGNED_PATTERNS[node.entityType]?.has(node.pattern)) return true;
268
+
209
269
  const defaults = K8S_SERVICE_DEFAULTS[node.entityType];
210
270
  if (!defaults || !Object.prototype.hasOwnProperty.call(defaults, node.pattern)) return false;
211
271
  return canonicalJson(defaults[node.pattern]) === canonicalJson(node.value);
@@ -96,6 +96,120 @@ describe("k8sDeepNormalizationHooks — the static rules", () => {
96
96
  expect(declaredExplicit).toEqual({ spec: { strategy: strategyDefault, replicas: 3 } });
97
97
  });
98
98
 
99
+ test("chant's own stamped labels are not drift; a foreign managed-by and a user label still are", () => {
100
+ const live = {
101
+ metadata: {
102
+ name: "web",
103
+ labels: {
104
+ "app.kubernetes.io/managed-by": "chant",
105
+ "chant.intentius.io/stack": "cc-azure-canonical",
106
+ "chant.intentius.io/env": "local",
107
+ team: "platform",
108
+ },
109
+ },
110
+ };
111
+ const out = normalizeDeepProperties(live, {
112
+ entityType: "K8s::Apps::Deployment",
113
+ side: "live",
114
+ hooks: k8sDeepNormalizationHooks,
115
+ counterpartPaths: new Set(["metadata", "metadata.name"]),
116
+ });
117
+ expect(out).toEqual({ metadata: { name: "web", labels: { team: "platform" } } });
118
+
119
+ const foreign = normalizeDeepProperties(
120
+ { metadata: { name: "web", labels: { "app.kubernetes.io/managed-by": "helm" } } },
121
+ {
122
+ entityType: "K8s::Apps::Deployment",
123
+ side: "live",
124
+ hooks: k8sDeepNormalizationHooks,
125
+ counterpartPaths: new Set(["metadata", "metadata.name"]),
126
+ },
127
+ );
128
+ expect(foreign).toEqual({ metadata: { name: "web", labels: { "app.kubernetes.io/managed-by": "helm" } } });
129
+ });
130
+
131
+ test("a server-assigned ClusterIP nobody declared subtracts; a declared one is compared", () => {
132
+ const undeclared = normalizeDeepProperties(
133
+ { spec: { clusterIP: "10.43.201.161", clusterIPs: ["10.43.201.161"], ipFamilies: ["IPv4"], selector: { app: "web" } } },
134
+ {
135
+ entityType: "K8s::Core::Service",
136
+ side: "live",
137
+ hooks: k8sDeepNormalizationHooks,
138
+ counterpartPaths: new Set(["spec", "spec.selector", "spec.selector.app"]),
139
+ },
140
+ );
141
+ expect(undeclared).toEqual({ spec: { selector: { app: "web" } } });
142
+
143
+ const declared = normalizeDeepProperties(
144
+ { spec: { clusterIP: "10.43.0.10" } },
145
+ {
146
+ entityType: "K8s::Core::Service",
147
+ side: "live",
148
+ hooks: k8sDeepNormalizationHooks,
149
+ counterpartPaths: new Set(["spec", "spec.clusterIP"]),
150
+ },
151
+ );
152
+ expect(declared).toEqual({ spec: { clusterIP: "10.43.0.10" } });
153
+ });
154
+
155
+ test("probe and termination defaults subtract inside a declared container", () => {
156
+ const out = normalizeDeepProperties(
157
+ {
158
+ spec: {
159
+ template: {
160
+ spec: {
161
+ securityContext: {},
162
+ containers: [
163
+ {
164
+ name: "api",
165
+ image: "example:1",
166
+ terminationMessagePath: "/dev/termination-log",
167
+ terminationMessagePolicy: "File",
168
+ livenessProbe: { httpGet: { path: "/", port: 8080, scheme: "HTTP" }, failureThreshold: 3, periodSeconds: 10, successThreshold: 1, timeoutSeconds: 1 },
169
+ ports: [{ containerPort: 8080, name: "http", protocol: "TCP" }],
170
+ },
171
+ ],
172
+ },
173
+ },
174
+ },
175
+ },
176
+ {
177
+ entityType: "K8s::Apps::Deployment",
178
+ side: "live",
179
+ hooks: k8sDeepNormalizationHooks,
180
+ counterpartPaths: new Set([
181
+ "spec", "spec.template", "spec.template.spec",
182
+ "spec.template.spec.containers", "spec.template.spec.containers[]",
183
+ "spec.template.spec.containers[].name", "spec.template.spec.containers[].image",
184
+ "spec.template.spec.containers[].livenessProbe",
185
+ "spec.template.spec.containers[].livenessProbe.httpGet",
186
+ "spec.template.spec.containers[].livenessProbe.httpGet.path",
187
+ "spec.template.spec.containers[].livenessProbe.httpGet.port",
188
+ "spec.template.spec.containers[].ports",
189
+ "spec.template.spec.containers[].ports[]",
190
+ "spec.template.spec.containers[].ports[].containerPort",
191
+ "spec.template.spec.containers[].ports[].name",
192
+ ]),
193
+ },
194
+ );
195
+ expect(out).toEqual({
196
+ spec: {
197
+ template: {
198
+ spec: {
199
+ containers: [
200
+ {
201
+ name: "api",
202
+ image: "example:1",
203
+ livenessProbe: { httpGet: { path: "/", port: 8080 } },
204
+ ports: [{ containerPort: 8080, name: "http" }],
205
+ },
206
+ ],
207
+ },
208
+ },
209
+ },
210
+ });
211
+ });
212
+
99
213
  test("a one-sided pass never subtracts defaults — the reader has no declared tree yet", () => {
100
214
  const out = normalizeDeepProperties(
101
215
  { spec: { strategy: { type: "RollingUpdate", rollingUpdate: { maxSurge: "25%", maxUnavailable: "25%" } } } },
@@ -465,6 +465,44 @@ describe("k8s describeResources", () => {
465
465
  warnSpy.mockRestore();
466
466
  });
467
467
 
468
+ test("a failed read names the bound context it read (#1488)", async () => {
469
+ loadChantConfigMock.mockResolvedValue({ config: { k8s: { profiles: { prod: { context: "prod-eks" } } } } });
470
+ const cluster = fakeCluster({
471
+ kubeconfig: twoContexts,
472
+ respond: (req) =>
473
+ req.path.endsWith("/deployments/web")
474
+ ? { status: 500, body: statusBody(500, "InternalError", "etcdserver: leader changed") }
475
+ : undefined,
476
+ });
477
+
478
+ const result = await describeResources(
479
+ { environment: "prod", buildOutput: "", entityNames: ["web"], entities: entities() },
480
+ (o) => defaultK8sConnector({ ...o, client: { kubeconfig: twoContexts, requestLayer: cluster.layer } }),
481
+ );
482
+
483
+ expect(result.unobserved?.web?.reason).toBe("read-failed");
484
+ expect(result.unobserved?.web?.detail).toContain('context "prod-eks" (bound by k8s.profiles.prod.context)');
485
+ });
486
+
487
+ test("an ambient read failure names the context that was read and the missing binding (#1488)", async () => {
488
+ loadChantConfigMock.mockResolvedValue({ config: {} });
489
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
490
+ const cluster = fakeCluster({
491
+ kubeconfig: twoContexts,
492
+ respond: (req) =>
493
+ req.path.endsWith("/deployments/web") ? { status: 500, body: statusBody(500, "InternalError", "nope") } : undefined,
494
+ });
495
+
496
+ const result = await describeResources(
497
+ { environment: "prod", buildOutput: "", entityNames: ["web"], entities: entities() },
498
+ (o) => defaultK8sConnector({ ...o, client: { kubeconfig: twoContexts, requestLayer: cluster.layer } }),
499
+ );
500
+ warnSpy.mockRestore();
501
+
502
+ expect(result.unobserved?.web?.reason).toBe("read-failed");
503
+ expect(result.unobserved?.web?.detail).toContain('context "prod-eks" (ambient; no k8s.profiles.prod binding)');
504
+ });
505
+
468
506
  test("a bound context the kubeconfig does not have refuses with the context name", async () => {
469
507
  loadChantConfigMock.mockResolvedValue({ config: { k8s: { profiles: { prod: { context: "gone-eks" } } } } });
470
508
  const oneContext = fakeKubeconfig({ contexts: [{ name: "prod-eks" }], currentContext: "prod-eks" });
@@ -573,6 +611,161 @@ describe("k8s describeResources", () => {
573
611
  expect(result.resources["prod/hand-made"]).toBeUndefined();
574
612
  });
575
613
 
614
+ // The issue's own estate: a declared MicroVMReplicaSet whose operator
615
+ // made MicroVMs — a CRD kind no table anticipated. Discovery names it.
616
+ test("a controller-made child of a CRD kind surfaces as a runtime child of the declared CR", async () => {
617
+ const replicaSet = {
618
+ apiVersion: "lambda.aws.amazon.com/v1alpha1",
619
+ kind: "MicroVMReplicaSet",
620
+ metadata: { name: "kmv-dev-a-vm", namespace: "microvm-demo", uid: "mvrs-uid" },
621
+ };
622
+ const vm = {
623
+ apiVersion: "lambda.aws.amazon.com/v1alpha1",
624
+ kind: "MicroVM",
625
+ metadata: {
626
+ name: "kmv-dev-a-vm-42sgf",
627
+ namespace: "microvm-demo",
628
+ uid: "vm-uid",
629
+ ownerReferences: [
630
+ { apiVersion: "lambda.aws.amazon.com/v1alpha1", kind: "MicroVMReplicaSet", name: "kmv-dev-a-vm", uid: "mvrs-uid", controller: true },
631
+ ],
632
+ },
633
+ status: { phase: "Running" },
634
+ };
635
+ const cluster = fakeCluster({
636
+ objects: {
637
+ [objectKey("lambda.aws.amazon.com/v1alpha1", "MicroVMReplicaSet", "kmv-dev-a-vm", "microvm-demo")]: replicaSet,
638
+ [objectKey("lambda.aws.amazon.com/v1alpha1", "MicroVM", "kmv-dev-a-vm-42sgf", "microvm-demo")]: vm,
639
+ },
640
+ });
641
+
642
+ const result = await describeResources(
643
+ {
644
+ environment: "dev",
645
+ buildOutput: "",
646
+ entityNames: ["workloadReplicaSet"],
647
+ entities: makeEntities([
648
+ {
649
+ name: "workloadReplicaSet",
650
+ entityType: "K8s::KubeMicroVM::MicroVMReplicaSet",
651
+ props: { metadata: { name: "kmv-dev-a-vm", namespace: "microvm-demo" } },
652
+ },
653
+ ]),
654
+ },
655
+ cluster.connector,
656
+ );
657
+
658
+ expect(result.resources["microvm-demo/kmv-dev-a-vm-42sgf"]).toMatchObject({
659
+ type: "K8s::Lambda::MicroVM",
660
+ physicalId: "vm-uid",
661
+ ownerChain: { root: "declared", entity: "workloadReplicaSet" },
662
+ });
663
+ });
664
+
665
+ test("a child in a group the estate never declares surfaces too — discovery names the kinds, not the declaration", async () => {
666
+ // A declared CR whose operator makes ordinary Deployments: ray.io is
667
+ // declared, apps/v1 is not, and the estate-groups sweep never listed it.
668
+ const rayCluster = { apiVersion: "ray.io/v1", kind: "RayCluster", metadata: { name: "ml", namespace: "ray", uid: "ray-uid" } };
669
+ const head = {
670
+ apiVersion: "apps/v1",
671
+ kind: "Deployment",
672
+ metadata: {
673
+ name: "ml-head",
674
+ namespace: "ray",
675
+ uid: "head-uid",
676
+ ownerReferences: [{ apiVersion: "ray.io/v1", kind: "RayCluster", name: "ml", uid: "ray-uid", controller: true }],
677
+ },
678
+ status: { readyReplicas: 1, replicas: 1 },
679
+ };
680
+ const cluster = fakeCluster({
681
+ objects: {
682
+ [objectKey("ray.io/v1", "RayCluster", "ml", "ray")]: rayCluster,
683
+ [objectKey("apps/v1", "Deployment", "ml-head", "ray")]: head,
684
+ },
685
+ });
686
+
687
+ const result = await describeResources(
688
+ {
689
+ environment: "prod",
690
+ buildOutput: "",
691
+ entityNames: ["ml"],
692
+ entities: makeEntities([
693
+ { name: "ml", entityType: "K8s::Ray::RayCluster", props: { metadata: { name: "ml", namespace: "ray" } } },
694
+ ]),
695
+ },
696
+ cluster.connector,
697
+ );
698
+
699
+ expect(result.resources["ray/ml-head"]).toMatchObject({
700
+ type: "K8s::Apps::Deployment",
701
+ physicalId: "head-uid",
702
+ ownerChain: { root: "declared", entity: "ml" },
703
+ });
704
+ });
705
+
706
+ test("a discovered kind the sweep cannot list is an unobserved hole, never silently absent", async () => {
707
+ const cluster = fakeCluster({
708
+ objects: { [objectKey("batch/v1", "CronJob", "backup", "prod")]: cronJob },
709
+ respond: (req) =>
710
+ req.path === "/apis/batch/v1/namespaces/prod/jobs"
711
+ ? { status: 403, body: statusBody(403, "Forbidden", "jobs is forbidden") }
712
+ : undefined,
713
+ });
714
+
715
+ const result = await describeResources(
716
+ { environment: "prod", buildOutput: "", entityNames: ["backup"], entities: declaredCronJob() },
717
+ cluster.connector,
718
+ );
719
+
720
+ // Declared resolution is never failed by the sweep.
721
+ expect(result.resources.backup).toMatchObject({ type: "K8s::Batch::CronJob", physicalId: "cj-uid" });
722
+ // The failed list is a hole the observation admits, with the failure's
723
+ // own classification — a 403 is a credentials problem, and silence here
724
+ // would have read as "no runtime children in prod".
725
+ expect(result.unobserved?.["runtime-sweep:batch/v1/Job"]).toMatchObject({
726
+ type: "K8s::Batch::Job",
727
+ reason: "no-credentials",
728
+ });
729
+ expect(result.unobserved?.["runtime-sweep:batch/v1/Job"]?.detail).toContain("namespace prod");
730
+ });
731
+
732
+ test("a cluster-scoped child chains to a declared cluster-scoped entity — the one case cluster-scoped kinds are swept", async () => {
733
+ const ns = { apiVersion: "v1", kind: "Namespace", metadata: { name: "team-a", uid: "ns-uid" }, status: { phase: "Active" } };
734
+ const role = {
735
+ apiVersion: "rbac.authorization.k8s.io/v1",
736
+ kind: "ClusterRole",
737
+ metadata: {
738
+ name: "team-a-admin",
739
+ uid: "role-uid",
740
+ ownerReferences: [{ apiVersion: "v1", kind: "Namespace", name: "team-a", uid: "ns-uid", controller: true }],
741
+ },
742
+ };
743
+ const cluster = fakeCluster({
744
+ objects: {
745
+ [objectKey("v1", "Namespace", "team-a")]: ns,
746
+ [objectKey("rbac.authorization.k8s.io/v1", "ClusterRole", "team-a-admin")]: role,
747
+ },
748
+ });
749
+
750
+ const result = await describeResources(
751
+ {
752
+ environment: "prod",
753
+ buildOutput: "",
754
+ entityNames: ["team"],
755
+ entities: makeEntities([
756
+ { name: "team", entityType: "K8s::Core::Namespace", props: { metadata: { name: "team-a" } } },
757
+ ]),
758
+ },
759
+ cluster.connector,
760
+ );
761
+
762
+ expect(result.resources["cluster:team-a-admin"]).toMatchObject({
763
+ type: "K8s::Rbac::ClusterRole",
764
+ physicalId: "role-uid",
765
+ ownerChain: { root: "declared", entity: "team" },
766
+ });
767
+ });
768
+
576
769
  test("a Flux-managed Deployment is attributed to its declared Kustomization by the controller's labels (#1549)", async () => {
577
770
  // Flux stamps managed objects with name+namespace labels instead of
578
771
  // ownerReferences, so the chain walk reaches nothing — the label
@@ -702,7 +895,7 @@ describe("k8s describeResources", () => {
702
895
  expect(result.resources["flux-system/cert-manager"]).toBeUndefined();
703
896
  });
704
897
 
705
- test("a core-only estate sweeps no extra groups — Pods stay the core representative", async () => {
898
+ test("the sweep's bound is the estate's namespaces, not a kind list — and no cluster-scoped list without a cluster-scoped entity", async () => {
706
899
  const svc = { apiVersion: "v1", kind: "Service", metadata: { name: "web", namespace: "prod", uid: "svc-uid" } };
707
900
  const cluster = fakeCluster({
708
901
  objects: { [objectKey("v1", "Service", "web", "prod")]: svc },
@@ -720,10 +913,20 @@ describe("k8s describeResources", () => {
720
913
  cluster.connector,
721
914
  );
722
915
 
723
- // Discovery was consulted for no non-core groupVersion: the only list
724
- // sweeps are the namespace's pods.
725
- const groupDiscovery = cluster.layer.paths().filter((p) => /^\/apis\/[^/]+\/[^/]+$/.test(p) && !p.includes("namespaces"));
726
- expect(groupDiscovery.filter((p) => !p.startsWith("/apis/apps"))).toEqual([]);
916
+ const paths = cluster.layer.paths();
917
+ const isDiscovery = (p: string) =>
918
+ p === "/api" || p === "/apis" || /^\/api\/[^/]+$/.test(p) || /^\/apis\/[^/]+\/[^/]+$/.test(p);
919
+ const reads = paths.filter((p) => !isDiscovery(p));
920
+ // Discovery names the kinds; every actual read or list stays inside the
921
+ // one namespace the estate touches. Nothing is listed cluster-wide.
922
+ expect(reads.length).toBeGreaterThan(0);
923
+ expect(reads.every((p) => p.includes("/namespaces/prod/"))).toBe(true);
924
+ // Nothing declared is cluster-scoped, so no owner chain could reach a
925
+ // cluster-scoped object — their kinds are not listed at all.
926
+ expect(paths.some((p) => p.endsWith("/clusterroles"))).toBe(false);
927
+ // Events churn by the thousands and can never chain (`involvedObject`,
928
+ // not `ownerReferences`) — discovery serves them, the sweep skips them.
929
+ expect(paths.some((p) => p.endsWith("/events"))).toBe(false);
727
930
  });
728
931
  });
729
932
 
@@ -914,7 +1117,7 @@ describe("k8s describeResources", () => {
914
1117
  expect(Object.keys(result.resources)).toHaveLength(2); // web + standalonePod, no synthetic duplicate
915
1118
  });
916
1119
 
917
- test("a Pod-list failure for a namespace is best-effort — declared entities still resolve", async () => {
1120
+ test("a Pod-list failure never fails declared resolution — and surfaces as an unobserved hole (#1517)", async () => {
918
1121
  const cluster = fakeCluster({
919
1122
  objects: { [objectKey("apps/v1", "Deployment", "web", "prod")]: web },
920
1123
  respond: (req) => (req.path.endsWith("/pods") ? { status: 403, body: statusBody(403, "Forbidden", "no pods list") } : undefined),
@@ -927,6 +1130,12 @@ describe("k8s describeResources", () => {
927
1130
 
928
1131
  expect(result.resources.web).toMatchObject({ type: "K8s::Apps::Deployment", physicalId: "uid-1" });
929
1132
  expect(Object.keys(result.resources)).toEqual(["web"]);
1133
+ // Silence would read as "no runtime Pods in prod", which the failed
1134
+ // list never proved — the sweep admits the hole instead.
1135
+ expect(result.unobserved?.["runtime-sweep:v1/Pod"]).toMatchObject({
1136
+ type: "K8s::Core::Pod",
1137
+ reason: "no-credentials",
1138
+ });
930
1139
  });
931
1140
 
932
1141
  test("a lexicon-supported entity type with no namespaced entities resolved does no Pod scan at all", async () => {