@intentius/chant-lexicon-k8s 0.41.19 → 0.42.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 (276) hide show
  1. package/dist/components/builders.d.ts +4 -0
  2. package/dist/components/builders.d.ts.map +1 -1
  3. package/dist/components/capability-plugin.d.ts +1 -1
  4. package/dist/components/capability-plugin.d.ts.map +1 -1
  5. package/dist/components/index.d.ts +1 -0
  6. package/dist/components/index.d.ts.map +1 -1
  7. package/dist/components/kustomize-apply.d.ts +48 -0
  8. package/dist/components/kustomize-apply.d.ts.map +1 -0
  9. package/dist/deep-observe-hooks.d.ts +8 -0
  10. package/dist/deep-observe-hooks.d.ts.map +1 -1
  11. package/dist/describe-resources.d.ts.map +1 -1
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/integrity.json +2 -2
  15. package/dist/manifest.json +1 -1
  16. package/dist/okf/index.md +250 -0
  17. package/dist/okf/rules/ARGO001.md +16 -0
  18. package/dist/okf/rules/ARGO002.md +16 -0
  19. package/dist/okf/rules/ARGO003.md +15 -0
  20. package/dist/okf/rules/ARGO004.md +17 -0
  21. package/dist/okf/rules/ARGO005.md +15 -0
  22. package/dist/okf/rules/WK8001.md +16 -0
  23. package/dist/okf/rules/WK8002.md +20 -0
  24. package/dist/okf/rules/WK8003.md +19 -0
  25. package/dist/okf/rules/WK8005.md +11 -0
  26. package/dist/okf/rules/WK8006.md +11 -0
  27. package/dist/okf/rules/WK8041.md +11 -0
  28. package/dist/okf/rules/WK8042.md +11 -0
  29. package/dist/okf/rules/WK8101.md +11 -0
  30. package/dist/okf/rules/WK8102.md +11 -0
  31. package/dist/okf/rules/WK8103.md +11 -0
  32. package/dist/okf/rules/WK8104.md +15 -0
  33. package/dist/okf/rules/WK8105.md +11 -0
  34. package/dist/okf/rules/WK8201.md +11 -0
  35. package/dist/okf/rules/WK8202.md +11 -0
  36. package/dist/okf/rules/WK8203.md +11 -0
  37. package/dist/okf/rules/WK8204.md +11 -0
  38. package/dist/okf/rules/WK8205.md +11 -0
  39. package/dist/okf/rules/WK8207.md +11 -0
  40. package/dist/okf/rules/WK8208.md +11 -0
  41. package/dist/okf/rules/WK8209.md +11 -0
  42. package/dist/okf/rules/WK8301.md +16 -0
  43. package/dist/okf/rules/WK8302.md +11 -0
  44. package/dist/okf/rules/WK8303.md +15 -0
  45. package/dist/okf/rules/WK8304.md +15 -0
  46. package/dist/okf/rules/WK8305.md +15 -0
  47. package/dist/okf/rules/WK8306.md +11 -0
  48. package/dist/okf/rules/WK8401.md +15 -0
  49. package/dist/okf/rules/WK8402.md +15 -0
  50. package/dist/okf/rules/WK8403.md +15 -0
  51. package/dist/okf/types/APIGroup.md +16 -0
  52. package/dist/okf/types/APIGroupList.md +13 -0
  53. package/dist/okf/types/APIResourceList.md +14 -0
  54. package/dist/okf/types/APIService.md +14 -0
  55. package/dist/okf/types/APIServiceList.md +14 -0
  56. package/dist/okf/types/APIVersions.md +14 -0
  57. package/dist/okf/types/Alert.md +14 -0
  58. package/dist/okf/types/AppProject.md +19 -0
  59. package/dist/okf/types/Application.md +23 -0
  60. package/dist/okf/types/ApplicationSet.md +18 -0
  61. package/dist/okf/types/Backup.md +14 -0
  62. package/dist/okf/types/BatchJob.md +14 -0
  63. package/dist/okf/types/Binding.md +14 -0
  64. package/dist/okf/types/Bucket.md +14 -0
  65. package/dist/okf/types/CM.md +16 -0
  66. package/dist/okf/types/CSIDriver.md +14 -0
  67. package/dist/okf/types/CSIDriverList.md +14 -0
  68. package/dist/okf/types/CSINode.md +14 -0
  69. package/dist/okf/types/CSINodeList.md +14 -0
  70. package/dist/okf/types/CSIStorageCapacity.md +17 -0
  71. package/dist/okf/types/CSIStorageCapacityList.md +14 -0
  72. package/dist/okf/types/Certificate.md +18 -0
  73. package/dist/okf/types/CertificateRequest.md +14 -0
  74. package/dist/okf/types/CertificateSigningRequest.md +14 -0
  75. package/dist/okf/types/CertificateSigningRequestList.md +14 -0
  76. package/dist/okf/types/Challenge.md +14 -0
  77. package/dist/okf/types/Cluster.md +14 -0
  78. package/dist/okf/types/ClusterIssuer.md +14 -0
  79. package/dist/okf/types/ClusterRole.md +15 -0
  80. package/dist/okf/types/ClusterRoleBinding.md +15 -0
  81. package/dist/okf/types/ClusterRoleBindingList.md +14 -0
  82. package/dist/okf/types/ClusterRoleList.md +14 -0
  83. package/dist/okf/types/ClusterTrustBundle.md +14 -0
  84. package/dist/okf/types/ClusterTrustBundleList.md +14 -0
  85. package/dist/okf/types/ComponentStatus.md +14 -0
  86. package/dist/okf/types/ComponentStatusList.md +14 -0
  87. package/dist/okf/types/ConfigMapList.md +14 -0
  88. package/dist/okf/types/ControllerRevision.md +15 -0
  89. package/dist/okf/types/ControllerRevisionList.md +14 -0
  90. package/dist/okf/types/CoreEventList.md +14 -0
  91. package/dist/okf/types/CrdbCluster.md +14 -0
  92. package/dist/okf/types/CronJob.md +20 -0
  93. package/dist/okf/types/CronJobList.md +14 -0
  94. package/dist/okf/types/CustomResourceDefinition.md +14 -0
  95. package/dist/okf/types/CustomResourceDefinitionList.md +14 -0
  96. package/dist/okf/types/DaemonSet.md +19 -0
  97. package/dist/okf/types/DaemonSetList.md +14 -0
  98. package/dist/okf/types/DeleteOptions.md +18 -0
  99. package/dist/okf/types/Deploy.md +14 -0
  100. package/dist/okf/types/DeploymentList.md +14 -0
  101. package/dist/okf/types/DeviceClass.md +14 -0
  102. package/dist/okf/types/DeviceClassList.md +14 -0
  103. package/dist/okf/types/DeviceTaintRule.md +14 -0
  104. package/dist/okf/types/DeviceTaintRuleList.md +14 -0
  105. package/dist/okf/types/EndpointSlice.md +16 -0
  106. package/dist/okf/types/EndpointSliceList.md +14 -0
  107. package/dist/okf/types/Endpoints.md +14 -0
  108. package/dist/okf/types/EndpointsList.md +14 -0
  109. package/dist/okf/types/Event.md +27 -0
  110. package/dist/okf/types/EventsEvent.md +27 -0
  111. package/dist/okf/types/EventsEventList.md +14 -0
  112. package/dist/okf/types/Eviction.md +14 -0
  113. package/dist/okf/types/FlowSchema.md +14 -0
  114. package/dist/okf/types/FlowSchemaList.md +14 -0
  115. package/dist/okf/types/FluxInstance.md +14 -0
  116. package/dist/okf/types/FluxReport.md +14 -0
  117. package/dist/okf/types/GRPCRoute.md +14 -0
  118. package/dist/okf/types/Gateway.md +14 -0
  119. package/dist/okf/types/GatewayClass.md +14 -0
  120. package/dist/okf/types/GitRepository.md +14 -0
  121. package/dist/okf/types/HTTPRoute.md +14 -0
  122. package/dist/okf/types/HelmChart.md +14 -0
  123. package/dist/okf/types/HelmRelease.md +14 -0
  124. package/dist/okf/types/HelmRepository.md +14 -0
  125. package/dist/okf/types/HorizontalPodAutoscaler.md +14 -0
  126. package/dist/okf/types/HorizontalPodAutoscalerList.md +14 -0
  127. package/dist/okf/types/IPAddress.md +14 -0
  128. package/dist/okf/types/IPAddressList.md +14 -0
  129. package/dist/okf/types/ImagePolicy.md +14 -0
  130. package/dist/okf/types/ImageRepository.md +14 -0
  131. package/dist/okf/types/ImageUpdateAutomation.md +14 -0
  132. package/dist/okf/types/InfisicalDynamicSecret.md +14 -0
  133. package/dist/okf/types/InfisicalPushSecret.md +14 -0
  134. package/dist/okf/types/InfisicalSecret.md +14 -0
  135. package/dist/okf/types/Ing.md +14 -0
  136. package/dist/okf/types/IngressClass.md +14 -0
  137. package/dist/okf/types/IngressClassList.md +14 -0
  138. package/dist/okf/types/IngressList.md +14 -0
  139. package/dist/okf/types/IngressRoute.md +14 -0
  140. package/dist/okf/types/IngressRouteTCP.md +14 -0
  141. package/dist/okf/types/IngressRouteUDP.md +14 -0
  142. package/dist/okf/types/Issuer.md +14 -0
  143. package/dist/okf/types/JobList.md +14 -0
  144. package/dist/okf/types/Kustomization.md +14 -0
  145. package/dist/okf/types/Lease.md +14 -0
  146. package/dist/okf/types/LeaseCandidate.md +14 -0
  147. package/dist/okf/types/LeaseCandidateList.md +14 -0
  148. package/dist/okf/types/LeaseList.md +14 -0
  149. package/dist/okf/types/LimitRange.md +14 -0
  150. package/dist/okf/types/LimitRangeList.md +14 -0
  151. package/dist/okf/types/LocalSubjectAccessReview.md +14 -0
  152. package/dist/okf/types/MicroVM.md +14 -0
  153. package/dist/okf/types/MicroVMClass.md +14 -0
  154. package/dist/okf/types/MicroVMImage.md +14 -0
  155. package/dist/okf/types/MicroVMNetwork.md +14 -0
  156. package/dist/okf/types/MicroVMReplicaSet.md +14 -0
  157. package/dist/okf/types/Middleware.md +14 -0
  158. package/dist/okf/types/MiddlewareTCP.md +14 -0
  159. package/dist/okf/types/MutatingAdmissionPolicy.md +14 -0
  160. package/dist/okf/types/MutatingAdmissionPolicyBinding.md +14 -0
  161. package/dist/okf/types/MutatingAdmissionPolicyBindingList.md +14 -0
  162. package/dist/okf/types/MutatingAdmissionPolicyList.md +14 -0
  163. package/dist/okf/types/MutatingWebhookConfiguration.md +14 -0
  164. package/dist/okf/types/MutatingWebhookConfigurationList.md +14 -0
  165. package/dist/okf/types/Namespace.md +18 -0
  166. package/dist/okf/types/NamespaceList.md +14 -0
  167. package/dist/okf/types/NetPol.md +14 -0
  168. package/dist/okf/types/NetworkPolicyList.md +14 -0
  169. package/dist/okf/types/Node.md +21 -0
  170. package/dist/okf/types/NodeList.md +14 -0
  171. package/dist/okf/types/OCIRepository.md +14 -0
  172. package/dist/okf/types/ObjectStore.md +14 -0
  173. package/dist/okf/types/Order.md +14 -0
  174. package/dist/okf/types/PDB.md +18 -0
  175. package/dist/okf/types/PersistentVolume.md +14 -0
  176. package/dist/okf/types/PersistentVolumeClaim.md +14 -0
  177. package/dist/okf/types/PersistentVolumeClaimList.md +14 -0
  178. package/dist/okf/types/PersistentVolumeList.md +14 -0
  179. package/dist/okf/types/Pod.md +18 -0
  180. package/dist/okf/types/PodCertificateRequest.md +14 -0
  181. package/dist/okf/types/PodCertificateRequestList.md +14 -0
  182. package/dist/okf/types/PodDisruptionBudgetList.md +14 -0
  183. package/dist/okf/types/PodList.md +14 -0
  184. package/dist/okf/types/PodTemplate.md +14 -0
  185. package/dist/okf/types/PodTemplateList.md +14 -0
  186. package/dist/okf/types/Pooler.md +14 -0
  187. package/dist/okf/types/PriorityClass.md +17 -0
  188. package/dist/okf/types/PriorityClassList.md +14 -0
  189. package/dist/okf/types/PriorityLevelConfiguration.md +14 -0
  190. package/dist/okf/types/PriorityLevelConfigurationList.md +14 -0
  191. package/dist/okf/types/PrometheusRule.md +14 -0
  192. package/dist/okf/types/Provider.md +14 -0
  193. package/dist/okf/types/RayCluster.md +20 -0
  194. package/dist/okf/types/RayJob.md +14 -0
  195. package/dist/okf/types/RayService.md +14 -0
  196. package/dist/okf/types/Receiver.md +14 -0
  197. package/dist/okf/types/ReferenceGrant.md +14 -0
  198. package/dist/okf/types/ReplicaSet.md +19 -0
  199. package/dist/okf/types/ReplicaSetList.md +14 -0
  200. package/dist/okf/types/ReplicationController.md +14 -0
  201. package/dist/okf/types/ReplicationControllerList.md +14 -0
  202. package/dist/okf/types/ResourceClaim.md +14 -0
  203. package/dist/okf/types/ResourceClaimList.md +14 -0
  204. package/dist/okf/types/ResourceClaimTemplate.md +14 -0
  205. package/dist/okf/types/ResourceClaimTemplateList.md +14 -0
  206. package/dist/okf/types/ResourceQuota.md +14 -0
  207. package/dist/okf/types/ResourceQuotaList.md +14 -0
  208. package/dist/okf/types/ResourceSet.md +14 -0
  209. package/dist/okf/types/ResourceSetInputProvider.md +14 -0
  210. package/dist/okf/types/ResourceSlice.md +14 -0
  211. package/dist/okf/types/ResourceSliceList.md +14 -0
  212. package/dist/okf/types/Role.md +14 -0
  213. package/dist/okf/types/RoleBinding.md +15 -0
  214. package/dist/okf/types/RoleBindingList.md +14 -0
  215. package/dist/okf/types/RoleList.md +14 -0
  216. package/dist/okf/types/RuntimeClass.md +16 -0
  217. package/dist/okf/types/RuntimeClassList.md +14 -0
  218. package/dist/okf/types/SA.md +16 -0
  219. package/dist/okf/types/Scale.md +14 -0
  220. package/dist/okf/types/ScheduledBackup.md +14 -0
  221. package/dist/okf/types/Sec.md +17 -0
  222. package/dist/okf/types/SecretList.md +14 -0
  223. package/dist/okf/types/SelfSubjectAccessReview.md +14 -0
  224. package/dist/okf/types/SelfSubjectReview.md +13 -0
  225. package/dist/okf/types/SelfSubjectRulesReview.md +14 -0
  226. package/dist/okf/types/ServersTransport.md +14 -0
  227. package/dist/okf/types/ServersTransportTCP.md +14 -0
  228. package/dist/okf/types/Service.md +20 -0
  229. package/dist/okf/types/ServiceAccountList.md +14 -0
  230. package/dist/okf/types/ServiceCIDR.md +14 -0
  231. package/dist/okf/types/ServiceCIDRList.md +14 -0
  232. package/dist/okf/types/ServiceList.md +14 -0
  233. package/dist/okf/types/ServiceMonitor.md +14 -0
  234. package/dist/okf/types/StatefulSet.md +19 -0
  235. package/dist/okf/types/StatefulSetList.md +14 -0
  236. package/dist/okf/types/Status.md +17 -0
  237. package/dist/okf/types/StorageClass.md +20 -0
  238. package/dist/okf/types/StorageClassList.md +14 -0
  239. package/dist/okf/types/StorageVersionMigration.md +14 -0
  240. package/dist/okf/types/StorageVersionMigrationList.md +14 -0
  241. package/dist/okf/types/SubjectAccessReview.md +14 -0
  242. package/dist/okf/types/TLSOption.md +14 -0
  243. package/dist/okf/types/TLSStore.md +14 -0
  244. package/dist/okf/types/TokenRequest.md +14 -0
  245. package/dist/okf/types/TokenReview.md +14 -0
  246. package/dist/okf/types/TraefikService.md +14 -0
  247. package/dist/okf/types/ValidatingAdmissionPolicy.md +14 -0
  248. package/dist/okf/types/ValidatingAdmissionPolicyBinding.md +14 -0
  249. package/dist/okf/types/ValidatingAdmissionPolicyBindingList.md +14 -0
  250. package/dist/okf/types/ValidatingAdmissionPolicyList.md +14 -0
  251. package/dist/okf/types/ValidatingWebhookConfiguration.md +14 -0
  252. package/dist/okf/types/ValidatingWebhookConfigurationList.md +14 -0
  253. package/dist/okf/types/VolumeAttachment.md +14 -0
  254. package/dist/okf/types/VolumeAttachmentList.md +14 -0
  255. package/dist/okf/types/VolumeAttributesClass.md +15 -0
  256. package/dist/okf/types/VolumeAttributesClassList.md +14 -0
  257. package/dist/okf/types/WatchEvent.md +14 -0
  258. package/dist/op/activities/kubectl.d.ts +15 -1
  259. package/dist/op/activities/kubectl.d.ts.map +1 -1
  260. package/dist/op/activities/wait-for-ready.d.ts +16 -6
  261. package/dist/op/activities/wait-for-ready.d.ts.map +1 -1
  262. package/package.json +2 -2
  263. package/src/components/builders.ts +5 -0
  264. package/src/components/capability-plugin.ts +3 -2
  265. package/src/components/index.ts +7 -0
  266. package/src/components/kustomize-apply.test.ts +98 -0
  267. package/src/components/kustomize-apply.ts +133 -0
  268. package/src/deep-observe-hooks.ts +60 -0
  269. package/src/deep-observe.test.ts +114 -0
  270. package/src/describe-resources.test.ts +159 -0
  271. package/src/describe-resources.ts +142 -2
  272. package/src/index.ts +1 -0
  273. package/src/op/activities/kubectl.test.ts +22 -0
  274. package/src/op/activities/kubectl.ts +15 -2
  275. package/src/op/activities/wait-for-ready.test.ts +27 -0
  276. package/src/op/activities/wait-for-ready.ts +84 -6
@@ -104,9 +104,54 @@ export function statusFromObject(obj: K8sObject): string {
104
104
  ? "READY"
105
105
  : `PROGRESSING(${status.readyReplicas}/${status.replicas})`;
106
106
  }
107
+
108
+ // The kstatus rung (#1549): an object with NO phase and NO replica counts —
109
+ // a Flux Kustomization/HelmRelease, cert-manager, any conditions-first CRD —
110
+ // used to fall straight to PRESENT, so a wedged reconciler read exactly like
111
+ // a healthy one. `Ready` is the contract those objects speak: False → its
112
+ // reason (`BuildFailed`, `UpgradeFailed`, …), True → READY. Deliberately
113
+ // BELOW the phase/replica rungs so no Pod or workload word changes — for
114
+ // those objects a False Ready is already visible through the rungs above.
115
+ const ready = readyConditionWord(status);
116
+ if (ready) return ready;
117
+
118
+ // Argo's Application speaks health/sync, not a Ready condition (the same
119
+ // split the readiness registry encodes) — `Degraded` / `OutOfSync` beats
120
+ // PRESENT for the one object whose whole job is convergence.
121
+ const argo = argoStatusWord(status);
122
+ if (argo) return argo;
123
+
107
124
  return "PRESENT";
108
125
  }
109
126
 
127
+ /** The `Ready` condition rendered as a status word: False → its reason (or
128
+ * NOT-READY when the controller gave none), True → READY, absent → nothing. */
129
+ function readyConditionWord(status: K8sObject["status"]): string | undefined {
130
+ const conditions = (status as { conditions?: unknown } | undefined)?.conditions;
131
+ if (!Array.isArray(conditions)) return undefined;
132
+ for (const c of conditions) {
133
+ const cond = c as { type?: unknown; status?: unknown; reason?: unknown };
134
+ if (cond.type !== "Ready") continue;
135
+ if (cond.status === "True") return "READY";
136
+ if (cond.status === "False") {
137
+ return typeof cond.reason === "string" && cond.reason.length > 0 ? cond.reason : "NOT-READY";
138
+ }
139
+ }
140
+ return undefined;
141
+ }
142
+
143
+ /** Argo Application health/sync as a status word: anything but the happy pair
144
+ * surfaces the unhappy half; Healthy+Synced reads as READY. */
145
+ function argoStatusWord(status: K8sObject["status"]): string | undefined {
146
+ const s = status as { health?: { status?: unknown }; sync?: { status?: unknown } } | undefined;
147
+ const health = typeof s?.health?.status === "string" ? s.health.status : undefined;
148
+ const sync = typeof s?.sync?.status === "string" ? s.sync.status : undefined;
149
+ if (!health && !sync) return undefined;
150
+ if (health && health !== "Healthy") return health;
151
+ if (sync && sync !== "Synced") return sync;
152
+ return "READY";
153
+ }
154
+
110
155
  /**
111
156
  * The reason a container is stuck, if one is — `CrashLoopBackOff`,
112
157
  * `ImagePullBackOff`, `CreateContainerConfigError`.
@@ -333,7 +378,7 @@ export async function describeResources(
333
378
  if (op && op.apiVersion.includes("/")) runtimeGroupVersions.add(op.apiVersion);
334
379
  }
335
380
 
336
- await addRuntimeChildren(client, resources, options.owned, runtimeGroupVersions);
381
+ await addRuntimeChildren(client, resources, options.owned, runtimeGroupVersions, declared);
337
382
 
338
383
  return observation(resources, unobserved);
339
384
  }
@@ -360,22 +405,99 @@ export async function describeResources(
360
405
  * axis for them to report against, the same way an out-of-band AWS child
361
406
  * resource has none either.
362
407
  */
408
+ /** The declared GitOps CRs, indexed the way their controllers' labels can be
409
+ * resolved (#1549). `byRef` keys are `type\0namespace\0name` (Flux — exact);
410
+ * `argoByName` maps an Application's name to its declared entity, or null
411
+ * when two declared Applications share the name (ambiguous → no claim). */
412
+ interface GitopsIndex {
413
+ present: boolean;
414
+ byRef: Map<string, string>;
415
+ argoByName: Map<string, string | null>;
416
+ targetNamespaces: Set<string>;
417
+ }
418
+
419
+ function gitopsIndex(declared: readonly Declared[]): GitopsIndex {
420
+ const byRef = new Map<string, string>();
421
+ const argoByName = new Map<string, string | null>();
422
+ const targetNamespaces = new Set<string>();
423
+ let present = false;
424
+ for (const { entityName, entityType, props } of declared) {
425
+ const isArgo = entityType === "K8s::Argo::Application";
426
+ const isFlux = entityType === "K8s::Flux::Kustomization" || entityType === "K8s::Flux::HelmRelease";
427
+ if (!isArgo && !isFlux) continue;
428
+ present = true;
429
+ const meta = props.metadata as { name?: string; namespace?: string } | undefined;
430
+ const spec = props.spec as { targetNamespace?: unknown; destination?: { namespace?: unknown } } | undefined;
431
+ const name = meta?.name;
432
+ if (!name) continue;
433
+ if (isFlux) {
434
+ byRef.set(`${entityType}\0${meta?.namespace ?? "default"}\0${name}`, entityName);
435
+ if (typeof spec?.targetNamespace === "string") targetNamespaces.add(spec.targetNamespace);
436
+ } else {
437
+ argoByName.set(name, argoByName.has(name) ? null : entityName);
438
+ if (typeof spec?.destination?.namespace === "string") targetNamespaces.add(spec.destination.namespace);
439
+ }
440
+ }
441
+ return { present, byRef, argoByName, targetNamespaces };
442
+ }
443
+
444
+ /** The declared GitOps CR a swept object's controller labels resolve to, or
445
+ * undefined. Flux's label pairs are exact; Argo's bare instance label claims
446
+ * only a UNIQUE declared Application. */
447
+ function gitopsOwner(labels: Record<string, string> | undefined, gitops: GitopsIndex): string | undefined {
448
+ if (!labels || !gitops.present) return undefined;
449
+ const ksName = labels["kustomize.toolkit.fluxcd.io/name"];
450
+ const ksNs = labels["kustomize.toolkit.fluxcd.io/namespace"];
451
+ if (ksName && ksNs) {
452
+ const hit = gitops.byRef.get(`K8s::Flux::Kustomization\0${ksNs}\0${ksName}`);
453
+ if (hit) return hit;
454
+ }
455
+ const hrName = labels["helm.toolkit.fluxcd.io/name"];
456
+ const hrNs = labels["helm.toolkit.fluxcd.io/namespace"];
457
+ if (hrName && hrNs) {
458
+ const hit = gitops.byRef.get(`K8s::Flux::HelmRelease\0${hrNs}\0${hrName}`);
459
+ if (hit) return hit;
460
+ }
461
+ const instance = labels["app.kubernetes.io/instance"];
462
+ if (instance) {
463
+ const hit = gitops.argoByName.get(instance);
464
+ if (hit) return hit; // null (ambiguous) and undefined both decline
465
+ }
466
+ return undefined;
467
+ }
468
+
363
469
  async function addRuntimeChildren(
364
470
  client: K8sClient,
365
471
  resources: Record<string, ResourceMetadata>,
366
472
  owned: boolean | undefined,
367
473
  groupVersions: ReadonlySet<string> = new Set(),
474
+ declared: readonly Declared[] = [],
368
475
  ): Promise<void> {
369
476
  const declaredByUid = new Map<string, string>();
370
477
  for (const [entityName, meta] of Object.entries(resources)) {
371
478
  if (meta.physicalId) declaredByUid.set(meta.physicalId, entityName);
372
479
  }
373
480
 
481
+ // GitOps attribution (#1549): Argo and Flux stamp their managed objects with
482
+ // labels, not ownerReferences, so the chain walk never reaches a declared
483
+ // entity for them. The label channel resolves against the DECLARED CRs by
484
+ // (type, namespace, name) — exact for Flux (its labels carry both halves) —
485
+ // and by unique name for Argo (its instance label carries no namespace, and
486
+ // it doubles as a generic Helm label, so anything ambiguous resolves to
487
+ // nothing: the module's standing conservatism).
488
+ const gitops = gitopsIndex(declared);
489
+
374
490
  const namespaces = new Set<string>();
375
491
  for (const meta of Object.values(resources)) {
376
492
  const namespace = (meta.attributes as { namespace?: string } | undefined)?.namespace;
377
493
  if (namespace) namespaces.add(namespace);
378
494
  }
495
+ // The namespaces a declared GitOps CR TARGETS are declared literals too
496
+ // (Argo `spec.destination.namespace`, Flux `spec.targetNamespace`) — the
497
+ // controller puts the workloads there, so the scan must look there, and
498
+ // reading it off the declaration keeps this bounded by the estate, never a
499
+ // cluster-wide sweep.
500
+ for (const ns of gitops.targetNamespaces) namespaces.add(ns);
379
501
  if (namespaces.size === 0) return;
380
502
 
381
503
  // The kinds to scan: Pods, plus each declared groupVersion's namespaced,
@@ -384,6 +506,18 @@ async function addRuntimeChildren(
384
506
  const kinds: Array<{ apiVersion: string; kind: string; typeName: string }> = [
385
507
  { apiVersion: "v1", kind: "Pod", typeName: "K8s::Core::Pod" },
386
508
  ];
509
+ // A GitOps estate's managed WORKLOADS live in groups the estate itself never
510
+ // declares (a pure Argo/Flux estate declares only the CRs), so without this
511
+ // the label channel could only ever fire on Pods (#1549).
512
+ if (gitops.present) {
513
+ kinds.push(
514
+ { apiVersion: "apps/v1", kind: "Deployment", typeName: "K8s::Apps::Deployment" },
515
+ { apiVersion: "apps/v1", kind: "StatefulSet", typeName: "K8s::Apps::StatefulSet" },
516
+ { apiVersion: "apps/v1", kind: "DaemonSet", typeName: "K8s::Apps::DaemonSet" },
517
+ { apiVersion: "v1", kind: "Service", typeName: "K8s::Core::Service" },
518
+ { apiVersion: "v1", kind: "ConfigMap", typeName: "K8s::Core::ConfigMap" },
519
+ );
520
+ }
387
521
  for (const gv of [...groupVersions].sort()) {
388
522
  let infos;
389
523
  try {
@@ -413,7 +547,13 @@ async function addRuntimeChildren(
413
547
  if (!uid || !name || declaredByUid.has(uid)) return; // declared directly, or unaddressable
414
548
  if (resources[`${namespace}/${name}`]) return; // already reported by an earlier kind
415
549
 
416
- const ownerChain = await resolveK8sOwnerChain(obj, { declaredByUid, reader: client, namespace });
550
+ let ownerChain = await resolveK8sOwnerChain(obj, { declaredByUid, reader: client, namespace });
551
+ // The label channel (#1549) — only when the chain itself said nothing:
552
+ // a verdict the walk DID reach is never overridden.
553
+ if (ownerChain.root !== "declared") {
554
+ const entity = gitopsOwner(obj.metadata?.labels, gitops);
555
+ if (entity) ownerChain = { root: "declared", entity };
556
+ }
417
557
 
418
558
  // `--owned`: withhold an object that is neither a runtime child of a
419
559
  // declared entity nor carrying chant's own marker — the same rule the
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export { k8sPlugin } from "./plugin";
9
9
  // cfn-deploy.
10
10
  export { k8sCapabilityPlugin, K8S_VERB_FAMILIES } from "./components/capability-plugin";
11
11
  export { kubectlApplyCapability, createKubectlApplyCapability, type KubectlApplyInput } from "./components/kubectl-apply";
12
+ export { kustomizeApplyCapability, createKustomizeApplyCapability, type KustomizeApplyInput } from "./components/kustomize-apply";
12
13
 
13
14
  // Default labels & annotations
14
15
  export { defaultLabels, defaultAnnotations, isDefaultLabels, isDefaultAnnotations } from "./default-labels";
@@ -281,6 +281,28 @@ describe("kubectlApply", () => {
281
281
  expect(body.metadata?.labels).toBeUndefined();
282
282
  });
283
283
 
284
+ test("inline documents bypass the filesystem — the kustomize-apply seam (#1548)", async () => {
285
+ const cluster = fakeCluster({ respond: echoApplies });
286
+ const result = await applyManifest(
287
+ {
288
+ manifest: "kustomize:overlays/dev", // a label; nothing at this path
289
+ documents: [
290
+ { apiVersion: "v1", kind: "Namespace", metadata: { name: "web" } },
291
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "app", namespace: "web" } },
292
+ ],
293
+ stack: "web",
294
+ },
295
+ undefined,
296
+ cluster.connector,
297
+ );
298
+ expect(result.applied.map((r) => r.kind)).toEqual(["Namespace", "Deployment"]);
299
+ // The documents were stamped like any read-from-disk manifest.
300
+ const body = JSON.parse(String(cluster.layer.requests.find((r) => r.method === "PATCH")!.body)) as {
301
+ metadata?: { labels?: Record<string, string> };
302
+ };
303
+ expect(body.metadata?.labels?.["chant.intentius.io/stack"]).toBe("web");
304
+ });
305
+
284
306
  test("an explicit context is honored and skips the environment lookup entirely", async () => {
285
307
  const file = join(dir, "k8s.yaml");
286
308
  writeFileSync(file, deploymentYaml);
@@ -55,8 +55,21 @@ import { DEFAULT_IMPORT_TYPES } from "../../api/sweep-types";
55
55
  export type ApplyDeleteMode = "never" | "owned-only" | "gated";
56
56
 
57
57
  export interface KubectlApplyArgs {
58
- /** Path to a manifest file, or a directory of them. */
58
+ /**
59
+ * Path to a manifest file, or a directory of them. With `documents` given,
60
+ * this becomes only the human-facing label the heartbeats and logs carry
61
+ * (e.g. `kustomize:<dir>`), and nothing is read from disk.
62
+ */
59
63
  manifest: string;
64
+ /**
65
+ * Already-parsed documents to apply INSTEAD of reading `manifest` (#1548):
66
+ * a renderer that produced the objects in memory — kustomize-apply's
67
+ * `kustomize build` — hands them straight in rather than round-tripping
68
+ * through a temp file. Everything downstream (ownership stamping, the
69
+ * marker-scoped prune, the self-conflict retake) is document-driven and
70
+ * behaves identically.
71
+ */
72
+ documents?: K8sObject[];
60
73
  /**
61
74
  * kubectl context name. Uses the ambient context if omitted. To target the
62
75
  * same cluster the read path (`describeResources`) resolved for an
@@ -285,7 +298,7 @@ export async function applyManifest(
285
298
  signal?: AbortSignal,
286
299
  connect: K8sConnector = defaultK8sConnector,
287
300
  ): Promise<ApplyManifestResult> {
288
- const documents = readManifestDocuments(args.manifest);
301
+ const documents = args.documents ?? readManifestDocuments(args.manifest);
289
302
  const { fieldManager, stack } = await resolveApplyIdentity(args);
290
303
  const heartbeatInterval = setInterval(() => {
291
304
  safeHeartbeat({ step: "kubectl apply", manifest: args.manifest });
@@ -49,6 +49,33 @@ describe("readiness model", () => {
49
49
  expect(isReady(obj, DEFAULT_READINESS)).toBe(true);
50
50
  });
51
51
 
52
+ test("Flux overrides fail fast on Ready=False wedge reasons, wait on ordinary progress (#1549)", () => {
53
+ // A Flux wedge (bad build, failed upgrade, unreachable source) keeps
54
+ // Ready=False forever — before these entries the generic poll waited out
55
+ // its whole timeout. The ready half is still the kstatus default.
56
+ const spec = readinessFor("kustomize.toolkit.fluxcd.io", "Kustomization");
57
+ expect(spec).not.toBe(DEFAULT_READINESS);
58
+ expect(isReady(ready([{ type: "Ready", status: "True" }]), spec)).toBe(true);
59
+
60
+ const wedged = ready([{ type: "Ready", status: "False", reason: "BuildFailed" } as never]);
61
+ expect(isReady(wedged, spec)).toBe(false);
62
+ expect(firstTerminal(wedged, spec)).toBeDefined();
63
+
64
+ // Ready=False with a PROGRESS reason (a reconcile in flight) is not a
65
+ // wedge — keep polling.
66
+ const progressing = ready([{ type: "Ready", status: "False", reason: "Progressing" } as never]);
67
+ expect(firstTerminal(progressing, spec)).toBeUndefined();
68
+
69
+ // A reason on a TRUE condition is vocabulary, never terminal.
70
+ const healthyWithReason = ready([{ type: "Ready", status: "True", reason: "ReconciliationSucceeded" } as never]);
71
+ expect(firstTerminal(healthyWithReason, spec)).toBeUndefined();
72
+
73
+ const hr = readinessFor("helm.toolkit.fluxcd.io", "HelmRelease");
74
+ expect(firstTerminal(ready([{ type: "Ready", status: "False", reason: "UpgradeFailed" } as never]), hr)).toBeDefined();
75
+ const git = readinessFor("source.toolkit.fluxcd.io", "GitRepository");
76
+ expect(firstTerminal(ready([{ type: "Ready", status: "False", reason: "AuthenticationFailed" } as never]), git)).toBeDefined();
77
+ });
78
+
52
79
  test("Argo override uses health/sync, not a Ready condition", () => {
53
80
  const spec = readinessFor("argoproj.io", "Application");
54
81
  expect(spec).not.toBe(DEFAULT_READINESS);
@@ -40,7 +40,23 @@ export interface PathMatch {
40
40
  // With neither `equals` nor `oneOf`, holds when the value is present (non-null).
41
41
  }
42
42
 
43
- export type ReadinessMatch = ConditionMatch | PathMatch;
43
+ /**
44
+ * Match a `status.conditions[]` entry by `type` AND its `reason` (#1549).
45
+ * The dot-path walk can't index into a conditions array, and the Flux
46
+ * toolkit's failure signal is exactly `conditions[type=Ready].reason` — a
47
+ * wedge like `BuildFailed` keeps `Ready=False` forever while the generic
48
+ * poll waits out its whole timeout. `status` defaults to "False": a reason
49
+ * on a True condition is progress vocabulary, not a wedge.
50
+ */
51
+ export interface ConditionReasonMatch {
52
+ conditionType: string;
53
+ /** Required `status` of the condition (default "False"). */
54
+ status?: string;
55
+ /** Holds when the condition's `reason` is one of these. */
56
+ reasonOneOf: string[];
57
+ }
58
+
59
+ export type ReadinessMatch = ConditionMatch | PathMatch | ConditionReasonMatch;
44
60
 
45
61
  export interface ReadinessSpec {
46
62
  /** All must hold for the resource to be ready. */
@@ -69,6 +85,19 @@ export const DEFAULT_READINESS: ReadinessSpec = {
69
85
  * reports `health`/`sync`, not a `Ready` condition — the case that proves the
70
86
  * override is necessary (#365).
71
87
  */
88
+ /**
89
+ * Flux toolkit wedge reasons (#1549): a `Ready=False` with one of these
90
+ * `reason`s does not heal by waiting — a bad build, a failed install, an
91
+ * unreachable or invalid source. The toolkit is kstatus-conformant, so the
92
+ * READY half of every entry below is just the default; the value added is
93
+ * failing fast instead of polling out the full timeout.
94
+ */
95
+ const FLUX_TERMINAL = (reasons: string[]): ReadinessSpec => ({
96
+ ready: [{ conditionType: "Ready", status: "True" }],
97
+ terminal: [{ conditionType: "Ready", reasonOneOf: reasons }],
98
+ observedGeneration: true,
99
+ });
100
+
72
101
  export const READINESS_OVERRIDES: Record<string, ReadinessSpec> = {
73
102
  "argoproj.io/Application": {
74
103
  ready: [
@@ -78,6 +107,38 @@ export const READINESS_OVERRIDES: Record<string, ReadinessSpec> = {
78
107
  terminal: [{ path: "status.health.status", oneOf: ["Degraded", "Missing"] }],
79
108
  observedGeneration: false,
80
109
  },
110
+ "kustomize.toolkit.fluxcd.io/Kustomization": FLUX_TERMINAL([
111
+ "BuildFailed",
112
+ "HealthCheckFailed",
113
+ "ReconciliationFailed",
114
+ "ArtifactFailed",
115
+ "PruneFailed",
116
+ ]),
117
+ "helm.toolkit.fluxcd.io/HelmRelease": FLUX_TERMINAL([
118
+ "InstallFailed",
119
+ "UpgradeFailed",
120
+ "RollbackFailed",
121
+ "UninstallFailed",
122
+ "ArtifactFailed",
123
+ "ChartPullError",
124
+ ]),
125
+ "source.toolkit.fluxcd.io/GitRepository": FLUX_TERMINAL([
126
+ "AuthenticationFailed",
127
+ "GitOperationFailed",
128
+ "URLInvalid",
129
+ "StorageOperationFailed",
130
+ ]),
131
+ "source.toolkit.fluxcd.io/OCIRepository": FLUX_TERMINAL([
132
+ "AuthenticationFailed",
133
+ "OCIPullFailed",
134
+ "URLInvalid",
135
+ "StorageOperationFailed",
136
+ ]),
137
+ "source.toolkit.fluxcd.io/HelmChart": FLUX_TERMINAL([
138
+ "ChartPullError",
139
+ "ChartPackageError",
140
+ "StorageOperationFailed",
141
+ ]),
81
142
  };
82
143
 
83
144
  /** Resolve the readiness spec for a resource: registry override, else default. */
@@ -94,15 +155,31 @@ function getPath(obj: unknown, path: string): unknown {
94
155
  );
95
156
  }
96
157
 
158
+ function isConditionReason(m: ReadinessMatch): m is ConditionReasonMatch {
159
+ return (m as ConditionReasonMatch).reasonOneOf !== undefined;
160
+ }
161
+
97
162
  function isCondition(m: ReadinessMatch): m is ConditionMatch {
98
- return (m as ConditionMatch).conditionType !== undefined;
163
+ return (m as ConditionMatch).conditionType !== undefined && !isConditionReason(m);
99
164
  }
100
165
 
101
- function matchCondition(obj: unknown, m: ConditionMatch): boolean {
166
+ function findCondition(obj: unknown, conditionType: string): Record<string, unknown> | undefined {
102
167
  const conds = getPath(obj, "status.conditions");
103
- if (!Array.isArray(conds)) return false;
104
- const c = conds.find((x) => x && typeof x === "object" && (x as Record<string, unknown>).type === m.conditionType);
105
- return !!c && String((c as Record<string, unknown>).status) === (m.status ?? "True");
168
+ if (!Array.isArray(conds)) return undefined;
169
+ return conds.find((x) => x && typeof x === "object" && (x as Record<string, unknown>).type === conditionType) as
170
+ | Record<string, unknown>
171
+ | undefined;
172
+ }
173
+
174
+ function matchCondition(obj: unknown, m: ConditionMatch): boolean {
175
+ const c = findCondition(obj, m.conditionType);
176
+ return !!c && String(c.status) === (m.status ?? "True");
177
+ }
178
+
179
+ function matchConditionReason(obj: unknown, m: ConditionReasonMatch): boolean {
180
+ const c = findCondition(obj, m.conditionType);
181
+ if (!c || String(c.status) !== (m.status ?? "False")) return false;
182
+ return typeof c.reason === "string" && m.reasonOneOf.includes(c.reason);
106
183
  }
107
184
 
108
185
  function matchPath(obj: unknown, m: PathMatch): boolean {
@@ -113,6 +190,7 @@ function matchPath(obj: unknown, m: PathMatch): boolean {
113
190
  }
114
191
 
115
192
  function matches(obj: unknown, m: ReadinessMatch): boolean {
193
+ if (isConditionReason(m)) return matchConditionReason(obj, m);
116
194
  return isCondition(m) ? matchCondition(obj, m) : matchPath(obj, m);
117
195
  }
118
196