typekro 0.28.1 → 0.29.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 (671) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/advanced/index.d.ts +5 -1
  3. package/dist/advanced/index.d.ts.map +1 -1
  4. package/dist/advanced/index.js +2 -3
  5. package/dist/advanced/index.js.map +1 -1
  6. package/dist/alchemy/deployers.d.ts +6 -4
  7. package/dist/alchemy/deployers.d.ts.map +1 -1
  8. package/dist/alchemy/deployers.js +46 -9
  9. package/dist/alchemy/deployers.js.map +1 -1
  10. package/dist/alchemy/index.d.ts +2 -3
  11. package/dist/alchemy/index.d.ts.map +1 -1
  12. package/dist/alchemy/index.js +1 -1
  13. package/dist/alchemy/index.js.map +1 -1
  14. package/dist/alchemy/kro-delete.d.ts +6 -5
  15. package/dist/alchemy/kro-delete.d.ts.map +1 -1
  16. package/dist/alchemy/kro-delete.js +169 -22
  17. package/dist/alchemy/kro-delete.js.map +1 -1
  18. package/dist/alchemy/resource-registration.d.ts +24 -31
  19. package/dist/alchemy/resource-registration.d.ts.map +1 -1
  20. package/dist/alchemy/resource-registration.js +439 -75
  21. package/dist/alchemy/resource-registration.js.map +1 -1
  22. package/dist/alchemy/types.d.ts +56 -14
  23. package/dist/alchemy/types.d.ts.map +1 -1
  24. package/dist/alchemy/utilities.d.ts +2 -2
  25. package/dist/alchemy/utilities.d.ts.map +1 -1
  26. package/dist/compositions/typekro-runtime/typekro-runtime.d.ts.map +1 -1
  27. package/dist/compositions/typekro-runtime/typekro-runtime.js +31 -28
  28. package/dist/compositions/typekro-runtime/typekro-runtime.js.map +1 -1
  29. package/dist/compositions/typekro-runtime/types.d.ts +12 -0
  30. package/dist/compositions/typekro-runtime/types.d.ts.map +1 -1
  31. package/dist/core/composition/analysis-metadata.d.ts +15 -0
  32. package/dist/core/composition/analysis-metadata.d.ts.map +1 -0
  33. package/dist/core/composition/analysis-metadata.js +20 -0
  34. package/dist/core/composition/analysis-metadata.js.map +1 -0
  35. package/dist/core/composition/imperative.d.ts.map +1 -1
  36. package/dist/core/composition/imperative.js +38 -9
  37. package/dist/core/composition/imperative.js.map +1 -1
  38. package/dist/core/composition/nested-status-cel.js +3 -3
  39. package/dist/core/composition/nested-status-cel.js.map +1 -1
  40. package/dist/core/constants/brands.d.ts +1 -1
  41. package/dist/core/constants/brands.d.ts.map +1 -1
  42. package/dist/core/constants/brands.js +1 -1
  43. package/dist/core/constants/brands.js.map +1 -1
  44. package/dist/core/dependencies/resolver.d.ts +3 -1
  45. package/dist/core/dependencies/resolver.d.ts.map +1 -1
  46. package/dist/core/dependencies/resolver.js +29 -13
  47. package/dist/core/dependencies/resolver.js.map +1 -1
  48. package/dist/core/deployment/deletion-result.d.ts +22 -0
  49. package/dist/core/deployment/deletion-result.d.ts.map +1 -0
  50. package/dist/core/deployment/deletion-result.js +108 -0
  51. package/dist/core/deployment/deletion-result.js.map +1 -0
  52. package/dist/core/deployment/deployment-state-discovery.d.ts +1 -0
  53. package/dist/core/deployment/deployment-state-discovery.d.ts.map +1 -1
  54. package/dist/core/deployment/deployment-state-discovery.js +89 -9
  55. package/dist/core/deployment/deployment-state-discovery.js.map +1 -1
  56. package/dist/core/deployment/direct-factory.d.ts +46 -25
  57. package/dist/core/deployment/direct-factory.d.ts.map +1 -1
  58. package/dist/core/deployment/direct-factory.js +536 -126
  59. package/dist/core/deployment/direct-factory.js.map +1 -1
  60. package/dist/core/deployment/engine.d.ts +4 -1
  61. package/dist/core/deployment/engine.d.ts.map +1 -1
  62. package/dist/core/deployment/engine.js +105 -34
  63. package/dist/core/deployment/engine.js.map +1 -1
  64. package/dist/core/deployment/errors.d.ts +20 -1
  65. package/dist/core/deployment/errors.d.ts.map +1 -1
  66. package/dist/core/deployment/errors.js +55 -0
  67. package/dist/core/deployment/errors.js.map +1 -1
  68. package/dist/core/deployment/event-monitor.d.ts +4 -3
  69. package/dist/core/deployment/event-monitor.d.ts.map +1 -1
  70. package/dist/core/deployment/event-monitor.js +17 -5
  71. package/dist/core/deployment/event-monitor.js.map +1 -1
  72. package/dist/core/deployment/index.d.ts +1 -1
  73. package/dist/core/deployment/index.d.ts.map +1 -1
  74. package/dist/core/deployment/index.js +1 -1
  75. package/dist/core/deployment/index.js.map +1 -1
  76. package/dist/core/deployment/k8s-helpers.d.ts +1 -1
  77. package/dist/core/deployment/k8s-helpers.d.ts.map +1 -1
  78. package/dist/core/deployment/k8s-helpers.js +11 -5
  79. package/dist/core/deployment/k8s-helpers.js.map +1 -1
  80. package/dist/core/deployment/kro-factory.d.ts +54 -30
  81. package/dist/core/deployment/kro-factory.d.ts.map +1 -1
  82. package/dist/core/deployment/kro-factory.js +2002 -259
  83. package/dist/core/deployment/kro-factory.js.map +1 -1
  84. package/dist/core/deployment/kro-instance-admission.d.ts +17 -0
  85. package/dist/core/deployment/kro-instance-admission.d.ts.map +1 -0
  86. package/dist/core/deployment/kro-instance-admission.js +73 -0
  87. package/dist/core/deployment/kro-instance-admission.js.map +1 -0
  88. package/dist/core/deployment/kro-instance-safety.d.ts +0 -3
  89. package/dist/core/deployment/kro-instance-safety.d.ts.map +1 -1
  90. package/dist/core/deployment/kro-instance-safety.js +0 -11
  91. package/dist/core/deployment/kro-instance-safety.js.map +1 -1
  92. package/dist/core/deployment/kro-namespace-teardown.d.ts +63 -5
  93. package/dist/core/deployment/kro-namespace-teardown.d.ts.map +1 -1
  94. package/dist/core/deployment/kro-namespace-teardown.js +124 -12
  95. package/dist/core/deployment/kro-namespace-teardown.js.map +1 -1
  96. package/dist/core/deployment/kro-readiness.d.ts +2 -0
  97. package/dist/core/deployment/kro-readiness.d.ts.map +1 -1
  98. package/dist/core/deployment/kro-readiness.js +23 -6
  99. package/dist/core/deployment/kro-readiness.js.map +1 -1
  100. package/dist/core/deployment/poll-timeout.d.ts +1 -1
  101. package/dist/core/deployment/poll-timeout.d.ts.map +1 -1
  102. package/dist/core/deployment/poll-timeout.js +12 -1
  103. package/dist/core/deployment/poll-timeout.js.map +1 -1
  104. package/dist/core/deployment/resource-applier.d.ts +19 -3
  105. package/dist/core/deployment/resource-applier.d.ts.map +1 -1
  106. package/dist/core/deployment/resource-applier.js +263 -44
  107. package/dist/core/deployment/resource-applier.js.map +1 -1
  108. package/dist/core/deployment/rollback-manager.d.ts +6 -1
  109. package/dist/core/deployment/rollback-manager.d.ts.map +1 -1
  110. package/dist/core/deployment/rollback-manager.js +38 -12
  111. package/dist/core/deployment/rollback-manager.js.map +1 -1
  112. package/dist/core/deployment/schema-cel-evaluator.d.ts.map +1 -1
  113. package/dist/core/deployment/schema-cel-evaluator.js +63 -0
  114. package/dist/core/deployment/schema-cel-evaluator.js.map +1 -1
  115. package/dist/core/deployment/shared-utilities.d.ts +11 -6
  116. package/dist/core/deployment/shared-utilities.d.ts.map +1 -1
  117. package/dist/core/deployment/shared-utilities.js +169 -14
  118. package/dist/core/deployment/shared-utilities.js.map +1 -1
  119. package/dist/core/deployment/singleton-gitops.d.ts.map +1 -1
  120. package/dist/core/deployment/singleton-gitops.js +0 -2
  121. package/dist/core/deployment/singleton-gitops.js.map +1 -1
  122. package/dist/core/deployment/singleton-owner-drift.d.ts +11 -2
  123. package/dist/core/deployment/singleton-owner-drift.d.ts.map +1 -1
  124. package/dist/core/deployment/singleton-owner-drift.js +30 -13
  125. package/dist/core/deployment/singleton-owner-drift.js.map +1 -1
  126. package/dist/core/deployment/strategies/base-strategy.d.ts +2 -0
  127. package/dist/core/deployment/strategies/base-strategy.d.ts.map +1 -1
  128. package/dist/core/deployment/strategies/base-strategy.js +19 -2
  129. package/dist/core/deployment/strategies/base-strategy.js.map +1 -1
  130. package/dist/core/deployment/strategies/direct-strategy.d.ts +3 -0
  131. package/dist/core/deployment/strategies/direct-strategy.d.ts.map +1 -1
  132. package/dist/core/deployment/strategies/direct-strategy.js +31 -8
  133. package/dist/core/deployment/strategies/direct-strategy.js.map +1 -1
  134. package/dist/core/deployment/strategies/index.d.ts +1 -1
  135. package/dist/core/deployment/strategies/index.d.ts.map +1 -1
  136. package/dist/core/deployment/strategies/index.js +1 -1
  137. package/dist/core/deployment/strategies/index.js.map +1 -1
  138. package/dist/core/errors.d.ts +2 -2
  139. package/dist/core/errors.d.ts.map +1 -1
  140. package/dist/core/errors.js.map +1 -1
  141. package/dist/core/expressions/analysis/cel-emitter.d.ts.map +1 -1
  142. package/dist/core/expressions/analysis/cel-emitter.js +17 -13
  143. package/dist/core/expressions/analysis/cel-emitter.js.map +1 -1
  144. package/dist/core/expressions/analysis/expression-metadata.d.ts +15 -0
  145. package/dist/core/expressions/analysis/expression-metadata.d.ts.map +1 -0
  146. package/dist/core/expressions/analysis/expression-metadata.js +10 -0
  147. package/dist/core/expressions/analysis/expression-metadata.js.map +1 -0
  148. package/dist/core/expressions/analysis/ready-when.d.ts +3 -0
  149. package/dist/core/expressions/analysis/ready-when.d.ts.map +1 -0
  150. package/dist/core/expressions/analysis/ready-when.js +127 -0
  151. package/dist/core/expressions/analysis/ready-when.js.map +1 -0
  152. package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts +3 -2
  153. package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts.map +1 -1
  154. package/dist/core/expressions/composition/composition-analyzer-helpers.js +6 -4
  155. package/dist/core/expressions/composition/composition-analyzer-helpers.js.map +1 -1
  156. package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts +3 -2
  157. package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts.map +1 -1
  158. package/dist/core/expressions/composition/composition-analyzer-ternary.js +12 -12
  159. package/dist/core/expressions/composition/composition-analyzer-ternary.js.map +1 -1
  160. package/dist/core/expressions/composition/composition-analyzer-traversal.d.ts.map +1 -1
  161. package/dist/core/expressions/composition/composition-analyzer-traversal.js +68 -4
  162. package/dist/core/expressions/composition/composition-analyzer-traversal.js.map +1 -1
  163. package/dist/core/expressions/composition/composition-analyzer-types.d.ts +2 -0
  164. package/dist/core/expressions/composition/composition-analyzer-types.d.ts.map +1 -1
  165. package/dist/core/expressions/composition/composition-analyzer.d.ts.map +1 -1
  166. package/dist/core/expressions/composition/composition-analyzer.js +2 -0
  167. package/dist/core/expressions/composition/composition-analyzer.js.map +1 -1
  168. package/dist/core/expressions/factory/status-builder-analyzer.d.ts.map +1 -1
  169. package/dist/core/expressions/factory/status-builder-analyzer.js +14 -1
  170. package/dist/core/expressions/factory/status-builder-analyzer.js.map +1 -1
  171. package/dist/core/kubernetes/api.d.ts.map +1 -1
  172. package/dist/core/kubernetes/api.js +2 -3
  173. package/dist/core/kubernetes/api.js.map +1 -1
  174. package/dist/core/kubernetes/bun-api-client.d.ts +1 -1
  175. package/dist/core/kubernetes/bun-api-client.d.ts.map +1 -1
  176. package/dist/core/kubernetes/bun-api-client.js +17 -17
  177. package/dist/core/kubernetes/bun-api-client.js.map +1 -1
  178. package/dist/core/kubernetes/bun-http-library.d.ts +16 -0
  179. package/dist/core/kubernetes/bun-http-library.d.ts.map +1 -1
  180. package/dist/core/kubernetes/bun-http-library.js +7 -1
  181. package/dist/core/kubernetes/bun-http-library.js.map +1 -1
  182. package/dist/core/kubernetes/bun-watch.d.ts +30 -0
  183. package/dist/core/kubernetes/bun-watch.d.ts.map +1 -0
  184. package/dist/core/kubernetes/bun-watch.js +144 -0
  185. package/dist/core/kubernetes/bun-watch.js.map +1 -0
  186. package/dist/core/kubernetes/client-node-runtime.d.ts +10 -0
  187. package/dist/core/kubernetes/client-node-runtime.d.ts.map +1 -0
  188. package/dist/core/kubernetes/client-node-runtime.js +15 -0
  189. package/dist/core/kubernetes/client-node-runtime.js.map +1 -0
  190. package/dist/core/kubernetes/client-provider.d.ts +21 -1
  191. package/dist/core/kubernetes/client-provider.d.ts.map +1 -1
  192. package/dist/core/kubernetes/client-provider.js +10 -10
  193. package/dist/core/kubernetes/client-provider.js.map +1 -1
  194. package/dist/core/kubernetes/index.d.ts +1 -1
  195. package/dist/core/kubernetes/index.d.ts.map +1 -1
  196. package/dist/core/kubernetes/index.js.map +1 -1
  197. package/dist/core/metadata/resource-metadata.d.ts +24 -1
  198. package/dist/core/metadata/resource-metadata.d.ts.map +1 -1
  199. package/dist/core/metadata/resource-metadata.js +8 -4
  200. package/dist/core/metadata/resource-metadata.js.map +1 -1
  201. package/dist/core/planning/artifact-codec.d.ts +14 -0
  202. package/dist/core/planning/artifact-codec.d.ts.map +1 -0
  203. package/dist/core/planning/artifact-codec.js +329 -0
  204. package/dist/core/planning/artifact-codec.js.map +1 -0
  205. package/dist/core/planning/artifacts.d.ts +212 -0
  206. package/dist/core/planning/artifacts.d.ts.map +1 -0
  207. package/dist/core/planning/artifacts.js +5 -0
  208. package/dist/core/planning/artifacts.js.map +1 -0
  209. package/dist/core/planning/aspects.d.ts +11 -0
  210. package/dist/core/planning/aspects.d.ts.map +1 -0
  211. package/dist/core/planning/aspects.js +77 -0
  212. package/dist/core/planning/aspects.js.map +1 -0
  213. package/dist/core/planning/canonical.d.ts +10 -0
  214. package/dist/core/planning/canonical.d.ts.map +1 -0
  215. package/dist/core/planning/canonical.js +54 -0
  216. package/dist/core/planning/canonical.js.map +1 -0
  217. package/dist/core/planning/capabilities.d.ts +16 -0
  218. package/dist/core/planning/capabilities.d.ts.map +1 -0
  219. package/dist/core/planning/capabilities.js +90 -0
  220. package/dist/core/planning/capabilities.js.map +1 -0
  221. package/dist/core/planning/capture.d.ts +29 -0
  222. package/dist/core/planning/capture.d.ts.map +1 -0
  223. package/dist/core/planning/capture.js +18 -0
  224. package/dist/core/planning/capture.js.map +1 -0
  225. package/dist/core/planning/compiler.d.ts +12 -0
  226. package/dist/core/planning/compiler.d.ts.map +1 -0
  227. package/dist/core/planning/compiler.js +631 -0
  228. package/dist/core/planning/compiler.js.map +1 -0
  229. package/dist/core/planning/direct-runtime-adapter.d.ts +39 -0
  230. package/dist/core/planning/direct-runtime-adapter.d.ts.map +1 -0
  231. package/dist/core/planning/direct-runtime-adapter.js +238 -0
  232. package/dist/core/planning/direct-runtime-adapter.js.map +1 -0
  233. package/dist/core/planning/execution-record.d.ts +21 -0
  234. package/dist/core/planning/execution-record.d.ts.map +1 -0
  235. package/dist/core/planning/execution-record.js +304 -0
  236. package/dist/core/planning/execution-record.js.map +1 -0
  237. package/dist/core/planning/index.d.ts +20 -0
  238. package/dist/core/planning/index.d.ts.map +1 -0
  239. package/dist/core/planning/index.js +18 -0
  240. package/dist/core/planning/index.js.map +1 -0
  241. package/dist/core/planning/kro-bundle.d.ts +24 -0
  242. package/dist/core/planning/kro-bundle.d.ts.map +1 -0
  243. package/dist/core/planning/kro-bundle.js +314 -0
  244. package/dist/core/planning/kro-bundle.js.map +1 -0
  245. package/dist/core/planning/kro-runtime-adapter.d.ts +28 -0
  246. package/dist/core/planning/kro-runtime-adapter.d.ts.map +1 -0
  247. package/dist/core/planning/kro-runtime-adapter.js +211 -0
  248. package/dist/core/planning/kro-runtime-adapter.js.map +1 -0
  249. package/dist/core/planning/materialization.d.ts +54 -0
  250. package/dist/core/planning/materialization.d.ts.map +1 -0
  251. package/dist/core/planning/materialization.js +489 -0
  252. package/dist/core/planning/materialization.js.map +1 -0
  253. package/dist/core/planning/plan-codec.d.ts +11 -0
  254. package/dist/core/planning/plan-codec.d.ts.map +1 -0
  255. package/dist/core/planning/plan-codec.js +555 -0
  256. package/dist/core/planning/plan-codec.js.map +1 -0
  257. package/dist/core/planning/planner.d.ts +21 -0
  258. package/dist/core/planning/planner.d.ts.map +1 -0
  259. package/dist/core/planning/planner.js +1518 -0
  260. package/dist/core/planning/planner.js.map +1 -0
  261. package/dist/core/planning/schema.d.ts +10 -0
  262. package/dist/core/planning/schema.d.ts.map +1 -0
  263. package/dist/core/planning/schema.js +159 -0
  264. package/dist/core/planning/schema.js.map +1 -0
  265. package/dist/core/planning/types.d.ts +441 -0
  266. package/dist/core/planning/types.d.ts.map +1 -0
  267. package/dist/core/planning/types.js +3 -0
  268. package/dist/core/planning/types.js.map +1 -0
  269. package/dist/core/planning/values.d.ts +76 -0
  270. package/dist/core/planning/values.d.ts.map +1 -0
  271. package/dist/core/planning/values.js +852 -0
  272. package/dist/core/planning/values.js.map +1 -0
  273. package/dist/core/proxy/create-resource.d.ts +12 -0
  274. package/dist/core/proxy/create-resource.d.ts.map +1 -1
  275. package/dist/core/proxy/create-resource.js +39 -9
  276. package/dist/core/proxy/create-resource.js.map +1 -1
  277. package/dist/core/readiness/evaluator-factories.d.ts +3 -1
  278. package/dist/core/readiness/evaluator-factories.d.ts.map +1 -1
  279. package/dist/core/readiness/evaluator-factories.js +130 -5
  280. package/dist/core/readiness/evaluator-factories.js.map +1 -1
  281. package/dist/core/readiness/index.d.ts +2 -1
  282. package/dist/core/readiness/index.d.ts.map +1 -1
  283. package/dist/core/readiness/index.js +2 -1
  284. package/dist/core/readiness/index.js.map +1 -1
  285. package/dist/core/readiness/portable-strategies.d.ts +28 -0
  286. package/dist/core/readiness/portable-strategies.d.ts.map +1 -0
  287. package/dist/core/readiness/portable-strategies.js +55 -0
  288. package/dist/core/readiness/portable-strategies.js.map +1 -0
  289. package/dist/core/readiness/strategy-configuration.d.ts +12 -0
  290. package/dist/core/readiness/strategy-configuration.d.ts.map +1 -0
  291. package/dist/core/readiness/strategy-configuration.js +51 -0
  292. package/dist/core/readiness/strategy-configuration.js.map +1 -0
  293. package/dist/core/references/cel-evaluator.d.ts +5 -0
  294. package/dist/core/references/cel-evaluator.d.ts.map +1 -1
  295. package/dist/core/references/cel-evaluator.js +33 -2
  296. package/dist/core/references/cel-evaluator.js.map +1 -1
  297. package/dist/core/references/external-refs.d.ts +2 -0
  298. package/dist/core/references/external-refs.d.ts.map +1 -1
  299. package/dist/core/references/external-refs.js +3 -1
  300. package/dist/core/references/external-refs.js.map +1 -1
  301. package/dist/core/references/resolver.d.ts +7 -1
  302. package/dist/core/references/resolver.d.ts.map +1 -1
  303. package/dist/core/references/resolver.js +91 -9
  304. package/dist/core/references/resolver.js.map +1 -1
  305. package/dist/core/references/schema-proxy.d.ts.map +1 -1
  306. package/dist/core/references/schema-proxy.js +35 -21
  307. package/dist/core/references/schema-proxy.js.map +1 -1
  308. package/dist/core/resources/factory-registry.d.ts +64 -2
  309. package/dist/core/resources/factory-registry.d.ts.map +1 -1
  310. package/dist/core/resources/factory-registry.js +74 -3
  311. package/dist/core/resources/factory-registry.js.map +1 -1
  312. package/dist/core/resources/index.d.ts +3 -2
  313. package/dist/core/resources/index.d.ts.map +1 -1
  314. package/dist/core/resources/index.js +2 -1
  315. package/dist/core/resources/index.js.map +1 -1
  316. package/dist/core/resources/live-comparison.d.ts +58 -0
  317. package/dist/core/resources/live-comparison.d.ts.map +1 -0
  318. package/dist/core/resources/live-comparison.js +293 -0
  319. package/dist/core/resources/live-comparison.js.map +1 -0
  320. package/dist/core/runtime/standalone-operation.d.ts +12 -0
  321. package/dist/core/runtime/standalone-operation.d.ts.map +1 -0
  322. package/dist/core/runtime/standalone-operation.js +62 -0
  323. package/dist/core/runtime/standalone-operation.js.map +1 -0
  324. package/dist/core/serialization/cel-references.d.ts.map +1 -1
  325. package/dist/core/serialization/cel-references.js +11 -1
  326. package/dist/core/serialization/cel-references.js.map +1 -1
  327. package/dist/core/serialization/core.d.ts.map +1 -1
  328. package/dist/core/serialization/core.js +277 -182
  329. package/dist/core/serialization/core.js.map +1 -1
  330. package/dist/core/serialization/schema.d.ts.map +1 -1
  331. package/dist/core/serialization/schema.js +3 -3
  332. package/dist/core/serialization/schema.js.map +1 -1
  333. package/dist/core/serialization/status-analysis-pipeline.d.ts +1 -1
  334. package/dist/core/serialization/status-analysis-pipeline.d.ts.map +1 -1
  335. package/dist/core/serialization/status-analysis-pipeline.js +14 -11
  336. package/dist/core/serialization/status-analysis-pipeline.js.map +1 -1
  337. package/dist/core/serialization/yaml.d.ts +5 -1
  338. package/dist/core/serialization/yaml.d.ts.map +1 -1
  339. package/dist/core/serialization/yaml.js +35 -182
  340. package/dist/core/serialization/yaml.js.map +1 -1
  341. package/dist/core/types/deployment.d.ts +135 -14
  342. package/dist/core/types/deployment.d.ts.map +1 -1
  343. package/dist/core/types/kubernetes.d.ts +3 -0
  344. package/dist/core/types/kubernetes.d.ts.map +1 -1
  345. package/dist/core/types/resource-graph.d.ts +7 -1
  346. package/dist/core/types/resource-graph.d.ts.map +1 -1
  347. package/dist/core/types/serialization.d.ts +8 -1
  348. package/dist/core/types/serialization.d.ts.map +1 -1
  349. package/dist/core/validation/cel-validator.d.ts.map +1 -1
  350. package/dist/core/validation/cel-validator.js +25 -16
  351. package/dist/core/validation/cel-validator.js.map +1 -1
  352. package/dist/experimental-planning.d.ts +13 -0
  353. package/dist/experimental-planning.d.ts.map +1 -0
  354. package/dist/experimental-planning.js +12 -0
  355. package/dist/experimental-planning.js.map +1 -0
  356. package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts.map +1 -1
  357. package/dist/factories/apisix/compositions/apisix-bootstrap.js +27 -22
  358. package/dist/factories/apisix/compositions/apisix-bootstrap.js.map +1 -1
  359. package/dist/factories/apisix/compositions/apisix-helm-repository.d.ts +15 -0
  360. package/dist/factories/apisix/compositions/apisix-helm-repository.d.ts.map +1 -0
  361. package/dist/factories/apisix/compositions/apisix-helm-repository.js +28 -0
  362. package/dist/factories/apisix/compositions/apisix-helm-repository.js.map +1 -0
  363. package/dist/factories/apisix/compositions/index.d.ts +1 -0
  364. package/dist/factories/apisix/compositions/index.d.ts.map +1 -1
  365. package/dist/factories/apisix/compositions/index.js +1 -0
  366. package/dist/factories/apisix/compositions/index.js.map +1 -1
  367. package/dist/factories/apisix/types.d.ts +22 -0
  368. package/dist/factories/apisix/types.d.ts.map +1 -1
  369. package/dist/factories/apisix/types.js +16 -0
  370. package/dist/factories/apisix/types.js.map +1 -1
  371. package/dist/factories/apisix/utils/admin-credentials.d.ts +1 -0
  372. package/dist/factories/apisix/utils/admin-credentials.d.ts.map +1 -1
  373. package/dist/factories/apisix/utils/admin-credentials.js +5 -4
  374. package/dist/factories/apisix/utils/admin-credentials.js.map +1 -1
  375. package/dist/factories/caddy/compositions/caddy-ingress.d.ts.map +1 -1
  376. package/dist/factories/caddy/compositions/caddy-ingress.js +9 -2
  377. package/dist/factories/caddy/compositions/caddy-ingress.js.map +1 -1
  378. package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.d.ts.map +1 -1
  379. package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.js +11 -25
  380. package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.js.map +1 -1
  381. package/dist/factories/cert-manager/resources/certificates.d.ts.map +1 -1
  382. package/dist/factories/cert-manager/resources/certificates.js +2 -1
  383. package/dist/factories/cert-manager/resources/certificates.js.map +1 -1
  384. package/dist/factories/cert-manager/resources/challenges.d.ts.map +1 -1
  385. package/dist/factories/cert-manager/resources/challenges.js +3 -0
  386. package/dist/factories/cert-manager/resources/challenges.js.map +1 -1
  387. package/dist/factories/cilium/compositions/cilium-bootstrap.d.ts +4 -4
  388. package/dist/factories/cilium/resources/gateway.d.ts.map +1 -1
  389. package/dist/factories/cilium/resources/gateway.js +13 -12
  390. package/dist/factories/cilium/resources/gateway.js.map +1 -1
  391. package/dist/factories/cilium/resources/networking.d.ts.map +1 -1
  392. package/dist/factories/cilium/resources/networking.js +9 -0
  393. package/dist/factories/cilium/resources/networking.js.map +1 -1
  394. package/dist/factories/clickhouse/compositions/clickhouse-operator-bootstrap.d.ts +2 -1
  395. package/dist/factories/clickhouse/compositions/clickhouse-operator-bootstrap.d.ts.map +1 -1
  396. package/dist/factories/clickhouse/compositions/clickhouse-operator-bootstrap.js +2 -6
  397. package/dist/factories/clickhouse/compositions/clickhouse-operator-bootstrap.js.map +1 -1
  398. package/dist/factories/clickhouse/resources/installation.d.ts.map +1 -1
  399. package/dist/factories/clickhouse/resources/installation.js +2 -0
  400. package/dist/factories/clickhouse/resources/installation.js.map +1 -1
  401. package/dist/factories/clickhouse/resources/keeper.d.ts.map +1 -1
  402. package/dist/factories/clickhouse/resources/keeper.js +2 -0
  403. package/dist/factories/clickhouse/resources/keeper.js.map +1 -1
  404. package/dist/factories/clickhouse/types.d.ts +5 -2
  405. package/dist/factories/clickhouse/types.d.ts.map +1 -1
  406. package/dist/factories/clickhouse/types.js +2 -1
  407. package/dist/factories/clickhouse/types.js.map +1 -1
  408. package/dist/factories/clickstack/compositions/k8s-telemetry.d.ts +4 -2
  409. package/dist/factories/clickstack/compositions/k8s-telemetry.d.ts.map +1 -1
  410. package/dist/factories/clickstack/compositions/k8s-telemetry.js +2 -5
  411. package/dist/factories/clickstack/compositions/k8s-telemetry.js.map +1 -1
  412. package/dist/factories/clickstack/types.d.ts +2 -1
  413. package/dist/factories/clickstack/types.d.ts.map +1 -1
  414. package/dist/factories/clickstack/types.js +3 -1
  415. package/dist/factories/clickstack/types.js.map +1 -1
  416. package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts +2 -1
  417. package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts.map +1 -1
  418. package/dist/factories/cnpg/compositions/cnpg-bootstrap.js +3 -8
  419. package/dist/factories/cnpg/compositions/cnpg-bootstrap.js.map +1 -1
  420. package/dist/factories/cnpg/resources/backup.d.ts.map +1 -1
  421. package/dist/factories/cnpg/resources/backup.js +2 -0
  422. package/dist/factories/cnpg/resources/backup.js.map +1 -1
  423. package/dist/factories/cnpg/resources/cluster.d.ts.map +1 -1
  424. package/dist/factories/cnpg/resources/cluster.js +2 -1
  425. package/dist/factories/cnpg/resources/cluster.js.map +1 -1
  426. package/dist/factories/cnpg/resources/pooler.d.ts.map +1 -1
  427. package/dist/factories/cnpg/resources/pooler.js +2 -0
  428. package/dist/factories/cnpg/resources/pooler.js.map +1 -1
  429. package/dist/factories/cnpg/resources/scheduled-backup.d.ts.map +1 -1
  430. package/dist/factories/cnpg/resources/scheduled-backup.js +2 -1
  431. package/dist/factories/cnpg/resources/scheduled-backup.js.map +1 -1
  432. package/dist/factories/cnpg/types.d.ts +6 -3
  433. package/dist/factories/cnpg/types.d.ts.map +1 -1
  434. package/dist/factories/cnpg/types.js +2 -1
  435. package/dist/factories/cnpg/types.js.map +1 -1
  436. package/dist/factories/dagster/compositions/dagster-bootstrap.d.ts +2 -0
  437. package/dist/factories/dagster/compositions/dagster-bootstrap.d.ts.map +1 -1
  438. package/dist/factories/dagster/types.d.ts +5 -0
  439. package/dist/factories/dagster/types.d.ts.map +1 -1
  440. package/dist/factories/dagster/types.js +1 -0
  441. package/dist/factories/dagster/types.js.map +1 -1
  442. package/dist/factories/dagster/utils/helm-values-mapper.js +3 -0
  443. package/dist/factories/dagster/utils/helm-values-mapper.js.map +1 -1
  444. package/dist/factories/external-dns/compositions/external-dns-bootstrap.d.ts.map +1 -1
  445. package/dist/factories/external-dns/compositions/external-dns-bootstrap.js +7 -4
  446. package/dist/factories/external-dns/compositions/external-dns-bootstrap.js.map +1 -1
  447. package/dist/factories/external-dns/resources/dns-endpoint.d.ts.map +1 -1
  448. package/dist/factories/external-dns/resources/dns-endpoint.js +20 -18
  449. package/dist/factories/external-dns/resources/dns-endpoint.js.map +1 -1
  450. package/dist/factories/flux/git-repository.d.ts.map +1 -1
  451. package/dist/factories/flux/git-repository.js +41 -34
  452. package/dist/factories/flux/git-repository.js.map +1 -1
  453. package/dist/factories/flux/kustomize/readiness-evaluators.d.ts +0 -6
  454. package/dist/factories/flux/kustomize/readiness-evaluators.d.ts.map +1 -1
  455. package/dist/factories/flux/kustomize/readiness-evaluators.js +12 -2
  456. package/dist/factories/flux/kustomize/readiness-evaluators.js.map +1 -1
  457. package/dist/factories/harbor/compositions/harbor-installation.d.ts +46 -38
  458. package/dist/factories/harbor/compositions/harbor-installation.d.ts.map +1 -1
  459. package/dist/factories/harbor/compositions/harbor-installation.js +4 -2
  460. package/dist/factories/harbor/compositions/harbor-installation.js.map +1 -1
  461. package/dist/factories/harbor/types.d.ts +13 -11
  462. package/dist/factories/harbor/types.d.ts.map +1 -1
  463. package/dist/factories/harbor/types.js +11 -9
  464. package/dist/factories/harbor/types.js.map +1 -1
  465. package/dist/factories/helm/helm-repository.d.ts.map +1 -1
  466. package/dist/factories/helm/helm-repository.js +14 -1
  467. package/dist/factories/helm/helm-repository.js.map +1 -1
  468. package/dist/factories/helm/index.d.ts +2 -0
  469. package/dist/factories/helm/index.d.ts.map +1 -1
  470. package/dist/factories/helm/index.js +1 -0
  471. package/dist/factories/helm/index.js.map +1 -1
  472. package/dist/factories/helm/readiness-evaluators.d.ts.map +1 -1
  473. package/dist/factories/helm/readiness-evaluators.js +46 -7
  474. package/dist/factories/helm/readiness-evaluators.js.map +1 -1
  475. package/dist/factories/helm/status.d.ts +18 -0
  476. package/dist/factories/helm/status.d.ts.map +1 -0
  477. package/dist/factories/helm/status.js +38 -0
  478. package/dist/factories/helm/status.js.map +1 -0
  479. package/dist/factories/index.d.ts +2 -2
  480. package/dist/factories/index.d.ts.map +1 -1
  481. package/dist/factories/index.js +4 -4
  482. package/dist/factories/index.js.map +1 -1
  483. package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts +1 -1
  484. package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts.map +1 -1
  485. package/dist/factories/inngest/compositions/inngest-bootstrap.js +2 -4
  486. package/dist/factories/inngest/compositions/inngest-bootstrap.js.map +1 -1
  487. package/dist/factories/inngest/resources/helm.d.ts.map +1 -1
  488. package/dist/factories/inngest/resources/helm.js +12 -3
  489. package/dist/factories/inngest/resources/helm.js.map +1 -1
  490. package/dist/factories/inngest/types.d.ts +3 -7
  491. package/dist/factories/inngest/types.d.ts.map +1 -1
  492. package/dist/factories/inngest/types.js +1 -1
  493. package/dist/factories/inngest/types.js.map +1 -1
  494. package/dist/factories/kro/kro-crd.d.ts.map +1 -1
  495. package/dist/factories/kro/kro-crd.js +32 -33
  496. package/dist/factories/kro/kro-crd.js.map +1 -1
  497. package/dist/factories/kro/kro-custom-resource.d.ts.map +1 -1
  498. package/dist/factories/kro/kro-custom-resource.js +91 -85
  499. package/dist/factories/kro/kro-custom-resource.js.map +1 -1
  500. package/dist/factories/kro/resource-graph-definition.d.ts.map +1 -1
  501. package/dist/factories/kro/resource-graph-definition.js +127 -117
  502. package/dist/factories/kro/resource-graph-definition.js.map +1 -1
  503. package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler-v1.d.ts.map +1 -1
  504. package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler-v1.js +27 -26
  505. package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler-v1.js.map +1 -1
  506. package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler.d.ts.map +1 -1
  507. package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler.js +22 -22
  508. package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler.js.map +1 -1
  509. package/dist/factories/kubernetes/certificates/certificate-signing-request.d.ts.map +1 -1
  510. package/dist/factories/kubernetes/certificates/certificate-signing-request.js +41 -39
  511. package/dist/factories/kubernetes/certificates/certificate-signing-request.js.map +1 -1
  512. package/dist/factories/kubernetes/core/component-status.d.ts.map +1 -1
  513. package/dist/factories/kubernetes/core/component-status.js +14 -16
  514. package/dist/factories/kubernetes/core/component-status.js.map +1 -1
  515. package/dist/factories/kubernetes/core/namespace.d.ts.map +1 -1
  516. package/dist/factories/kubernetes/core/namespace.js +31 -38
  517. package/dist/factories/kubernetes/core/namespace.js.map +1 -1
  518. package/dist/factories/kubernetes/core/node.d.ts.map +1 -1
  519. package/dist/factories/kubernetes/core/node.js +17 -25
  520. package/dist/factories/kubernetes/core/node.js.map +1 -1
  521. package/dist/factories/kubernetes/core/pod.d.ts.map +1 -1
  522. package/dist/factories/kubernetes/core/pod.js +31 -35
  523. package/dist/factories/kubernetes/core/pod.js.map +1 -1
  524. package/dist/factories/kubernetes/extensions/custom-resource-definition.d.ts.map +1 -1
  525. package/dist/factories/kubernetes/extensions/custom-resource-definition.js +34 -36
  526. package/dist/factories/kubernetes/extensions/custom-resource-definition.js.map +1 -1
  527. package/dist/factories/kubernetes/extensions/custom-resource.d.ts.map +1 -1
  528. package/dist/factories/kubernetes/extensions/custom-resource.js +2 -4
  529. package/dist/factories/kubernetes/extensions/custom-resource.js.map +1 -1
  530. package/dist/factories/kubernetes/networking/endpoints.d.ts.map +1 -1
  531. package/dist/factories/kubernetes/networking/endpoints.js +29 -30
  532. package/dist/factories/kubernetes/networking/endpoints.js.map +1 -1
  533. package/dist/factories/kubernetes/networking/ingress.d.ts.map +1 -1
  534. package/dist/factories/kubernetes/networking/ingress.js +46 -57
  535. package/dist/factories/kubernetes/networking/ingress.js.map +1 -1
  536. package/dist/factories/kubernetes/networking/network-policy.d.ts +9 -0
  537. package/dist/factories/kubernetes/networking/network-policy.d.ts.map +1 -1
  538. package/dist/factories/kubernetes/networking/network-policy.js +51 -1
  539. package/dist/factories/kubernetes/networking/network-policy.js.map +1 -1
  540. package/dist/factories/kubernetes/networking/service.d.ts.map +1 -1
  541. package/dist/factories/kubernetes/networking/service.js +48 -40
  542. package/dist/factories/kubernetes/networking/service.js.map +1 -1
  543. package/dist/factories/kubernetes/policy/pod-disruption-budget.d.ts.map +1 -1
  544. package/dist/factories/kubernetes/policy/pod-disruption-budget.js +40 -46
  545. package/dist/factories/kubernetes/policy/pod-disruption-budget.js.map +1 -1
  546. package/dist/factories/kubernetes/policy/resource-quota.d.ts.map +1 -1
  547. package/dist/factories/kubernetes/policy/resource-quota.js +30 -34
  548. package/dist/factories/kubernetes/policy/resource-quota.js.map +1 -1
  549. package/dist/factories/kubernetes/storage/persistent-volume-claim.d.ts.map +1 -1
  550. package/dist/factories/kubernetes/storage/persistent-volume-claim.js +22 -21
  551. package/dist/factories/kubernetes/storage/persistent-volume-claim.js.map +1 -1
  552. package/dist/factories/kubernetes/storage/persistent-volume.d.ts.map +1 -1
  553. package/dist/factories/kubernetes/storage/persistent-volume.js +32 -38
  554. package/dist/factories/kubernetes/storage/persistent-volume.js.map +1 -1
  555. package/dist/factories/kubernetes/storage/volume-attachment.d.ts.map +1 -1
  556. package/dist/factories/kubernetes/storage/volume-attachment.js +12 -11
  557. package/dist/factories/kubernetes/storage/volume-attachment.js.map +1 -1
  558. package/dist/factories/kubernetes/workloads/cron-job.d.ts.map +1 -1
  559. package/dist/factories/kubernetes/workloads/cron-job.js +39 -34
  560. package/dist/factories/kubernetes/workloads/cron-job.js.map +1 -1
  561. package/dist/factories/kubernetes/workloads/daemon-set.d.ts.map +1 -1
  562. package/dist/factories/kubernetes/workloads/daemon-set.js +24 -23
  563. package/dist/factories/kubernetes/workloads/daemon-set.js.map +1 -1
  564. package/dist/factories/kubernetes/workloads/deployment.d.ts.map +1 -1
  565. package/dist/factories/kubernetes/workloads/deployment.js +45 -35
  566. package/dist/factories/kubernetes/workloads/deployment.js.map +1 -1
  567. package/dist/factories/kubernetes/workloads/job.d.ts.map +1 -1
  568. package/dist/factories/kubernetes/workloads/job.js +87 -77
  569. package/dist/factories/kubernetes/workloads/job.js.map +1 -1
  570. package/dist/factories/kubernetes/workloads/replica-set.d.ts.map +1 -1
  571. package/dist/factories/kubernetes/workloads/replica-set.js +24 -24
  572. package/dist/factories/kubernetes/workloads/replica-set.js.map +1 -1
  573. package/dist/factories/kubernetes/workloads/replication-controller.d.ts.map +1 -1
  574. package/dist/factories/kubernetes/workloads/replication-controller.js +37 -26
  575. package/dist/factories/kubernetes/workloads/replication-controller.js.map +1 -1
  576. package/dist/factories/kubernetes/workloads/stateful-set.d.ts.map +1 -1
  577. package/dist/factories/kubernetes/workloads/stateful-set.js +69 -55
  578. package/dist/factories/kubernetes/workloads/stateful-set.js.map +1 -1
  579. package/dist/factories/kubernetes/yaml/yaml-artifact-applier.d.ts +15 -0
  580. package/dist/factories/kubernetes/yaml/yaml-artifact-applier.d.ts.map +1 -0
  581. package/dist/factories/kubernetes/yaml/yaml-artifact-applier.js +69 -0
  582. package/dist/factories/kubernetes/yaml/yaml-artifact-applier.js.map +1 -0
  583. package/dist/factories/kubernetes/yaml/yaml-directory.d.ts +2 -1
  584. package/dist/factories/kubernetes/yaml/yaml-directory.d.ts.map +1 -1
  585. package/dist/factories/kubernetes/yaml/yaml-directory.js +3 -34
  586. package/dist/factories/kubernetes/yaml/yaml-directory.js.map +1 -1
  587. package/dist/factories/kubernetes/yaml/yaml-file.d.ts +13 -91
  588. package/dist/factories/kubernetes/yaml/yaml-file.d.ts.map +1 -1
  589. package/dist/factories/kubernetes/yaml/yaml-file.js +21 -311
  590. package/dist/factories/kubernetes/yaml/yaml-file.js.map +1 -1
  591. package/dist/factories/nats/compositions/nats-bootstrap.d.ts +2 -2
  592. package/dist/factories/nats/compositions/nats-bootstrap.d.ts.map +1 -1
  593. package/dist/factories/nats/compositions/nats-bootstrap.js +2 -3
  594. package/dist/factories/nats/compositions/nats-bootstrap.js.map +1 -1
  595. package/dist/factories/nats/resources/jetstream.d.ts.map +1 -1
  596. package/dist/factories/nats/resources/jetstream.js +2 -0
  597. package/dist/factories/nats/resources/jetstream.js.map +1 -1
  598. package/dist/factories/nats/types.d.ts +3 -3
  599. package/dist/factories/nats/types.js +1 -1
  600. package/dist/factories/nats/types.js.map +1 -1
  601. package/dist/factories/ory/compositions/ory-platform-stack.d.ts.map +1 -1
  602. package/dist/factories/ory/compositions/ory-platform-stack.js +35 -9
  603. package/dist/factories/ory/compositions/ory-platform-stack.js.map +1 -1
  604. package/dist/factories/ory/resources/oathkeeper-rule.d.ts.map +1 -1
  605. package/dist/factories/ory/resources/oathkeeper-rule.js +11 -8
  606. package/dist/factories/ory/resources/oathkeeper-rule.js.map +1 -1
  607. package/dist/factories/ory/resources/oauth2-client.d.ts.map +1 -1
  608. package/dist/factories/ory/resources/oauth2-client.js +12 -9
  609. package/dist/factories/ory/resources/oauth2-client.js.map +1 -1
  610. package/dist/factories/ory/types.d.ts +3 -0
  611. package/dist/factories/ory/types.d.ts.map +1 -1
  612. package/dist/factories/ory/types.js +1 -0
  613. package/dist/factories/ory/types.js.map +1 -1
  614. package/dist/factories/rook/compositions/rook-ceph-operator-installation.d.ts +2 -2
  615. package/dist/factories/rook/compositions/rook-ceph-operator-installation.d.ts.map +1 -1
  616. package/dist/factories/rook/compositions/rook-ceph-operator-installation.js +2 -3
  617. package/dist/factories/rook/compositions/rook-ceph-operator-installation.js.map +1 -1
  618. package/dist/factories/rook/resources/ceph-object-store-user.d.ts.map +1 -1
  619. package/dist/factories/rook/resources/ceph-object-store-user.js +2 -0
  620. package/dist/factories/rook/resources/ceph-object-store-user.js.map +1 -1
  621. package/dist/factories/rook/resources/ceph-object-store.d.ts.map +1 -1
  622. package/dist/factories/rook/resources/ceph-object-store.js +2 -0
  623. package/dist/factories/rook/resources/ceph-object-store.js.map +1 -1
  624. package/dist/factories/rook/resources/object-bucket-claim.d.ts.map +1 -1
  625. package/dist/factories/rook/resources/object-bucket-claim.js +2 -0
  626. package/dist/factories/rook/resources/object-bucket-claim.js.map +1 -1
  627. package/dist/factories/rook/types.d.ts +1 -1
  628. package/dist/factories/rook/types.js +1 -1
  629. package/dist/factories/rook/types.js.map +1 -1
  630. package/dist/factories/searxng/compositions/searxng-bootstrap.d.ts.map +1 -1
  631. package/dist/factories/searxng/compositions/searxng-bootstrap.js +2 -2
  632. package/dist/factories/searxng/compositions/searxng-bootstrap.js.map +1 -1
  633. package/dist/factories/searxng/resources/searxng.d.ts.map +1 -1
  634. package/dist/factories/searxng/resources/searxng.js +2 -0
  635. package/dist/factories/searxng/resources/searxng.js.map +1 -1
  636. package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts +2 -2
  637. package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts.map +1 -1
  638. package/dist/factories/valkey/compositions/valkey-bootstrap.js +4 -9
  639. package/dist/factories/valkey/compositions/valkey-bootstrap.js.map +1 -1
  640. package/dist/factories/valkey/resources/valkey.d.ts.map +1 -1
  641. package/dist/factories/valkey/resources/valkey.js +2 -1
  642. package/dist/factories/valkey/resources/valkey.js.map +1 -1
  643. package/dist/factories/valkey/types.d.ts +3 -7
  644. package/dist/factories/valkey/types.d.ts.map +1 -1
  645. package/dist/factories/valkey/types.js +1 -1
  646. package/dist/factories/valkey/types.js.map +1 -1
  647. package/dist/factories/webapp/compositions/web-app-with-processing.d.ts +3 -2
  648. package/dist/factories/webapp/compositions/web-app-with-processing.d.ts.map +1 -1
  649. package/dist/factories/webapp/compositions/web-app-with-processing.js +7 -6
  650. package/dist/factories/webapp/compositions/web-app-with-processing.js.map +1 -1
  651. package/dist/factories/webapp/types.d.ts +1 -0
  652. package/dist/factories/webapp/types.d.ts.map +1 -1
  653. package/dist/factories/webapp/types.js +2 -0
  654. package/dist/factories/webapp/types.js.map +1 -1
  655. package/dist/index.d.ts +2 -1
  656. package/dist/index.d.ts.map +1 -1
  657. package/dist/index.js +1 -1
  658. package/dist/index.js.map +1 -1
  659. package/dist/shared/brands.d.ts +7 -0
  660. package/dist/shared/brands.d.ts.map +1 -1
  661. package/dist/shared/brands.js +7 -0
  662. package/dist/shared/brands.js.map +1 -1
  663. package/dist/utils/index.d.ts +3 -3
  664. package/dist/utils/index.d.ts.map +1 -1
  665. package/dist/utils/index.js +3 -3
  666. package/dist/utils/index.js.map +1 -1
  667. package/package.json +20 -5
  668. package/dist/factories/kubernetes/yaml/conflict-handler.d.ts +0 -23
  669. package/dist/factories/kubernetes/yaml/conflict-handler.d.ts.map +0 -1
  670. package/dist/factories/kubernetes/yaml/conflict-handler.js +0 -83
  671. package/dist/factories/kubernetes/yaml/conflict-handler.js.map +0 -1
@@ -8,8 +8,10 @@ import { compile as compileExpression } from 'angular-expressions';
8
8
  import * as yaml from 'js-yaml';
9
9
  import { createAlchemyResourceId } from '../../alchemy/utilities.js';
10
10
  import { preserveNonEnumerableProperties } from '../../utils/helpers.js';
11
+ import { shortStableHash, toCamelCase } from '../../utils/string.js';
11
12
  import { isKubernetesRef } from '../../utils/type-guards.js';
12
13
  import { applyAspects } from '../aspects/apply.js';
14
+ import { copyCompositionAnalysisMetadata } from '../composition/analysis-metadata.js';
13
15
  import { createCompositionContext, runWithCompositionContext } from '../composition/context.js';
14
16
  import { buildNestedCompositionAliasTargets } from '../composition/nested-status-cel.js';
15
17
  import { DEFAULT_DEPLOYMENT_TIMEOUT, DEFAULT_KRO_INSTANCE_TIMEOUT, DEFAULT_RGD_TIMEOUT, } from '../config/defaults.js';
@@ -19,31 +21,33 @@ import { isStrictCelDiagnosticsEnabled } from '../expressions/analysis/strict-ce
19
21
  import { applyAnalysisToResources } from '../expressions/composition/composition-analyzer.js';
20
22
  import { createBunCompatibleKubernetesObjectApi } from '../kubernetes/index.js';
21
23
  import { getComponentLogger } from '../logging/index.js';
22
- import { namespace as namespaceResource } from '../../factories/kubernetes/core/namespace.js';
23
- import { shortStableHash, toCamelCase } from '../../utils/string.js';
24
24
  // Dependency inversion: kroCustomResource, resourceGraphDefinition, and
25
25
  // alchemy bridge are injected via FactoryOptions providers (Phase 3.5)
26
26
  // instead of dynamic import() from higher layers.
27
- import { copyResourceMetadata, getMetadataField, getReadinessEvaluator, getResourceScope, setMetadataField, setReadinessEvaluator, } from '../metadata/index.js';
28
- import { createAlwaysReadyEvaluator, ensureReadinessEvaluator } from '../readiness/index.js';
27
+ import { copyResourceMetadata, getMetadataField, getReadinessEvaluator, getResourceScope, setMetadataField, setReadinessEvaluator, setResourceId, } from '../metadata/index.js';
28
+ import { assertAdapterCapabilitiesSupported, canonicalStringify, collectArtifactOutputUses, collectPlanValueSensitiveBindings, compileKroArtifactPlan, createKroArtifactBundle, decodeKroArtifactBundle, encodeKroArtifactBundle, KRO_ARTIFACT_BINDINGS_SPEC_FIELD, kroArtifactOutputField, kroArtifactPlanToGraphResources, kroArtifactPlanToInstanceResource, kroArtifactPlanToSupportingResources, kroArtifactRequirementField, lowerPlanValue, mapPlanValueSensitiveBindings, materializeKroArtifactBundleOperation, materializePlanValue, mergeKroArtifactBundleOperations, orderKroArtifactBundleOperations, planValueSensitiveBindingNames, resolveStaticYamlSensitiveBindings, schemaToIR, } from '../planning/index.js';
29
+ import { createAlwaysReadyEvaluator, createCustomResourceDefinitionReadinessEvaluator, ensureReadinessEvaluator, getPortableReadinessStrategy, resolvePortableReadinessStrategy, } from '../readiness/index.js';
29
30
  import { createSchemaProxy, DeploymentMode } from '../references/index.js';
31
+ import { runStandaloneOperation } from '../runtime/standalone-operation.js';
30
32
  import { isStaticExpression } from '../serialization/cel-references.js';
31
33
  import { applyTernaryConditionalsToResources } from '../serialization/kro-post-processing.js';
32
34
  import { generateKroSchemaFromArktype } from '../serialization/schema.js';
33
- import { serializeResourceGraphToYaml } from '../serialization/yaml.js';
35
+ import { serializeResourceGraphDefinitionToYaml, serializeResourceGraphToManifest, } from '../serialization/yaml.js';
34
36
  import { getSingletonResourceId } from '../singleton/singleton.js';
35
37
  import { validateStatusCelExpressions } from '../validation/cel-validator.js';
36
38
  import { KubernetesClientManager } from './client-provider-manager.js';
39
+ import { blockerForRemainingResource, createDeletionResultState, deletionTarget, finishDeletionResult, readDeletionResourceIdentity, } from './deletion-result.js';
37
40
  import { DirectDeploymentEngine } from './engine.js';
38
41
  import { logHandleSnapshot } from './handle-tracing.js';
39
42
  import { isNotFoundError } from './k8s-helpers.js';
40
- import { assertKroInstanceNamespaceOwnershipSafe, assertNoHoistWeakenedStatusFields, assertSingletonOwnerNamespaceOwnershipSafe, concreteActiveOwnedResources, concreteOwnedNamespaceResources, findDanglingHoistedReference, resolveConcreteMetadataValue, resolveNamespaceName, rewriteHoistedNamespaceReferences, rewriteHoistedNamespaceRefsInValue, selectHoistedNamespaces, } from './kro-instance-safety.js';
43
+ import { assertKroInstanceSpecPreserved } from './kro-instance-admission.js';
44
+ import { assertKroInstanceNamespaceOwnershipSafe, assertNoHoistWeakenedStatusFields, concreteActiveOwnedResources, concreteOwnedNamespaceResources, findDanglingHoistedReference, resolveConcreteMetadataValue, resolveNamespaceName, rewriteHoistedNamespaceReferences, rewriteHoistedNamespaceRefsInValue, selectHoistedNamespaces, } from './kro-instance-safety.js';
41
45
  import { decideNamespaceOwnershipCreateFirst, deleteNamespaceIfEmpty, HOISTED_NAMESPACES_ANNOTATION, listNamespacesOwnedByRgd, NAMESPACE_OWNER_ANNOTATION, parseHoistedNamespacesAnnotation, readHoistedNamespacesRecord, } from './kro-namespace-teardown.js';
42
- import { createRollbackManager } from './rollback-manager.js';
43
46
  import { waitForKroInstanceReady as waitForKroInstanceReadyShared } from './kro-readiness.js';
47
+ import { createRollbackManager } from './rollback-manager.js';
44
48
  import { evaluateSchemaCelExpression } from './schema-cel-evaluator.js';
45
49
  import { convertToKubernetesName, extractSerializableKubeConfigOptions, generateInstanceName, getSingletonInstanceName, pluralizeKind, validateSpec, } from './shared-utilities.js';
46
- import { joinYamlDocuments, singletonOwnerInstanceYamls, singletonRgdYamls, } from './singleton-gitops.js';
50
+ import { joinYamlDocuments, singletonOwnerInstanceManifests, singletonRgdYamls, } from './singleton-gitops.js';
47
51
  import { assertNoDeployedSingletonSpecDrift, singletonSpecFingerprintAnnotationValue, } from './singleton-owner-drift.js';
48
52
  /**
49
53
  * Label stamped on every KRO instance CR this factory creates, keyed to the
@@ -51,6 +55,48 @@ import { assertNoDeployedSingletonSpecDrift, singletonSpecFingerprintAnnotationV
51
55
  * to decide whether other instances still share the RGD.
52
56
  */
53
57
  const INSTANCE_RGD_LABEL = 'typekro.io/rgd';
58
+ function uniqueArtifactRequirements(requirements) {
59
+ const byId = new Map();
60
+ for (const requirement of requirements) {
61
+ const prior = byId.get(requirement.id);
62
+ if (prior && canonicalStringify(prior) !== canonicalStringify(requirement)) {
63
+ throw new TypeKroError(`Artifact requirement ${requirement.id} has conflicting definitions across KRO bundle members.`, 'KRO_ARTIFACT_REQUIREMENT_CONFLICT', { requirementId: requirement.id });
64
+ }
65
+ byId.set(requirement.id, requirement);
66
+ }
67
+ return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id));
68
+ }
69
+ function uniqueCapabilityRequirements(requirements) {
70
+ return [
71
+ ...new Map(requirements.map((requirement) => [canonicalStringify(requirement), requirement])).values(),
72
+ ].sort((left, right) => canonicalStringify(left).localeCompare(canonicalStringify(right)));
73
+ }
74
+ function kroBundleOperationId(resource) {
75
+ const name = resource.metadata?.name;
76
+ if (typeof resource.apiVersion !== 'string' || typeof resource.kind !== 'string' || !name) {
77
+ throw new TypeKroError('Cannot identify KRO bundle operation without a complete Kubernetes identity.', 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { apiVersion: resource.apiVersion, kind: resource.kind, name });
78
+ }
79
+ return [
80
+ 'k8s',
81
+ resource.apiVersion,
82
+ resource.kind,
83
+ resource.metadata?.namespace ?? '_cluster',
84
+ name,
85
+ ]
86
+ .map((part) => encodeURIComponent(part))
87
+ .join(':');
88
+ }
89
+ function kroMemberSensitiveBinding(memberId, binding) {
90
+ return binding.startsWith('spec/') ? `kro/${encodeURIComponent(memberId)}/${binding}` : binding;
91
+ }
92
+ function mergeSensitiveBindingEnvelopes(target, incoming) {
93
+ for (const [binding, value] of Object.entries(incoming)) {
94
+ if (Object.hasOwn(target, binding) && !Object.is(target[binding], value)) {
95
+ throw new TypeKroError(`Sensitive binding ${binding} resolved to conflicting values across KRO bundle members.`, 'KRO_SENSITIVE_BINDING_CONFLICT', { binding });
96
+ }
97
+ target[binding] = value;
98
+ }
99
+ }
54
100
  /**
55
101
  * Decide whether the RGD/CRD should be preserved after a `deleteInstance`
56
102
  * call, i.e., whether other instances still depend on it.
@@ -92,6 +138,100 @@ instances, targetName, instanceDeleted, targetNamespace) {
92
138
  : instances;
93
139
  return others.length > 0;
94
140
  }
141
+ function recordNamespaceDeletionOutcome(state, namespace, outcome) {
142
+ const resource = deletionTarget('v1', 'Namespace', namespace);
143
+ if (outcome.status === 'deleted' || outcome.status === 'absent') {
144
+ // A bounded descendant-drain retry may first observe the Namespace as occupied and then
145
+ // confirm it gone. Keep the final DTO truthful by removing interim retention/blocker evidence.
146
+ const replaceWithFilteredEntries = (entries, keep) => {
147
+ entries.splice(0, entries.length, ...entries.filter(keep));
148
+ };
149
+ replaceWithFilteredEntries(state.retained, (entry) => !(entry.resource.kind === 'Namespace' && entry.resource.name === namespace));
150
+ replaceWithFilteredEntries(state.remaining, (entry) => !(entry.kind === 'Namespace' && entry.name === namespace));
151
+ replaceWithFilteredEntries(state.blockers, (entry) => !(entry.resource?.kind === 'Namespace' && entry.resource.name === namespace));
152
+ state.deleted.push(resource);
153
+ return;
154
+ }
155
+ if (outcome.cause === 'not-owned') {
156
+ state.retained.push({ resource, policy: 'adopted-resource', reason: outcome.reason });
157
+ return;
158
+ }
159
+ if (outcome.cause === 'occupied') {
160
+ state.retained.push({ resource, policy: 'occupied-namespace', reason: outcome.reason });
161
+ return;
162
+ }
163
+ state.retained.push({
164
+ resource,
165
+ policy: 'safety-proof-unavailable',
166
+ reason: outcome.reason,
167
+ });
168
+ state.remaining.push(resource);
169
+ state.blockers.push({
170
+ code: outcome.cause === 'read-failed' ? 'OWNERSHIP_UNPROVEN' : 'DISCOVERY_FAILED',
171
+ message: outcome.reason,
172
+ resource,
173
+ retryable: true,
174
+ retryGuidance: 'Retry after Kubernetes discovery and namespace reads are available; TypeKro retained the namespace to avoid destructive cleanup.',
175
+ });
176
+ }
177
+ const DESCENDANT_PRODUCING_KINDS = new Set([
178
+ 'CronJob',
179
+ 'DaemonSet',
180
+ 'Deployment',
181
+ 'HelmRelease',
182
+ 'Job',
183
+ 'Kustomization',
184
+ 'ReplicaSet',
185
+ 'ReplicationController',
186
+ 'StatefulSet',
187
+ ]);
188
+ /**
189
+ * Capture dependents that Kubernetes may orphan when KRO removes their direct
190
+ * controller before garbage collection completes. The UID match is the
191
+ * ownership proof: a same-named Pod from a replacement Job is never adopted.
192
+ */
193
+ async function captureKnownControllerDescendants(k8sApi, childTargets, defaultNamespace) {
194
+ const descendants = [];
195
+ for (const target of childTargets) {
196
+ if (target.kind !== 'Job')
197
+ continue;
198
+ const namespace = target.namespace ?? defaultNamespace;
199
+ let live;
200
+ try {
201
+ live = await k8sApi.read({
202
+ apiVersion: target.apiVersion,
203
+ kind: target.kind,
204
+ metadata: { name: target.name, namespace },
205
+ });
206
+ }
207
+ catch (error) {
208
+ const statusCode = error.statusCode ??
209
+ error.body?.code;
210
+ if (statusCode === 404)
211
+ continue;
212
+ throw error;
213
+ }
214
+ const uid = live.metadata?.uid;
215
+ if (!uid) {
216
+ throw new Error(`Cannot prove descendant ownership for Job/${target.name}: UID is missing`);
217
+ }
218
+ const pods = await k8sApi.list('v1', 'Pod', namespace, undefined, undefined, undefined, undefined, `batch.kubernetes.io/controller-uid=${uid}`);
219
+ for (const pod of pods.items) {
220
+ const podName = pod.metadata?.name;
221
+ const controlledByJob = pod.metadata?.ownerReferences?.some((owner) => owner.uid === uid && owner.kind === 'Job' && owner.controller !== false);
222
+ if (!podName || !controlledByJob)
223
+ continue;
224
+ descendants.push({
225
+ apiVersion: 'v1',
226
+ kind: 'Pod',
227
+ name: podName,
228
+ namespace,
229
+ owner: { ...target, namespace, uid },
230
+ });
231
+ }
232
+ }
233
+ return descendants;
234
+ }
95
235
  function deepMergeStatusPlaceholders(staticFields, dynamicFields) {
96
236
  const merged = { ...staticFields };
97
237
  for (const [key, dynamicValue] of Object.entries(dynamicFields)) {
@@ -167,6 +307,16 @@ export class KroResourceFactoryImpl {
167
307
  this.singletonDefinitions = options.singletonDefinitions ?? [];
168
308
  this.factoryOptions = options;
169
309
  this.clientManager = new KubernetesClientManager(options);
310
+ const specSchema = schemaToIR(schemaDefinition.spec);
311
+ if (specSchema.root.kind === 'object' &&
312
+ specSchema.root.properties.some((property) => property.name === KRO_ARTIFACT_BINDINGS_SPEC_FIELD)) {
313
+ throw new TypeKroError(`Spec field ${KRO_ARTIFACT_BINDINGS_SPEC_FIELD} is reserved for TypeKro KRO provider bindings.`, 'KRO_RESERVED_SPEC_FIELD', {
314
+ field: KRO_ARTIFACT_BINDINGS_SPEC_FIELD,
315
+ surface: 'schema',
316
+ factory: name,
317
+ policyVersion: 1,
318
+ });
319
+ }
170
320
  // Pass the Arktype JSON so the proxy is shape-aware: spread
171
321
  // (`{ ...schema.spec.X }`) enumerates declared fields and
172
322
  // `Object.keys(schema.spec.X)` returns them. See the docstring on
@@ -180,7 +330,9 @@ export class KroResourceFactoryImpl {
180
330
  }
181
331
  /** Extract nested composition status CEL mappings from the raw status object. */
182
332
  getNestedStatusCel() {
183
- return this.statusMappings?.__nestedStatusCel;
333
+ if (!this.statusMappings)
334
+ return undefined;
335
+ return Object.getOwnPropertyDescriptor(this.statusMappings, '__nestedStatusCel')?.value;
184
336
  }
185
337
  getSchemaVersion() {
186
338
  return this.schemaDefinition.apiVersion.includes('/')
@@ -197,6 +349,19 @@ export class KroResourceFactoryImpl {
197
349
  getInstanceApiVersion() {
198
350
  return `${this.getSchemaGroup()}/${this.getSchemaVersion()}`;
199
351
  }
352
+ assertNoReservedKroSpecField(spec) {
353
+ if (!spec ||
354
+ typeof spec !== 'object' ||
355
+ !Object.hasOwn(spec, KRO_ARTIFACT_BINDINGS_SPEC_FIELD)) {
356
+ return;
357
+ }
358
+ throw new TypeKroError(`Spec field ${KRO_ARTIFACT_BINDINGS_SPEC_FIELD} is reserved for TypeKro KRO provider bindings.`, 'KRO_RESERVED_SPEC_FIELD', {
359
+ field: KRO_ARTIFACT_BINDINGS_SPEC_FIELD,
360
+ surface: 'instance-spec',
361
+ factory: this.name,
362
+ policyVersion: 1,
363
+ });
364
+ }
200
365
  /**
201
366
  * Resolve the namespace the KRO instance CR lives in for this call.
202
367
  *
@@ -310,6 +475,302 @@ export class KroResourceFactoryImpl {
310
475
  const remaining = Object.fromEntries(Object.entries(resources).filter(([id]) => !hoistIds.has(id)));
311
476
  return rewriteHoistedNamespaceReferences(remaining, hoistIds);
312
477
  }
478
+ /**
479
+ * Compile captured symbolic intent back into the established serializer input.
480
+ * Legacy resources remain a fallback for factories created outside the
481
+ * composition capture frontend.
482
+ */
483
+ plannedResources() {
484
+ const capture = this.factoryOptions.semanticCapture;
485
+ if (!capture) {
486
+ return this.resources;
487
+ }
488
+ const configuredPlan = this.factoryOptions.plan ?? {};
489
+ const plan = capture.planTemplate({
490
+ ...configuredPlan,
491
+ aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
492
+ });
493
+ const outerInputs = this.outerArtifactCompilationInputs();
494
+ const artifacts = compileKroArtifactPlan(plan, {
495
+ strict: true,
496
+ rgdName: this.rgdName,
497
+ ...outerInputs,
498
+ ...(this.factoryOptions.applyPolicy
499
+ ? { outerApplyPolicy: this.factoryOptions.applyPolicy }
500
+ : {}),
501
+ });
502
+ return kroArtifactPlanToGraphResources(artifacts);
503
+ }
504
+ /** Compile this factory's local graph and directly managed outer artifacts once. */
505
+ compiledKroArtifactPlan(spec, options = {}) {
506
+ const capture = this.factoryOptions.semanticCapture;
507
+ if (!capture)
508
+ return undefined;
509
+ const configuredPlan = this.factoryOptions.plan ?? {};
510
+ const planOptions = {
511
+ ...configuredPlan,
512
+ aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
513
+ };
514
+ const plan = capture.planTemplate(planOptions);
515
+ const concretePlan = spec === undefined ? undefined : capture.planSymbolic(spec, planOptions);
516
+ const memberId = options.memberId ?? 'root';
517
+ const instanceSpec = concretePlan
518
+ ? mapPlanValueSensitiveBindings(concretePlan.spec, (binding) => kroMemberSensitiveBinding(memberId, binding))
519
+ : undefined;
520
+ const sensitiveBindings = spec !== undefined && instanceSpec
521
+ ? collectPlanValueSensitiveBindings(instanceSpec, spec, `$.members.${memberId}.instance.spec`)
522
+ : {};
523
+ const instanceName = spec === undefined
524
+ ? undefined
525
+ : (options.instanceNameOverride ?? generateInstanceName(spec, this.name));
526
+ const metadata = spec === undefined
527
+ ? undefined
528
+ : this.instanceManifestMetadata(spec, options.singletonSpecFingerprint);
529
+ const artifacts = compileKroArtifactPlan(plan, {
530
+ strict: true,
531
+ rgdName: this.rgdName,
532
+ ...this.outerArtifactCompilationInputs(spec),
533
+ ...(this.factoryOptions.applyPolicy
534
+ ? { outerApplyPolicy: this.factoryOptions.applyPolicy }
535
+ : {}),
536
+ ...(spec !== undefined && instanceName && metadata && instanceSpec
537
+ ? {
538
+ instance: {
539
+ name: lowerPlanValue(instanceName).value,
540
+ namespace: lowerPlanValue(this.resolveInstanceNamespace(spec)).value,
541
+ apiVersion: this.getInstanceApiVersion(),
542
+ kind: this.schemaDefinition.kind,
543
+ spec: instanceSpec,
544
+ labels: lowerPlanValue(metadata.labels).value,
545
+ annotations: lowerPlanValue(metadata.annotations).value,
546
+ },
547
+ }
548
+ : {}),
549
+ });
550
+ return { artifacts, sensitiveBindings };
551
+ }
552
+ /**
553
+ * Recursively compile local plans into physical outer operations. Cross-member
554
+ * singleton edges resolve to the owner's concrete instance operation.
555
+ */
556
+ compileKroBundleAssembly(spec, options) {
557
+ const compilation = this.compiledKroArtifactPlan(spec, options);
558
+ if (!compilation)
559
+ return undefined;
560
+ const { artifacts } = compilation;
561
+ const sensitiveBindings = {
562
+ ...compilation.sensitiveBindings,
563
+ };
564
+ const childOperations = [];
565
+ const requiredCapabilities = [...artifacts.requiredCapabilities];
566
+ const artifactRequirements = [...artifacts.artifactRequirements];
567
+ const singletonOperationByPhysicalId = new Map();
568
+ const definitions = spec === undefined ? this.singletonDefinitions : this.discoverSingletonDefinitions(spec);
569
+ for (const definition of definitions) {
570
+ if (options.singletonStack.includes(definition.key)) {
571
+ throw new TypeKroError(`Singleton dependency cycle detected: ${[...options.singletonStack, definition.key].join(' -> ')}`, 'KRO_SINGLETON_DEPENDENCY_CYCLE', { singletonKey: definition.key });
572
+ }
573
+ const singletonFactory = this.singletonFactoryFor(definition);
574
+ const child = singletonFactory.compileKroBundleAssembly(definition.spec, {
575
+ memberId: `singleton:${definition.key}`,
576
+ root: false,
577
+ instanceNameOverride: getSingletonInstanceName(definition.id),
578
+ singletonSpecFingerprint: singletonSpecFingerprintAnnotationValue(definition.specFingerprint),
579
+ singletonStack: [...options.singletonStack, definition.key],
580
+ });
581
+ if (!child?.instanceOperationId) {
582
+ throw new TypeKroError(`Singleton owner ${definition.key} did not compile a concrete instance operation.`, 'KRO_SINGLETON_OWNER_ARTIFACT_MISSING', { singletonKey: definition.key });
583
+ }
584
+ mergeSensitiveBindingEnvelopes(sensitiveBindings, child.sensitiveBindings);
585
+ childOperations.push(...child.operations);
586
+ requiredCapabilities.push(...child.requiredCapabilities);
587
+ artifactRequirements.push(...child.artifactRequirements);
588
+ const ownerOperation = child.operations.find((operation) => operation.id === child.instanceOperationId);
589
+ if (!ownerOperation) {
590
+ throw new TypeKroError(`Singleton owner ${definition.key} instance operation is absent from its bundle.`, 'KRO_SINGLETON_OWNER_ARTIFACT_MISSING', { singletonKey: definition.key, operationId: child.instanceOperationId });
591
+ }
592
+ singletonOperationByPhysicalId.set(ownerOperation.id, ownerOperation.id);
593
+ }
594
+ const source = {
595
+ memberId: options.memberId,
596
+ planIdentityDigest: artifacts.planIdentityDigest,
597
+ compiledArtifactDigest: artifacts.compiledArtifactDigest,
598
+ };
599
+ const operations = [...childOperations];
600
+ const operationIdByArtifactId = new Map();
601
+ for (const artifact of artifacts.resources.filter((candidate) => candidate.role === 'singleton-owner')) {
602
+ if (!artifact.identity) {
603
+ throw new TypeKroError(`Singleton artifact ${artifact.id} has no physical identity.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { artifactId: artifact.id });
604
+ }
605
+ const name = materializePlanValue(artifact.identity.name, {
606
+ sensitive: sensitiveBindings,
607
+ });
608
+ const namespace = artifact.identity.namespace
609
+ ? materializePlanValue(artifact.identity.namespace, {
610
+ sensitive: sensitiveBindings,
611
+ })
612
+ : undefined;
613
+ if (typeof name !== 'string' || (namespace !== undefined && typeof namespace !== 'string')) {
614
+ throw new TypeKroError(`Singleton artifact ${artifact.id} has a non-concrete physical identity.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { artifactId: artifact.id, name, namespace });
615
+ }
616
+ const physicalId = kroBundleOperationId({
617
+ apiVersion: artifact.identity.apiVersion,
618
+ kind: artifact.identity.kind,
619
+ metadata: { name, ...(namespace ? { namespace } : {}) },
620
+ });
621
+ const ownerOperationId = singletonOperationByPhysicalId.get(physicalId);
622
+ if (!ownerOperationId) {
623
+ throw new TypeKroError(`Singleton artifact ${artifact.id} has no compiled owner member.`, 'KRO_SINGLETON_OWNER_ARTIFACT_MISSING', { artifactId: artifact.id, physicalId });
624
+ }
625
+ operationIdByArtifactId.set(artifact.id, ownerOperationId);
626
+ }
627
+ const nonSingletonArtifacts = {
628
+ ...artifacts,
629
+ resources: artifacts.resources.filter((artifact) => artifact.role !== 'singleton-owner'),
630
+ };
631
+ for (const { artifact, resource } of kroArtifactPlanToSupportingResources(nonSingletonArtifacts, {
632
+ readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
633
+ resolveReadinessStrategy: resolvePortableReadinessStrategy,
634
+ })) {
635
+ if (artifact.role === 'singleton-owner') {
636
+ throw new TypeKroError(`Singleton artifact ${artifact.id} bypassed identity-only bundle lowering.`, 'KRO_SINGLETON_OWNER_ARTIFACT_INVALID', { artifactId: artifact.id });
637
+ }
638
+ const physicalId = kroBundleOperationId(resource);
639
+ operationIdByArtifactId.set(artifact.id, physicalId);
640
+ operations.push({
641
+ id: physicalId,
642
+ role: artifact.role,
643
+ sources: [source],
644
+ artifact,
645
+ manifest: lowerPlanValue(resource).value,
646
+ dependencies: [],
647
+ });
648
+ }
649
+ const rgdArtifact = artifacts.resources.find((artifact) => artifact.role === 'resource-graph-definition');
650
+ if (!rgdArtifact || rgdArtifact.role !== 'resource-graph-definition') {
651
+ throw new TypeKroError(`KRO plan ${artifacts.compiledArtifactDigest} has no RGD artifact.`, 'KRO_RGD_ARTIFACT_MISSING');
652
+ }
653
+ const rgdManifest = this.buildRgdManifest();
654
+ const rgdOperationId = kroBundleOperationId(rgdManifest);
655
+ operationIdByArtifactId.set(rgdArtifact.id, rgdOperationId);
656
+ operations.push({
657
+ id: rgdOperationId,
658
+ role: options.root ? 'resource-graph-definition' : 'singleton-owner-rgd',
659
+ sources: [source],
660
+ artifact: rgdArtifact,
661
+ manifest: lowerPlanValue(rgdManifest).value,
662
+ dependencies: [],
663
+ });
664
+ let instanceOperationId;
665
+ if (spec !== undefined) {
666
+ const instanceArtifact = artifacts.resources.find((artifact) => artifact.role === 'instance');
667
+ if (!instanceArtifact || instanceArtifact.role !== 'instance') {
668
+ throw new TypeKroError(`KRO plan ${artifacts.compiledArtifactDigest} has no instance artifact.`, 'KRO_INSTANCE_ARTIFACT_MISSING');
669
+ }
670
+ if (!instanceArtifact.identity) {
671
+ throw new TypeKroError(`KRO instance artifact ${instanceArtifact.id} has no physical identity.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { artifactId: instanceArtifact.id });
672
+ }
673
+ const instanceName = materializePlanValue(instanceArtifact.identity.name, {
674
+ sensitive: sensitiveBindings,
675
+ });
676
+ const instanceNamespace = instanceArtifact.identity.namespace
677
+ ? materializePlanValue(instanceArtifact.identity.namespace, {
678
+ sensitive: sensitiveBindings,
679
+ })
680
+ : undefined;
681
+ if (typeof instanceName !== 'string' ||
682
+ (instanceNamespace !== undefined && typeof instanceNamespace !== 'string')) {
683
+ throw new TypeKroError(`KRO instance artifact ${instanceArtifact.id} has a non-concrete physical identity.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { artifactId: instanceArtifact.id, instanceName, instanceNamespace });
684
+ }
685
+ instanceOperationId = kroBundleOperationId({
686
+ apiVersion: instanceArtifact.identity.apiVersion,
687
+ kind: instanceArtifact.identity.kind,
688
+ metadata: {
689
+ name: instanceName,
690
+ ...(instanceNamespace ? { namespace: instanceNamespace } : {}),
691
+ },
692
+ });
693
+ operationIdByArtifactId.set(instanceArtifact.id, instanceOperationId);
694
+ operations.push({
695
+ id: instanceOperationId,
696
+ role: options.root ? 'instance' : 'singleton-owner-instance',
697
+ sources: [source],
698
+ artifact: instanceArtifact,
699
+ manifest: instanceArtifact.desired,
700
+ dependencies: [],
701
+ });
702
+ }
703
+ const dependencies = new Map();
704
+ for (const edge of artifacts.edges) {
705
+ let prerequisite;
706
+ let dependent;
707
+ switch (edge.kind) {
708
+ case 'output':
709
+ prerequisite = operationIdByArtifactId.get(edge.producer);
710
+ dependent = operationIdByArtifactId.get(edge.consumer);
711
+ break;
712
+ case 'existence':
713
+ case 'ready':
714
+ prerequisite = operationIdByArtifactId.get(edge.prerequisite);
715
+ dependent = operationIdByArtifactId.get(edge.dependent);
716
+ break;
717
+ case 'ownership':
718
+ prerequisite = operationIdByArtifactId.get(edge.owner);
719
+ dependent = operationIdByArtifactId.get(edge.child);
720
+ break;
721
+ case 'delete-after':
722
+ break;
723
+ }
724
+ if (!prerequisite || !dependent || prerequisite === dependent)
725
+ continue;
726
+ const incoming = dependencies.get(dependent) ?? new Set();
727
+ incoming.add(prerequisite);
728
+ dependencies.set(dependent, incoming);
729
+ }
730
+ return {
731
+ operations: mergeKroArtifactBundleOperations(operations.map((operation) => ({
732
+ ...operation,
733
+ dependencies: [
734
+ ...new Set([
735
+ ...operation.dependencies,
736
+ ...(dependencies.get(operation.id) ?? new Set()),
737
+ ]),
738
+ ],
739
+ }))),
740
+ requiredCapabilities: uniqueCapabilityRequirements(requiredCapabilities),
741
+ artifactRequirements: uniqueArtifactRequirements(artifactRequirements),
742
+ rgdOperationId,
743
+ ...(instanceOperationId ? { instanceOperationId } : {}),
744
+ sensitiveBindings,
745
+ };
746
+ }
747
+ /** Compile, canonicalize, and decode the complete KRO outer bundle. */
748
+ compiledKroArtifactBundle(spec, options = {}) {
749
+ const assembly = this.compileKroBundleAssembly(spec, {
750
+ memberId: 'root',
751
+ root: true,
752
+ ...options,
753
+ singletonStack: [],
754
+ });
755
+ if (!assembly)
756
+ return undefined;
757
+ const bundle = createKroArtifactBundle({
758
+ root: {
759
+ memberId: 'root',
760
+ rgdOperationId: assembly.rgdOperationId,
761
+ ...(assembly.instanceOperationId
762
+ ? { instanceOperationId: assembly.instanceOperationId }
763
+ : {}),
764
+ },
765
+ operations: assembly.operations,
766
+ requiredCapabilities: assembly.requiredCapabilities,
767
+ artifactRequirements: assembly.artifactRequirements,
768
+ });
769
+ return {
770
+ bundle: decodeKroArtifactBundle(encodeKroArtifactBundle(bundle)),
771
+ sensitiveBindings: assembly.sensitiveBindings,
772
+ };
773
+ }
313
774
  /**
314
775
  * Idempotently create the factory's target namespace if it doesn't
315
776
  * exist. KRO does not auto-create the CR's containing namespace, and
@@ -320,13 +781,15 @@ export class KroResourceFactoryImpl {
320
781
  * tolerance — ignored if the namespace already exists so concurrent
321
782
  * callers don't collide.
322
783
  */
323
- async ensureTargetNamespace(namespace = this.namespace) {
784
+ async ensureTargetNamespace(namespace = this.namespace, abortSignal) {
324
785
  try {
786
+ abortSignal?.throwIfAborted();
325
787
  const { createBunCompatibleKubernetesObjectApi } = await import('../kubernetes/bun-api-client.js');
326
788
  const k8sApi = createBunCompatibleKubernetesObjectApi(this.getKubeConfig());
327
789
  const waitForNamespaceDeletion = async () => {
328
790
  const start = Date.now();
329
791
  while (Date.now() - start < 120000) {
792
+ abortSignal?.throwIfAborted();
330
793
  try {
331
794
  const existing = (await k8sApi.read({
332
795
  apiVersion: 'v1',
@@ -345,7 +808,25 @@ export class KroResourceFactoryImpl {
345
808
  }
346
809
  throw pollError;
347
810
  }
348
- await new Promise((resolve) => setTimeout(resolve, 1000));
811
+ await new Promise((resolve, reject) => {
812
+ if (!abortSignal) {
813
+ setTimeout(resolve, 1000);
814
+ return;
815
+ }
816
+ const onAbort = () => {
817
+ clearTimeout(timeout);
818
+ reject(abortSignal.reason ?? new DOMException('The operation was aborted', 'AbortError'));
819
+ };
820
+ const timeout = setTimeout(() => {
821
+ abortSignal.removeEventListener('abort', onAbort);
822
+ resolve();
823
+ }, 1000);
824
+ if (abortSignal.aborted) {
825
+ onAbort();
826
+ return;
827
+ }
828
+ abortSignal.addEventListener('abort', onAbort, { once: true });
829
+ });
349
830
  }
350
831
  throw new Error(`Namespace ${namespace} is still terminating after 120000ms`);
351
832
  };
@@ -559,20 +1040,40 @@ export class KroResourceFactoryImpl {
559
1040
  * Deploy a new instance by creating a custom resource
560
1041
  */
561
1042
  async deploy(spec, opts) {
1043
+ if (opts?.operationSignal) {
1044
+ return this.deployWithinOperation(spec, opts, opts.operationSignal);
1045
+ }
1046
+ return runStandaloneOperation((abortSignal) => this.deployWithinOperation(spec, { ...opts, operationSignal: abortSignal }, abortSignal), { abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal] });
1047
+ }
1048
+ async deployWithinOperation(spec, opts, abortSignal) {
562
1049
  if (opts?.targetScopes !== undefined) {
563
1050
  throw new TypeKroError('Scope-targeted deployment is not supported in KRO mode. KRO manages resource lifecycle via its own controller. Use direct mode for scope-targeted deploys.', 'UNSUPPORTED_OPTION', { targetScopes: opts.targetScopes, mode: 'kro' });
564
1051
  }
1052
+ this.assertNoReservedKroSpecField(spec);
565
1053
  // Validate spec against ArkType schema
566
1054
  validateSpec(spec, this.schemaDefinition, {
567
1055
  kind: this.schemaDefinition.kind,
568
1056
  name: this.name,
569
1057
  });
570
1058
  this.assertInstanceNamespaceOwnershipSafe(spec);
571
- this.assertSingletonOwnersNamespaceOwnershipSafe(spec);
572
- // Execute closures before RGD creation (Kro mode requirement)
573
- await this.executeClosuresBeforeRGD(spec);
574
- await this.ensureSingletonOwners(spec);
575
- return this.deployDirect(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
1059
+ const compiledBundle = this.compiledKroArtifactBundle(spec, {
1060
+ ...(opts?.instanceNameOverride ? { instanceNameOverride: opts.instanceNameOverride } : {}),
1061
+ ...(opts?.singletonSpecFingerprint
1062
+ ? { singletonSpecFingerprint: opts.singletonSpecFingerprint }
1063
+ : {}),
1064
+ });
1065
+ this.assertBundleCapabilities(compiledBundle?.bundle, {
1066
+ host: 'standalone',
1067
+ output: 'live',
1068
+ });
1069
+ // Execute compatibility closures only after the complete semantic bundle
1070
+ // has passed host/output capability negotiation, but before RGD creation.
1071
+ await this.executeClosuresBeforeRGD(spec, abortSignal);
1072
+ if (compiledBundle) {
1073
+ return this.deployKroArtifactBundle(compiledBundle.bundle, spec, compiledBundle.sensitiveBindings, abortSignal);
1074
+ }
1075
+ await this.ensureSingletonOwners(spec, abortSignal);
1076
+ return this.deployDirect(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint, abortSignal);
576
1077
  }
577
1078
  /**
578
1079
  * Discover the singleton-owner definitions this composition depends on (deduped by key): from
@@ -596,6 +1097,58 @@ export class KroResourceFactoryImpl {
596
1097
  }
597
1098
  return Array.from(discovered.values());
598
1099
  }
1100
+ /**
1101
+ * Recreate only the runtime member contexts needed to execute a decoded KRO
1102
+ * bundle. Desired state and ordering remain authoritative in the bundle;
1103
+ * these contexts provide existing operational hooks, clients, and status
1104
+ * hydration for each compiled member.
1105
+ */
1106
+ collectKroBundleRuntimeMembers(spec) {
1107
+ const rootFactory = this;
1108
+ const members = new Map([
1109
+ [
1110
+ 'root',
1111
+ {
1112
+ memberId: 'root',
1113
+ factory: rootFactory,
1114
+ spec,
1115
+ consumers: [],
1116
+ dispose: false,
1117
+ },
1118
+ ],
1119
+ ]);
1120
+ const visit = (member, stack) => {
1121
+ for (const definition of member.factory.discoverSingletonDefinitions(member.spec)) {
1122
+ if (stack.includes(definition.key)) {
1123
+ throw new TypeKroError(`Singleton dependency cycle detected: ${[...stack, definition.key].join(' -> ')}`, 'KRO_SINGLETON_DEPENDENCY_CYCLE', { singletonKey: definition.key });
1124
+ }
1125
+ const memberId = `singleton:${definition.key}`;
1126
+ const consumer = { factory: member.factory, definition };
1127
+ const existing = members.get(memberId);
1128
+ if (existing) {
1129
+ existing.consumers.push(consumer);
1130
+ continue;
1131
+ }
1132
+ const childFactory = member.factory.singletonFactoryFor(definition);
1133
+ const child = {
1134
+ memberId,
1135
+ factory: childFactory,
1136
+ spec: definition.spec,
1137
+ definition,
1138
+ consumers: [consumer],
1139
+ dispose: true,
1140
+ };
1141
+ members.set(memberId, child);
1142
+ visit(child, [...stack, definition.key]);
1143
+ }
1144
+ };
1145
+ const root = members.get('root');
1146
+ if (!root) {
1147
+ throw new TypeKroError('KRO bundle runtime member collection lost the root member.', 'KRO_ARTIFACT_BUNDLE_MEMBER_MISSING');
1148
+ }
1149
+ visit(root, []);
1150
+ return members;
1151
+ }
599
1152
  /** Build the KRO factory that owns a singleton's RGD + instance (its registry namespace). */
600
1153
  singletonFactoryFor(definition) {
601
1154
  return definition.composition.factory('kro', {
@@ -617,10 +1170,9 @@ export class KroResourceFactoryImpl {
617
1170
  : {}),
618
1171
  });
619
1172
  }
620
- async ensureSingletonOwners(spec) {
1173
+ async ensureSingletonOwners(spec, operationSignal) {
621
1174
  for (const definition of this.discoverSingletonDefinitions(spec)) {
622
- assertSingletonOwnerNamespaceOwnershipSafe(definition);
623
- await this.ensureTargetNamespace(definition.registryNamespace);
1175
+ await this.ensureTargetNamespace(definition.registryNamespace, operationSignal);
624
1176
  const singletonInstanceName = getSingletonInstanceName(definition.id);
625
1177
  const singletonFactory = this.singletonFactoryFor(definition);
626
1178
  try {
@@ -629,11 +1181,12 @@ export class KroResourceFactoryImpl {
629
1181
  singletonKey: definition.key,
630
1182
  registryNamespace: definition.registryNamespace,
631
1183
  });
632
- const existingInstances = await this.getSingletonOwnerInstancesForDriftCheck(singletonFactory, definition);
1184
+ const existingInstances = await this.getSingletonOwnerInstancesForDriftCheck(singletonFactory, definition, operationSignal);
633
1185
  assertNoDeployedSingletonSpecDrift(definition, singletonInstanceName, existingInstances);
634
1186
  const singletonDeployOptions = {
635
1187
  instanceNameOverride: singletonInstanceName,
636
1188
  singletonSpecFingerprint: singletonSpecFingerprintAnnotationValue(definition.specFingerprint),
1189
+ ...(operationSignal ? { operationSignal } : {}),
637
1190
  };
638
1191
  const deployedSingleton = await singletonFactory.deploy(definition.spec, singletonDeployOptions);
639
1192
  const singletonStatus = deployedSingleton.status;
@@ -648,9 +1201,9 @@ export class KroResourceFactoryImpl {
648
1201
  }
649
1202
  }
650
1203
  }
651
- async getSingletonOwnerInstancesForDriftCheck(singletonFactory, definition) {
1204
+ async getSingletonOwnerInstancesForDriftCheck(singletonFactory, definition, operationSignal) {
652
1205
  try {
653
- return await singletonFactory.getInstances();
1206
+ return await singletonFactory.getInstances(operationSignal ? { operationSignal } : undefined);
654
1207
  }
655
1208
  catch (error) {
656
1209
  if (this.isMissingSingletonOwnerCrdError(error)) {
@@ -680,7 +1233,7 @@ export class KroResourceFactoryImpl {
680
1233
  * Execute closures before RGD creation (Kro mode requirement)
681
1234
  * Closures must execute before ResourceGraphDefinition is created
682
1235
  */
683
- async executeClosuresBeforeRGD(_spec) {
1236
+ async executeClosuresBeforeRGD(_spec, abortSignal) {
684
1237
  if (Object.keys(this.closures).length === 0) {
685
1238
  return []; // No closures to execute
686
1239
  }
@@ -693,6 +1246,7 @@ export class KroResourceFactoryImpl {
693
1246
  const mockDeploymentContext = {
694
1247
  // kubernetesApi intentionally omitted - not needed for validation
695
1248
  validationOnly: true,
1249
+ ...(abortSignal ? { abortSignal } : {}),
696
1250
  namespace: this.namespace,
697
1251
  deployedResources: new Map(),
698
1252
  resolveReference: async (ref) => {
@@ -701,6 +1255,7 @@ export class KroResourceFactoryImpl {
701
1255
  };
702
1256
  for (const [closureName, closure] of Object.entries(this.closures)) {
703
1257
  try {
1258
+ abortSignal?.throwIfAborted();
704
1259
  // Execute the deployment closure with mock context to trigger validation
705
1260
  await closure(mockDeploymentContext);
706
1261
  }
@@ -721,6 +1276,7 @@ export class KroResourceFactoryImpl {
721
1276
  const deploymentContext = {
722
1277
  kubernetesApi: createBunCompatibleKubernetesObjectApi(kubeConfig),
723
1278
  kubeConfig: kubeConfig,
1279
+ ...(abortSignal ? { abortSignal } : {}),
724
1280
  namespace: this.namespace,
725
1281
  deployedResources: new Map(), // Empty for pre-RGD execution
726
1282
  resolveReference: async (ref) => {
@@ -730,6 +1286,7 @@ export class KroResourceFactoryImpl {
730
1286
  // Execute closures sequentially to maintain order
731
1287
  for (const [closureName, closure] of Object.entries(this.closures)) {
732
1288
  try {
1289
+ abortSignal?.throwIfAborted();
733
1290
  this.logger.debug('Executing closure', { name: closureName });
734
1291
  // Note: We can't validate closure arguments here because we don't have access to them
735
1292
  // The validation happens inside the closure when it processes its config
@@ -762,13 +1319,222 @@ export class KroResourceFactoryImpl {
762
1319
  });
763
1320
  return allResults;
764
1321
  }
1322
+ assertBundleCapabilities(bundle, context) {
1323
+ const compatibilityRequirements = bundle?.requiredCapabilities ??
1324
+ (Object.keys(this.closures).length > 0
1325
+ ? [
1326
+ {
1327
+ id: 'typekro.runtime-closure',
1328
+ version: 1,
1329
+ host: 'standalone',
1330
+ output: 'live',
1331
+ },
1332
+ ]
1333
+ : []);
1334
+ assertAdapterCapabilitiesSupported(compatibilityRequirements, {
1335
+ target: 'kro',
1336
+ ...context,
1337
+ });
1338
+ }
1339
+ materializeKroBundleRuntimeResource(operation, member, sensitiveBindings) {
1340
+ const resource = materializeKroArtifactBundleOperation(operation, {
1341
+ sensitive: sensitiveBindings,
1342
+ });
1343
+ Object.defineProperty(resource, 'id', {
1344
+ value: operation.id,
1345
+ configurable: true,
1346
+ enumerable: false,
1347
+ });
1348
+ setResourceId(resource, operation.artifact.id);
1349
+ if (operation.artifact.identity?.scope) {
1350
+ setMetadataField(resource, 'scope', operation.artifact.identity.scope);
1351
+ }
1352
+ setMetadataField(resource, 'applyPolicy', operation.artifact.apply);
1353
+ const strategy = operation.artifact.readiness.strategy;
1354
+ if (strategy?.kind === 'runtime-binding') {
1355
+ const evaluator = member.factory.prerequisiteRuntimeReadinessEvaluators()[strategy.binding];
1356
+ if (!evaluator) {
1357
+ throw new TypeKroError(`KRO readiness binding ${strategy.binding} is unavailable for operation ${operation.id}.`, 'KRO_ARTIFACT_READINESS_UNAVAILABLE', { operationId: operation.id, binding: strategy.binding });
1358
+ }
1359
+ setReadinessEvaluator(resource, evaluator);
1360
+ }
1361
+ else if (strategy?.kind === 'registered') {
1362
+ const evaluator = resolvePortableReadinessStrategy(strategy);
1363
+ if (!evaluator) {
1364
+ throw new TypeKroError(`Registered readiness strategy ${strategy.id}@${strategy.revision} is unavailable.`, 'KRO_ARTIFACT_READINESS_UNAVAILABLE', { operationId: operation.id, strategy: strategy.id });
1365
+ }
1366
+ setReadinessEvaluator(resource, evaluator);
1367
+ }
1368
+ return resource;
1369
+ }
1370
+ /** Execute every directly managed KRO operation from one decoded artifact bundle. */
1371
+ async deployKroArtifactBundle(bundle, spec, sensitiveBindings, abortSignal) {
1372
+ const members = this.collectKroBundleRuntimeMembers(spec);
1373
+ const childMembers = [...members.values()].filter((member) => member.dispose);
1374
+ const deploymentEngine = new DirectDeploymentEngine(this.getKubeConfig(), undefined, undefined, DeploymentMode.KRO);
1375
+ let rootResult;
1376
+ const memberFor = (operation) => {
1377
+ for (const source of operation.sources) {
1378
+ const member = members.get(source.memberId);
1379
+ if (member)
1380
+ return member;
1381
+ }
1382
+ throw new TypeKroError(`KRO bundle operation ${operation.id} has no runtime member context.`, 'KRO_ARTIFACT_BUNDLE_MEMBER_MISSING', { operationId: operation.id });
1383
+ };
1384
+ try {
1385
+ abortSignal?.throwIfAborted();
1386
+ // Preserve singleton operational checks and compatibility closures while
1387
+ // taking desired state and ordering exclusively from the decoded bundle.
1388
+ for (const member of childMembers) {
1389
+ const definition = member.definition;
1390
+ if (!definition) {
1391
+ throw new TypeKroError(`KRO bundle member ${member.memberId} has no singleton definition.`, 'KRO_ARTIFACT_BUNDLE_MEMBER_MISSING', { memberId: member.memberId });
1392
+ }
1393
+ await member.factory.ensureTargetNamespace(definition.registryNamespace, abortSignal);
1394
+ const existing = await member.factory.getSingletonOwnerInstancesForDriftCheck(member.factory, definition, abortSignal);
1395
+ assertNoDeployedSingletonSpecDrift(definition, getSingletonInstanceName(definition.id), existing);
1396
+ await member.factory.executeClosuresBeforeRGD(member.spec, abortSignal);
1397
+ }
1398
+ // Fail closed on pre-hoist upgrades before mutating any RGD, then ensure
1399
+ // every CR placement namespace not represented by a hoisted artifact.
1400
+ for (const member of members.values()) {
1401
+ abortSignal?.throwIfAborted();
1402
+ const hoistedNames = bundle.operations
1403
+ .filter((operation) => operation.role === 'hoisted-namespace' &&
1404
+ operation.sources.some((source) => source.memberId === member.memberId))
1405
+ .map((operation) => materializeKroArtifactBundleOperation(operation, {
1406
+ sensitive: sensitiveBindings,
1407
+ }).metadata.name)
1408
+ .filter((name) => typeof name === 'string');
1409
+ await member.factory.assertNoPreHoistNamespaceConflict(hoistedNames);
1410
+ const instanceOperation = bundle.operations.find((operation) => (operation.role === 'instance' || operation.role === 'singleton-owner-instance') &&
1411
+ operation.sources.some((source) => source.memberId === member.memberId));
1412
+ if (instanceOperation) {
1413
+ const instance = materializeKroArtifactBundleOperation(instanceOperation, {
1414
+ sensitive: sensitiveBindings,
1415
+ });
1416
+ const namespace = instance.metadata.namespace ?? member.factory.namespace;
1417
+ if (!hoistedNames.includes(namespace)) {
1418
+ await member.factory.ensureTargetNamespace(namespace, abortSignal);
1419
+ }
1420
+ }
1421
+ }
1422
+ for (const operation of orderKroArtifactBundleOperations(bundle)) {
1423
+ abortSignal?.throwIfAborted();
1424
+ const member = memberFor(operation);
1425
+ const resource = this.materializeKroBundleRuntimeResource(operation, member, sensitiveBindings);
1426
+ const namespace = resource.metadata.namespace ?? member.factory.namespace;
1427
+ if (operation.role === 'kro-prerequisite') {
1428
+ const waitForReady = getReadinessEvaluator(resource) !== undefined;
1429
+ const deployable = waitForReady
1430
+ ? ensureReadinessEvaluator(resource)
1431
+ : resource;
1432
+ await deploymentEngine.deployResource(deployable, {
1433
+ mode: 'direct',
1434
+ namespace,
1435
+ waitForReady,
1436
+ timeout: member.factory.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT,
1437
+ ...(abortSignal ? { abortSignal } : {}),
1438
+ });
1439
+ const crdName = member.factory.prerequisiteCRDName(resource);
1440
+ if (crdName) {
1441
+ await deploymentEngine.waitForCRDReady(crdName, member.factory.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT, abortSignal);
1442
+ }
1443
+ continue;
1444
+ }
1445
+ if (operation.role === 'hoisted-namespace') {
1446
+ await member.factory.applyRetainedHoistedNamespace(resource, operation.artifact, abortSignal);
1447
+ continue;
1448
+ }
1449
+ if (operation.role === 'resource-graph-definition' ||
1450
+ operation.role === 'singleton-owner-rgd') {
1451
+ await member.factory.runKroPrerequisiteHook(deploymentEngine, undefined, abortSignal);
1452
+ await member.factory.addRgdSchemaStatusPruneMarkers(resource);
1453
+ const rgdFactory = member.factory.rgdProvider ??
1454
+ (await import('../../factories/kro/resource-graph-definition.js'))
1455
+ .resourceGraphDefinition;
1456
+ const enhanced = rgdFactory(resource);
1457
+ copyResourceMetadata(resource, enhanced);
1458
+ const deployable = {
1459
+ ...enhanced,
1460
+ id: operation.id,
1461
+ };
1462
+ copyResourceMetadata(enhanced, deployable);
1463
+ await deploymentEngine.deployResource(deployable, {
1464
+ mode: 'direct',
1465
+ namespace: member.factory.namespace,
1466
+ waitForReady: true,
1467
+ timeout: member.factory.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT,
1468
+ ...(abortSignal ? { abortSignal } : {}),
1469
+ });
1470
+ await member.factory.waitForCRDReadyWithEngine(deploymentEngine, abortSignal);
1471
+ continue;
1472
+ }
1473
+ const kroCustomResource = member.factory.kroCustomResourceProvider ??
1474
+ (await import('../../factories/kro/kro-custom-resource.js')).kroCustomResource;
1475
+ const resourceName = resource.metadata.name;
1476
+ if (typeof resourceName !== 'string' || resourceName.length === 0) {
1477
+ throw new TypeKroError(`KRO instance operation ${operation.id} has no concrete metadata.name.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { operationId: operation.id });
1478
+ }
1479
+ const enhanced = kroCustomResource({
1480
+ apiVersion: resource.apiVersion,
1481
+ kind: resource.kind,
1482
+ metadata: { ...resource.metadata, name: resourceName },
1483
+ spec: resource.spec,
1484
+ });
1485
+ copyResourceMetadata(resource, enhanced);
1486
+ const deployable = {
1487
+ ...enhanced,
1488
+ id: operation.id,
1489
+ metadata: { ...enhanced.metadata, name: resourceName, namespace },
1490
+ spec: resource.spec,
1491
+ };
1492
+ copyResourceMetadata(enhanced, deployable);
1493
+ const deployed = await deploymentEngine.deployResource(deployable, {
1494
+ mode: 'kro',
1495
+ namespace,
1496
+ waitForReady: false,
1497
+ timeout: member.factory.factoryOptions.timeout || DEFAULT_DEPLOYMENT_TIMEOUT,
1498
+ ...(abortSignal ? { abortSignal } : {}),
1499
+ });
1500
+ assertKroInstanceSpecPreserved(deployable, deployed.liveManifest ?? deployed.manifest);
1501
+ const shouldWait = operation.role === 'singleton-owner-instance' ||
1502
+ (member.factory.factoryOptions.waitForReady ?? true);
1503
+ if (shouldWait) {
1504
+ await member.factory.waitForKroInstanceReady(resourceName, member.factory.factoryOptions.timeout || DEFAULT_KRO_INSTANCE_TIMEOUT, namespace, abortSignal);
1505
+ }
1506
+ const result = await member.factory.createEnhancedProxy(member.spec, resourceName);
1507
+ if (operation.role === 'instance') {
1508
+ rootResult = result;
1509
+ }
1510
+ else {
1511
+ const status = result.status;
1512
+ if (status && typeof status === 'object' && !Array.isArray(status)) {
1513
+ for (const consumer of member.consumers) {
1514
+ consumer.factory.singletonOwnerStatuses.set(getSingletonResourceId(consumer.definition.key), status);
1515
+ }
1516
+ }
1517
+ }
1518
+ }
1519
+ if (!rootResult) {
1520
+ throw new TypeKroError('KRO artifact bundle completed without producing the root instance.', 'KRO_ARTIFACT_BUNDLE_ROOT_RESULT_MISSING', { bundleDigest: bundle.bundleDigest });
1521
+ }
1522
+ return rootResult;
1523
+ }
1524
+ finally {
1525
+ await deploymentEngine.dispose();
1526
+ for (const member of childMembers.reverse()) {
1527
+ await member.factory.dispose();
1528
+ }
1529
+ }
1530
+ }
765
1531
  /**
766
1532
  * Deploy directly to Kubernetes using DirectDeploymentEngine
767
1533
  */
768
- async deployDirect(spec, instanceNameOverride, singletonSpecFingerprint) {
1534
+ async deployDirect(spec, instanceNameOverride, singletonSpecFingerprint, abortSignal) {
769
1535
  const instanceNamespace = this.resolveInstanceNamespace(spec);
770
1536
  const instanceName = instanceNameOverride ?? generateInstanceName(spec, this.name);
771
- const hoistedNamespaces = this.concreteHoistedNamespaces(spec);
1537
+ const hoistedNamespaces = this.materializedHoistedNamespaces(spec);
772
1538
  // FINDING #2: fail closed on a PRE-HOIST upgrade BEFORE touching the RGD. If a
773
1539
  // namespace this factory now hoists is currently a KRO ApplySet member (a namespace
774
1540
  // a pre-hoist RGD owned as a graph child), rolling the new hoisted RGD over the old
@@ -781,7 +1547,7 @@ export class KroResourceFactoryImpl {
781
1547
  // owns a namespace and deleting the instance can never garbage-collect the
782
1548
  // namespace holding its own finalizer. The hoist is STRUCTURAL, so the shared RGD
783
1549
  // shape is the same for every instance (finding #4).
784
- await this.ensureRGDDeployed(spec);
1550
+ await this.ensureRGDDeployed(spec, abortSignal);
785
1551
  // Ensure the namespace that will hold the CR exists before posting it. KRO
786
1552
  // reconciles resources from the RGD into their own namespaces, but the CR
787
1553
  // instance itself must live in a namespace the user can write to. If the
@@ -794,10 +1560,10 @@ export class KroResourceFactoryImpl {
794
1560
  // (all labels incl. Pod Security + spec) plus retention markers; a non-hoisted
795
1561
  // instance namespace just needs to exist.
796
1562
  if (!hoistedNamespaces.has(instanceNamespace)) {
797
- await this.ensureTargetNamespace(instanceNamespace);
1563
+ await this.ensureTargetNamespace(instanceNamespace, abortSignal);
798
1564
  }
799
- for (const [name, original] of hoistedNamespaces) {
800
- await this.applyRetainedHoistedNamespace(name, original, spec);
1565
+ for (const { artifact, resource } of hoistedNamespaces.values()) {
1566
+ await this.applyRetainedHoistedNamespace(resource, artifact, abortSignal);
801
1567
  }
802
1568
  // Create DirectDeploymentEngine with KRO mode for CEL string conversion
803
1569
  const deploymentEngine = new DirectDeploymentEngine(this.getKubeConfig(), undefined, undefined, DeploymentMode.KRO);
@@ -830,19 +1596,21 @@ export class KroResourceFactoryImpl {
830
1596
  // Deploy without waiting for readiness - we'll handle that ourselves
831
1597
  this.logger.info('Deploying Kro instance', { instanceName, rgdName: this.rgdName });
832
1598
  try {
833
- await deploymentEngine.deployResource(deployableResource, {
1599
+ const deployed = await deploymentEngine.deployResource(deployableResource, {
834
1600
  mode: 'kro',
835
1601
  namespace: instanceNamespace,
836
1602
  waitForReady: false, // We'll handle Kro-specific readiness ourselves
837
1603
  timeout: this.factoryOptions.timeout || DEFAULT_DEPLOYMENT_TIMEOUT,
1604
+ ...(abortSignal ? { abortSignal } : {}),
838
1605
  });
1606
+ assertKroInstanceSpecPreserved(deployableResource, deployed.liveManifest ?? deployed.manifest);
839
1607
  this.logger.info('Instance deployed, checking readiness', {
840
1608
  instanceName,
841
1609
  rgdName: this.rgdName,
842
1610
  });
843
1611
  // Handle Kro-specific readiness checking if requested
844
1612
  if (this.factoryOptions.waitForReady ?? true) {
845
- await this.waitForKroInstanceReady(instanceName, this.factoryOptions.timeout || DEFAULT_KRO_INSTANCE_TIMEOUT, instanceNamespace); // 10 minutes
1613
+ await this.waitForKroInstanceReady(instanceName, this.factoryOptions.timeout || DEFAULT_KRO_INSTANCE_TIMEOUT, instanceNamespace, abortSignal); // 10 minutes
846
1614
  }
847
1615
  this.logger.info('Instance ready, creating enhanced proxy', {
848
1616
  instanceName,
@@ -867,23 +1635,41 @@ export class KroResourceFactoryImpl {
867
1635
  * silently degrading to a bare `ensureTargetNamespace` that would drop Pod
868
1636
  * Security + other declared configuration.
869
1637
  */
870
- async applyRetainedHoistedNamespace(name, original, spec) {
871
- const merged = KroResourceFactoryImpl.mergedHoistedNamespaceMetadata(name, original, spec);
1638
+ async applyRetainedHoistedNamespace(resource, artifact, abortSignal) {
1639
+ abortSignal?.throwIfAborted();
1640
+ const name = String(resource.metadata.name);
1641
+ const annotations = Object.fromEntries(Object.entries(resource.metadata.annotations ?? {}).map(([key, value]) => [
1642
+ key,
1643
+ String(value),
1644
+ ]));
1645
+ const applyPolicy = artifact?.apply ?? {
1646
+ strategy: 'server-side-apply',
1647
+ fieldManager: 'typekro',
1648
+ fieldConflictPolicy: 'force-owned-fields',
1649
+ immutableFieldPolicy: 'fail',
1650
+ };
1651
+ if (applyPolicy.strategy !== 'server-side-apply') {
1652
+ throw new TypeKroError(`Hoisted Namespace artifact ${artifact?.id ?? name} requires server-side apply.`, 'INVALID_ARTIFACT_APPLY_POLICY', { artifactId: artifact?.id ?? name, strategy: applyPolicy.strategy });
1653
+ }
872
1654
  const k8sApi = createBunCompatibleKubernetesObjectApi(this.getKubeConfig());
873
1655
  const baseMetadata = {
874
- name: merged.name,
875
- labels: merged.labels,
876
- ...(merged.finalizers !== undefined ? { finalizers: merged.finalizers } : {}),
877
- ...(merged.ownerReferences !== undefined ? { ownerReferences: merged.ownerReferences } : {}),
1656
+ name,
1657
+ ...(resource.metadata.labels !== undefined ? { labels: resource.metadata.labels } : {}),
1658
+ ...(resource.metadata.finalizers !== undefined
1659
+ ? { finalizers: resource.metadata.finalizers }
1660
+ : {}),
1661
+ ...(resource.metadata.ownerReferences !== undefined
1662
+ ? { ownerReferences: resource.metadata.ownerReferences }
1663
+ : {}),
878
1664
  };
879
- const buildManifest = (annotations) => ({
1665
+ const buildManifest = (manifestAnnotations) => ({
880
1666
  apiVersion: 'v1',
881
1667
  kind: 'Namespace',
882
- metadata: { ...baseMetadata, annotations },
883
- ...(merged.spec !== undefined ? { spec: merged.spec } : {}),
1668
+ metadata: { ...baseMetadata, annotations: manifestAnnotations },
1669
+ ...(resource.spec !== undefined ? { spec: resource.spec } : {}),
884
1670
  });
885
1671
  const annotationsWithStamp = {
886
- ...merged.annotations,
1672
+ ...annotations,
887
1673
  [NAMESPACE_OWNER_ANNOTATION]: this.rgdName,
888
1674
  };
889
1675
  // CREATE-FIRST ownership (finding #3): attempt to CREATE the namespace WITH the
@@ -895,6 +1681,7 @@ export class KroResourceFactoryImpl {
895
1681
  let decision;
896
1682
  try {
897
1683
  decision = await decideNamespaceOwnershipCreateFirst(k8sApi, buildManifest(annotationsWithStamp), this.rgdName);
1684
+ abortSignal?.throwIfAborted();
898
1685
  }
899
1686
  catch (error) {
900
1687
  // A non-conflict CREATE failure (or a failed conflict-read) — do NOT fall back to a
@@ -907,11 +1694,9 @@ export class KroResourceFactoryImpl {
907
1694
  return;
908
1695
  // 409: the namespace already existed. Apply the retained config via SSA, keeping the
909
1696
  // ownership stamp ONLY if we already own it (adoption must NOT be stamped).
910
- const annotations = decision.owned ? annotationsWithStamp : merged.annotations;
1697
+ const appliedAnnotations = decision.owned ? annotationsWithStamp : annotations;
911
1698
  try {
912
- await k8sApi.patch(buildManifest(annotations), undefined, undefined, 'typekro', // fieldManager — typekro's, NOT kro's
913
- true, // force (own the retention fields)
914
- 'application/apply-patch+yaml' // server-side apply
1699
+ await k8sApi.patch(buildManifest(appliedAnnotations), undefined, undefined, applyPolicy.fieldManager, applyPolicy.fieldConflictPolicy === 'force-owned-fields', 'application/apply-patch+yaml' // server-side apply
915
1700
  );
916
1701
  }
917
1702
  catch (error) {
@@ -1114,7 +1899,16 @@ export class KroResourceFactoryImpl {
1114
1899
  /**
1115
1900
  * Get all deployed instances
1116
1901
  */
1117
- async getInstances() {
1902
+ async getInstances(opts) {
1903
+ if (opts?.operationSignal) {
1904
+ return this.getInstancesWithinOperation(opts.operationSignal);
1905
+ }
1906
+ return runStandaloneOperation((abortSignal) => this.getInstancesWithinOperation(abortSignal), {
1907
+ abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal],
1908
+ });
1909
+ }
1910
+ async getInstancesWithinOperation(abortSignal) {
1911
+ abortSignal.throwIfAborted();
1118
1912
  const customApi = await this.createCustomObjectsApi();
1119
1913
  try {
1120
1914
  const version = this.getSchemaVersion();
@@ -1128,6 +1922,7 @@ export class KroResourceFactoryImpl {
1128
1922
  if (!this.discoveredPlural) {
1129
1923
  this.discoveredPlural = await this.lookupCRDPlural();
1130
1924
  }
1925
+ abortSignal.throwIfAborted();
1131
1926
  const plural = this.discoveredPlural ?? pluralizeKind(this.schemaDefinition.kind);
1132
1927
  // The instance CR lives in the namespace {@link resolveInstanceNamespace}
1133
1928
  // resolves to — the SAME shared resolver `deploy()`/`toYaml()` use, so a
@@ -1140,9 +1935,11 @@ export class KroResourceFactoryImpl {
1140
1935
  namespace: this.resolveInstanceNamespace(),
1141
1936
  plural,
1142
1937
  });
1938
+ abortSignal.throwIfAborted();
1143
1939
  const listResult = listResponse;
1144
1940
  const instances = listResult.items || [];
1145
1941
  return await Promise.all(instances.map(async (instance) => {
1942
+ abortSignal.throwIfAborted();
1146
1943
  const enhanced = await this.createEnhancedProxy(instance.spec, instance.metadata?.name || 'unknown');
1147
1944
  if (instance.metadata?.annotations) {
1148
1945
  const mutableEnhanced = enhanced;
@@ -1162,6 +1959,7 @@ export class KroResourceFactoryImpl {
1162
1959
  }));
1163
1960
  }
1164
1961
  catch (error) {
1962
+ abortSignal.throwIfAborted();
1165
1963
  const k8sError = error;
1166
1964
  // If the CRD doesn't exist yet or there are no instances, return empty array
1167
1965
  const bodyString = typeof k8sError.body === 'string' ? k8sError.body : JSON.stringify(k8sError.body || '');
@@ -1206,6 +2004,11 @@ export class KroResourceFactoryImpl {
1206
2004
  * Delete a specific instance by name
1207
2005
  */
1208
2006
  async deleteInstance(name, opts) {
2007
+ return runStandaloneOperation((abortSignal) => this.deleteInstanceWithinOperation(name, opts, abortSignal), { abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal] });
2008
+ }
2009
+ async deleteInstanceWithinOperation(name, opts, abortSignal) {
2010
+ const deletion = createDeletionResultState('kro', this.name, name);
2011
+ abortSignal.throwIfAborted();
1209
2012
  if (opts?.scopes?.length) {
1210
2013
  throw new TypeKroError('Scope-filtered deletion is not supported in KRO mode. KRO manages resource lifecycle via its own controller. Use direct mode for scope-filtered deletes.', 'UNSUPPORTED_OPTION', { scopes: opts.scopes, instanceName: name, mode: 'kro' });
1211
2014
  }
@@ -1215,7 +2018,7 @@ export class KroResourceFactoryImpl {
1215
2018
  // and THROWS DeploymentTimeoutError on timeout — never a silent return that lets a
1216
2019
  // later step run behind a still-Terminating resource.
1217
2020
  const rollback = createRollbackManager(k8sApi);
1218
- const timeout = this.factoryOptions.timeout ?? 300000;
2021
+ const timeout = opts?.timeout ?? this.factoryOptions.timeout ?? 300000;
1219
2022
  const apiVersion = this.getInstanceApiVersion();
1220
2023
  let definitionsAlreadyAbsent = false;
1221
2024
  try {
@@ -1231,6 +2034,7 @@ export class KroResourceFactoryImpl {
1231
2034
  // SAME shared resolver `deploy()` created it with (finding #2), so we never look in
1232
2035
  // the wrong namespace and mistake a 404 there for "already gone".
1233
2036
  const instanceNamespace = this.resolveInstanceNamespace();
2037
+ const instanceTarget = deletionTarget(apiVersion, this.schemaDefinition.kind, name, instanceNamespace);
1234
2038
  // Capture the CR's durable RECORD of which namespaces THIS instance hoisted BEFORE
1235
2039
  // deleting it (finding #4). deleteInstance has no caller spec, so the live CR is the
1236
2040
  // only source of the declared set. PREFER the recorded `typekro.io/hoisted-namespaces`
@@ -1265,31 +2069,53 @@ export class KroResourceFactoryImpl {
1265
2069
  : crSpec
1266
2070
  ? [...this.concreteHoistedNamespaces(crSpec).keys()]
1267
2071
  : [];
1268
- // 1. Delete the CR and GATE on its 404 — KRO cleared `kro.run/finalizer` after
1269
- // graph-deleting every child (findings #1 + #2). On timeout the gate THROWS, so we
1270
- // NEVER proceed to delete the RGD while KRO is mid-finalizer (which would orphan
1271
- // cleanup). A pre-existing 404 on delete is treated as already-deleted.
1272
- let instanceDeleted = false;
1273
- try {
1274
- if (!definitionsAlreadyAbsent) {
1275
- await rollback.deleteResourceAndWait({ apiVersion, kind: this.schemaDefinition.kind, name, namespace: instanceNamespace }, { timeout });
2072
+ const permanentlyRetainedOwnedNamespaces = new Set();
2073
+ let graphMayHaveDrainingDescendants = false;
2074
+ const deleteOwnedNamespace = async (namespace) => {
2075
+ const target = deletionTarget('v1', 'Namespace', namespace);
2076
+ try {
2077
+ const outcome = await deleteNamespaceIfEmpty(this.getKubeConfig(), namespace, {
2078
+ logger: this.logger,
2079
+ k8sApi,
2080
+ ownedByRgd: this.rgdName,
2081
+ timeoutMs: timeout,
2082
+ abortSignal,
2083
+ context: { rgdName: this.rgdName },
2084
+ });
2085
+ recordNamespaceDeletionOutcome(deletion, namespace, outcome);
2086
+ if (outcome.status === 'retained' &&
2087
+ outcome.cause === 'occupied' &&
2088
+ !graphMayHaveDrainingDescendants) {
2089
+ permanentlyRetainedOwnedNamespaces.add(namespace);
2090
+ }
2091
+ return undefined;
1276
2092
  }
1277
- instanceDeleted = true;
1278
- }
1279
- catch (error) {
1280
- if (error instanceof DeploymentTimeoutError) {
1281
- // KRO is still processing the finalizer. The RGD/CRD/namespace are LEFT ALONE
1282
- // (we throw before reaching them) so KRO can finish cleanup in the background.
1283
- throw new CRDInstanceError(`KRO instance ${name} deletion did not complete within ${timeout}ms`, this.schemaDefinition.apiVersion, this.schemaDefinition.kind, name, 'deletion', ensureError(error));
2093
+ catch (error) {
2094
+ if (abortSignal.aborted)
2095
+ throw abortSignal.reason ?? error;
2096
+ let live = target;
2097
+ try {
2098
+ live = (await readDeletionResourceIdentity(k8sApi, target)) ?? target;
2099
+ }
2100
+ catch (readError) {
2101
+ deletion.blockers.push({
2102
+ code: 'CLEANUP_ERROR',
2103
+ message: `Namespace cleanup failed and blocker inspection also failed: ${ensureError(readError).message}`,
2104
+ resource: target,
2105
+ retryable: true,
2106
+ retryGuidance: 'Restore Kubernetes API access, then retry the deletion operation.',
2107
+ });
2108
+ }
2109
+ deletion.remaining.push(live);
2110
+ deletion.blockers.push(blockerForRemainingResource(live, `Namespace ${namespace} did not complete deletion: ${ensureError(error).message}`));
2111
+ return finishDeletionResult(deletion, live.deletionTimestamp ? 'progressing' : 'blocked', {
2112
+ safe: true,
2113
+ afterMs: 2_000,
2114
+ guidance: 'Retry after the listed namespace finalizers or occupants have drained; the RGD and generated CRD were retained.',
2115
+ });
1284
2116
  }
1285
- throw new CRDInstanceError(`Failed to delete instance ${name}: ${ensureError(error).message}`, this.schemaDefinition.apiVersion, this.schemaDefinition.kind, name, 'deletion', ensureError(error));
1286
- }
1287
- // KRO's owner CR reaching 404 proves that its finalizer issued child
1288
- // deletions; it does not prove that controller-owned child finalizers have
1289
- // completed. Gate on every concrete owned child before touching the RGD or
1290
- // reporting successful cleanup. External refs and hoisted Namespaces are
1291
- // deliberately excluded: this instance never owns the former and TypeKro
1292
- // tears down the latter in the namespace phase below.
2117
+ };
2118
+ const childTargets = [];
1293
2119
  if (crSpec) {
1294
2120
  const compositionFn = this.factoryOptions.compositionFn;
1295
2121
  const ownedChildren = concreteActiveOwnedResources({
@@ -1298,14 +2124,22 @@ export class KroResourceFactoryImpl {
1298
2124
  resources: this.resources,
1299
2125
  ...(compositionFn ? { compositionFn } : {}),
1300
2126
  });
1301
- const childTargets = [];
1302
2127
  for (const [resourceId, resource] of ownedChildren) {
1303
2128
  if (resource.kind === 'Namespace')
1304
2129
  continue;
1305
2130
  const childName = resolveNamespaceName(resource.metadata?.name, crSpec);
1306
2131
  const childNamespace = resolveNamespaceName(resource.metadata?.namespace, crSpec);
1307
2132
  if (!childName) {
1308
- throw new CRDInstanceError(`Cannot prove KRO child cleanup: resource ${resourceId} (${resource.kind}) has an unresolved concrete name`, this.schemaDefinition.apiVersion, this.schemaDefinition.kind, name, 'deletion');
2133
+ deletion.blockers.push({
2134
+ code: 'OWNERSHIP_UNPROVEN',
2135
+ message: `Cannot prove KRO child cleanup: resource ${resourceId} (${resource.kind}) has an unresolved concrete name.`,
2136
+ retryable: false,
2137
+ retryGuidance: 'Give every KRO-owned child a concretely resolvable metadata.name so TypeKro can prove teardown completion.',
2138
+ });
2139
+ return finishDeletionResult(deletion, 'blocked', {
2140
+ safe: false,
2141
+ guidance: 'Correct the unresolved child identity before retrying; the instance was retained.',
2142
+ });
1309
2143
  }
1310
2144
  childTargets.push({
1311
2145
  apiVersion: resource.apiVersion,
@@ -1314,11 +2148,154 @@ export class KroResourceFactoryImpl {
1314
2148
  ...(childNamespace ? { namespace: childNamespace } : {}),
1315
2149
  });
1316
2150
  }
2151
+ graphMayHaveDrainingDescendants = childTargets.some((target) => DESCENDANT_PRODUCING_KINDS.has(target.kind));
2152
+ }
2153
+ let capturedDescendants = [];
2154
+ try {
2155
+ capturedDescendants = await captureKnownControllerDescendants(k8sApi, childTargets, instanceNamespace);
2156
+ }
2157
+ catch (error) {
2158
+ deletion.blockers.push({
2159
+ code: 'DISCOVERY_FAILED',
2160
+ message: `Cannot capture KRO-owned controller descendants before deletion: ${ensureError(error).message}`,
2161
+ retryable: true,
2162
+ retryGuidance: 'Restore read/list access to KRO-owned children and their dependents, then retry deletion.',
2163
+ });
2164
+ return finishDeletionResult(deletion, 'blocked', {
2165
+ safe: true,
2166
+ guidance: 'TypeKro retained the instance because descendant ownership could not be proven safely.',
2167
+ });
2168
+ }
2169
+ // 1. Delete the CR and GATE on its 404 — KRO cleared `kro.run/finalizer` after
2170
+ // graph-deleting every child (findings #1 + #2). On timeout the gate THROWS, so we
2171
+ // NEVER proceed to delete the RGD while KRO is mid-finalizer (which would orphan
2172
+ // cleanup). A pre-existing 404 on delete is treated as already-deleted.
2173
+ let instanceDeleted = false;
2174
+ try {
2175
+ if (!definitionsAlreadyAbsent) {
2176
+ await rollback.deleteResourceAndWait({ apiVersion, kind: this.schemaDefinition.kind, name, namespace: instanceNamespace }, { timeout, abortSignal });
2177
+ }
2178
+ instanceDeleted = true;
2179
+ deletion.deleted.push(instanceTarget);
2180
+ }
2181
+ catch (error) {
2182
+ if (abortSignal.aborted)
2183
+ throw abortSignal.reason ?? error;
2184
+ if (error instanceof DeploymentTimeoutError) {
2185
+ // KRO is still processing the finalizer. The RGD/CRD/namespace are LEFT ALONE
2186
+ // so KRO can finish cleanup in the background, and the caller receives the exact
2187
+ // live finalizer/owner state instead of a success-shaped void return.
2188
+ let live = instanceTarget;
2189
+ try {
2190
+ live = (await readDeletionResourceIdentity(k8sApi, instanceTarget)) ?? instanceTarget;
2191
+ }
2192
+ catch (readError) {
2193
+ deletion.blockers.push({
2194
+ code: 'CLEANUP_ERROR',
2195
+ message: `KRO instance deletion timed out and its live blocker state could not be read: ${ensureError(readError).message}`,
2196
+ resource: instanceTarget,
2197
+ retryable: true,
2198
+ retryGuidance: 'Retry when the Kubernetes API is readable; keep the RGD installed so KRO can continue finalizer processing.',
2199
+ });
2200
+ }
2201
+ deletion.remaining.push(live);
2202
+ deletion.blockers.push(blockerForRemainingResource(live, `KRO instance ${name} deletion did not complete within ${timeout}ms.`));
2203
+ return finishDeletionResult(deletion, live.deletionTimestamp ? 'progressing' : 'blocked', {
2204
+ safe: true,
2205
+ afterMs: 2_000,
2206
+ guidance: 'Retry deletion after KRO and the listed owners have had time to clear the remaining finalizers.',
2207
+ });
2208
+ }
2209
+ deletion.remaining.push(instanceTarget);
2210
+ deletion.blockers.push({
2211
+ code: 'CLEANUP_ERROR',
2212
+ message: `Failed to delete instance ${name}: ${ensureError(error).message}`,
2213
+ resource: instanceTarget,
2214
+ retryable: true,
2215
+ retryGuidance: 'Resolve the reported Kubernetes API error, then retry. TypeKro has preserved the RGD and generated CRD.',
2216
+ });
2217
+ return finishDeletionResult(deletion, 'blocked', {
2218
+ safe: true,
2219
+ guidance: 'Retry after resolving the reported Kubernetes API error.',
2220
+ });
2221
+ }
2222
+ // KRO's owner CR reaching 404 proves that its finalizer issued child
2223
+ // deletions; it does not prove that controller-owned child finalizers have
2224
+ // completed. Gate on every concrete owned child before touching the RGD or
2225
+ // reporting successful cleanup. External refs and hoisted Namespaces are
2226
+ // deliberately excluded: this instance never owns the former and TypeKro
2227
+ // tears down the latter in the namespace phase below.
2228
+ if (childTargets.length > 0) {
1317
2229
  try {
1318
- await Promise.all(childTargets.map((target) => rollback.waitForResourceGone(target, { timeout })));
2230
+ await Promise.all(childTargets.map((target) => rollback.waitForResourceGone(target, { timeout, abortSignal })));
2231
+ deletion.deleted.push(...childTargets.map((target) => deletionTarget(target.apiVersion, target.kind, target.name, target.namespace)));
1319
2232
  }
1320
2233
  catch (error) {
1321
- throw new CRDInstanceError(`KRO instance ${name} disappeared but one or more owned children did not finish deleting within ${timeout}ms`, this.schemaDefinition.apiVersion, this.schemaDefinition.kind, name, 'deletion', ensureError(error));
2234
+ if (abortSignal.aborted)
2235
+ throw abortSignal.reason ?? error;
2236
+ for (const target of childTargets) {
2237
+ const identity = deletionTarget(target.apiVersion, target.kind, target.name, target.namespace);
2238
+ try {
2239
+ const live = await readDeletionResourceIdentity(k8sApi, identity);
2240
+ if (!live) {
2241
+ deletion.deleted.push(identity);
2242
+ continue;
2243
+ }
2244
+ deletion.remaining.push(live);
2245
+ deletion.blockers.push(blockerForRemainingResource(live));
2246
+ }
2247
+ catch (readError) {
2248
+ deletion.remaining.push(identity);
2249
+ deletion.blockers.push({
2250
+ code: 'CLEANUP_ERROR',
2251
+ message: `Could not inspect remaining child ${target.kind}/${target.name}: ${ensureError(readError).message}`,
2252
+ resource: identity,
2253
+ retryable: true,
2254
+ retryGuidance: 'Retry after the Kubernetes API is readable; TypeKro retained the RGD and generated CRD.',
2255
+ });
2256
+ }
2257
+ }
2258
+ const progressing = deletion.remaining.length > 0 &&
2259
+ deletion.remaining.every((resource) => resource.deletionTimestamp !== undefined);
2260
+ return finishDeletionResult(deletion, progressing ? 'progressing' : 'blocked', {
2261
+ safe: true,
2262
+ afterMs: 2_000,
2263
+ guidance: 'Retry after the listed child resources and finalizers have drained; TypeKro retained the RGD and generated CRD.',
2264
+ });
2265
+ }
2266
+ }
2267
+ // KRO 0.9 can remove a completed Job before Kubernetes garbage collection
2268
+ // removes its Pod. Delete only the dependents captured before instance
2269
+ // deletion whose controller UID matched the KRO-owned Job exactly.
2270
+ for (const descendant of capturedDescendants) {
2271
+ const identity = deletionTarget(descendant.apiVersion, descendant.kind, descendant.name, descendant.namespace);
2272
+ try {
2273
+ await rollback.deleteResourceAndWait(identity, { timeout, abortSignal });
2274
+ deletion.deleted.push(identity);
2275
+ }
2276
+ catch (error) {
2277
+ if (abortSignal.aborted)
2278
+ throw abortSignal.reason ?? error;
2279
+ let live = identity;
2280
+ try {
2281
+ live = (await readDeletionResourceIdentity(k8sApi, identity)) ?? identity;
2282
+ }
2283
+ catch (readError) {
2284
+ deletion.blockers.push({
2285
+ code: 'CLEANUP_ERROR',
2286
+ message: `Could not inspect captured descendant ${descendant.kind}/${descendant.name}: ${ensureError(readError).message}`,
2287
+ resource: identity,
2288
+ retryable: true,
2289
+ retryGuidance: 'Restore Kubernetes API access, then retry deletion.',
2290
+ });
2291
+ }
2292
+ deletion.remaining.push(live);
2293
+ deletion.blockers.push(blockerForRemainingResource(live, `${descendant.kind}/${descendant.name}, owned by ${descendant.owner.kind}/${descendant.owner.name} (${descendant.owner.uid}), did not complete deletion: ${ensureError(error).message}`));
2294
+ return finishDeletionResult(deletion, live.deletionTimestamp ? 'progressing' : 'blocked', {
2295
+ safe: true,
2296
+ afterMs: 2_000,
2297
+ guidance: 'Retry after the listed controller-owned descendants and finalizers have drained.',
2298
+ });
1322
2299
  }
1323
2300
  }
1324
2301
  // 2. LIST REMAINING INSTANCES FIRST — BEFORE deleting any namespace (finding #2, v7).
@@ -1334,10 +2311,37 @@ export class KroResourceFactoryImpl {
1334
2311
  let remainingItems;
1335
2312
  try {
1336
2313
  remainingItems = definitionsAlreadyAbsent ? [] : await this.listInstancesForCleanup();
2314
+ abortSignal.throwIfAborted();
1337
2315
  }
1338
2316
  catch (listError) {
2317
+ const reason = `Cannot list KRO instances to prove shared ownership: ${ensureError(listError).message}`;
1339
2318
  this.logger.warn('Cannot list instances — preserving ALL namespaces + the RGD/CRD (fail closed; retry to complete)', { rgdName: this.rgdName, error: ensureError(listError).message });
1340
- return;
2319
+ const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
2320
+ deletion.retained.push({
2321
+ resource: rgd,
2322
+ policy: 'safety-proof-unavailable',
2323
+ reason,
2324
+ });
2325
+ for (const namespace of declaredHoistedNamespaceNames) {
2326
+ const resource = deletionTarget('v1', 'Namespace', namespace);
2327
+ deletion.retained.push({
2328
+ resource,
2329
+ policy: 'safety-proof-unavailable',
2330
+ reason,
2331
+ });
2332
+ deletion.remaining.push(resource);
2333
+ }
2334
+ deletion.blockers.push({
2335
+ code: 'DISCOVERY_FAILED',
2336
+ message: reason,
2337
+ resource: rgd,
2338
+ retryable: true,
2339
+ retryGuidance: 'Restore cluster-wide list access for the generated custom resource, then retry deletion.',
2340
+ });
2341
+ return finishDeletionResult(deletion, 'blocked', {
2342
+ safe: true,
2343
+ guidance: 'Retry after cluster-wide instance discovery is available; TypeKro retained shared resources fail-closed.',
2344
+ });
1341
2345
  }
1342
2346
  // The decision to keep the RGD is a pure function of (listed instances, target name,
1343
2347
  // instanceDeleted flag) — see {@link shouldPreserveRgd} for the rules.
@@ -1388,36 +2392,72 @@ export class KroResourceFactoryImpl {
1388
2392
  preserved.add(ns);
1389
2393
  }
1390
2394
  if (!allRemainingResolvable) {
2395
+ const reason = 'A remaining KRO instance has no valid hoisted-namespace ownership record, so shared namespace safety cannot be proven.';
1391
2396
  this.logger.warn("A remaining instance has no resolvable namespace record — preserving ALL of this instance's " +
1392
2397
  'namespaces (fail closed) to avoid deleting one a remaining instance shares', { rgdName: this.rgdName, instance: name });
2398
+ for (const ns of declaredHoistedNamespaceNames) {
2399
+ const resource = deletionTarget('v1', 'Namespace', ns);
2400
+ deletion.retained.push({
2401
+ resource,
2402
+ policy: 'safety-proof-unavailable',
2403
+ reason,
2404
+ });
2405
+ deletion.remaining.push(resource);
2406
+ }
2407
+ deletion.blockers.push({
2408
+ code: 'OWNERSHIP_UNPROVEN',
2409
+ message: reason,
2410
+ retryable: true,
2411
+ retryGuidance: 'Repair or migrate the remaining instance hoisted-namespace annotation, then retry deletion.',
2412
+ });
1393
2413
  }
1394
2414
  else {
1395
2415
  for (const ns of declaredHoistedNamespaceNames) {
2416
+ abortSignal.throwIfAborted();
1396
2417
  if (preserved.has(ns)) {
1397
2418
  this.logger.debug('Namespace recorded by a remaining instance — preserving', {
1398
2419
  namespace: ns,
1399
2420
  rgdName: this.rgdName,
1400
2421
  });
2422
+ deletion.retained.push({
2423
+ resource: deletionTarget('v1', 'Namespace', ns),
2424
+ policy: 'shared-instance',
2425
+ reason: 'A remaining KRO instance records this namespace as shared state.',
2426
+ });
1401
2427
  continue;
1402
2428
  }
1403
- await deleteNamespaceIfEmpty(this.getKubeConfig(), ns, {
1404
- logger: this.logger,
1405
- // Reuse the SAME (mockable) object API so discovery/delete is unit-testable.
1406
- k8sApi,
1407
- ownedByRgd: this.rgdName,
1408
- timeoutMs: timeout,
1409
- context: { rgdName: this.rgdName },
1410
- });
2429
+ const incomplete = await deleteOwnedNamespace(ns);
2430
+ if (incomplete)
2431
+ return incomplete;
1411
2432
  }
1412
2433
  }
1413
2434
  }
1414
2435
  // Only delete the RGD and CRD if no other instances remain. Multiple instances can share
1415
2436
  // one RGD — deleting it would break the others.
1416
2437
  if (hasRemainingInstances) {
2438
+ const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
2439
+ deletion.retained.push({
2440
+ resource: rgd,
2441
+ policy: 'shared-instance',
2442
+ reason: 'Other KRO instances still depend on this ResourceGraphDefinition.',
2443
+ });
2444
+ if (this.discoveredPlural) {
2445
+ deletion.retained.push({
2446
+ resource: deletionTarget('apiextensions.k8s.io/v1', 'CustomResourceDefinition', `${this.discoveredPlural}.${this.getSchemaGroup()}`),
2447
+ policy: 'generated-crd',
2448
+ reason: 'The generated CRD is retained while shared instances exist.',
2449
+ });
2450
+ }
1417
2451
  this.logger.debug('Skipping RGD/CRD deletion — other instances still exist', {
1418
2452
  rgdName: this.rgdName,
1419
2453
  });
1420
- return;
2454
+ const blocked = deletion.blockers.length > 0;
2455
+ return finishDeletionResult(deletion, blocked ? 'blocked' : 'complete', {
2456
+ safe: true,
2457
+ guidance: blocked
2458
+ ? 'Retry after repairing the missing namespace ownership evidence.'
2459
+ : 'The requested instance is gone. Shared definitions and namespaces were retained intentionally.',
2460
+ });
1421
2461
  }
1422
2462
  // 2b. RETRY-SAFE owned-namespace sweep (finding #2). The per-instance cleanup above
1423
2463
  // reads the names off the CR's `typekro.io/hoisted-namespaces` annotation — but that
@@ -1434,20 +2474,35 @@ export class KroResourceFactoryImpl {
1434
2474
  ownedNamespaces = await listNamespacesOwnedByRgd(this.getKubeConfig(), this.rgdName, {
1435
2475
  k8sApi,
1436
2476
  logger: this.logger,
2477
+ abortSignal,
1437
2478
  });
1438
2479
  }
1439
2480
  catch (listError) {
2481
+ const reason = `Cannot list namespaces owned by RGD ${this.rgdName}: ${ensureError(listError).message}`;
1440
2482
  this.logger.warn('Cannot list owned namespaces to confirm cleanup — preserving RGD/CRD (retry to complete)', { rgdName: this.rgdName, error: ensureError(listError).message });
1441
- return;
2483
+ const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
2484
+ deletion.retained.push({
2485
+ resource: rgd,
2486
+ policy: 'safety-proof-unavailable',
2487
+ reason,
2488
+ });
2489
+ deletion.blockers.push({
2490
+ code: 'DISCOVERY_FAILED',
2491
+ message: reason,
2492
+ resource: rgd,
2493
+ retryable: true,
2494
+ retryGuidance: 'Restore Namespace list access and retry; TypeKro retained the RGD and generated CRD.',
2495
+ });
2496
+ return finishDeletionResult(deletion, 'blocked', {
2497
+ safe: true,
2498
+ guidance: 'Retry after Namespace ownership discovery is available.',
2499
+ });
1442
2500
  }
1443
2501
  for (const ns of ownedNamespaces) {
1444
- await deleteNamespaceIfEmpty(this.getKubeConfig(), ns, {
1445
- logger: this.logger,
1446
- k8sApi,
1447
- ownedByRgd: this.rgdName,
1448
- timeoutMs: timeout,
1449
- context: { rgdName: this.rgdName },
1450
- });
2502
+ abortSignal.throwIfAborted();
2503
+ const incomplete = await deleteOwnedNamespace(ns);
2504
+ if (incomplete)
2505
+ return incomplete;
1451
2506
  }
1452
2507
  // 2c. CONFIRM owned-namespace cleanup BEFORE removing the definitions (finding #2): do
1453
2508
  // NOT delete the RGD/CRD while any owned namespace still exists or cannot be confirmed
@@ -1460,11 +2515,29 @@ export class KroResourceFactoryImpl {
1460
2515
  remainingOwned = await listNamespacesOwnedByRgd(this.getKubeConfig(), this.rgdName, {
1461
2516
  k8sApi,
1462
2517
  logger: this.logger,
2518
+ abortSignal,
1463
2519
  });
1464
2520
  }
1465
2521
  catch (listError) {
2522
+ const reason = `Cannot confirm cleanup of namespaces owned by RGD ${this.rgdName}: ${ensureError(listError).message}`;
1466
2523
  this.logger.warn('Cannot confirm owned-namespace cleanup — preserving RGD/CRD (retry to complete)', { rgdName: this.rgdName, error: ensureError(listError).message });
1467
- return;
2524
+ const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
2525
+ deletion.retained.push({
2526
+ resource: rgd,
2527
+ policy: 'safety-proof-unavailable',
2528
+ reason,
2529
+ });
2530
+ deletion.blockers.push({
2531
+ code: 'DISCOVERY_FAILED',
2532
+ message: reason,
2533
+ resource: rgd,
2534
+ retryable: true,
2535
+ retryGuidance: 'Restore Namespace list access and retry; TypeKro retained the RGD and generated CRD.',
2536
+ });
2537
+ return finishDeletionResult(deletion, 'blocked', {
2538
+ safe: true,
2539
+ guidance: 'Retry after Namespace cleanup can be confirmed.',
2540
+ });
1468
2541
  }
1469
2542
  // A graph child reaching 404 only proves its controller accepted deletion;
1470
2543
  // grandchildren such as Helm-managed Pods or Rook RGW Deployments may need
@@ -1473,40 +2546,126 @@ export class KroResourceFactoryImpl {
1473
2546
  // before returning the durable "preserved for retry" state. This prevents a
1474
2547
  // successful deleteInstance() from racing immediately into teardown of a
1475
2548
  // storage prerequisite while owned descendants are still draining.
1476
- if (crSpec && remainingOwned.length > 0) {
1477
- const drainDeadline = Date.now() + Math.min(timeout, 300_000);
1478
- while (remainingOwned.length > 0 && Date.now() < drainDeadline) {
1479
- await new Promise((resolve) => setTimeout(resolve, 2_000));
1480
- for (const ns of remainingOwned) {
1481
- await deleteNamespaceIfEmpty(this.getKubeConfig(), ns, {
1482
- logger: this.logger,
1483
- k8sApi,
1484
- ownedByRgd: this.rgdName,
1485
- timeoutMs: Math.max(1, drainDeadline - Date.now()),
1486
- context: { rgdName: this.rgdName },
1487
- });
2549
+ const namespacesStillDraining = () => remainingOwned.filter((namespace) => !permanentlyRetainedOwnedNamespaces.has(namespace));
2550
+ if (crSpec && namespacesStillDraining().length > 0) {
2551
+ const drainDeadline = Date.now() + Math.min(timeout, 60_000);
2552
+ while (namespacesStillDraining().length > 0 && Date.now() < drainDeadline) {
2553
+ await new Promise((resolve, reject) => {
2554
+ const onAbort = () => {
2555
+ clearTimeout(delay);
2556
+ reject(abortSignal.reason ?? new DOMException('The operation was aborted', 'AbortError'));
2557
+ };
2558
+ const delay = setTimeout(() => {
2559
+ abortSignal.removeEventListener('abort', onAbort);
2560
+ resolve();
2561
+ }, 2_000);
2562
+ if (abortSignal.aborted) {
2563
+ onAbort();
2564
+ return;
2565
+ }
2566
+ abortSignal.addEventListener('abort', onAbort, { once: true });
2567
+ });
2568
+ for (const ns of namespacesStillDraining()) {
2569
+ abortSignal.throwIfAborted();
2570
+ const incomplete = await deleteOwnedNamespace(ns);
2571
+ if (incomplete)
2572
+ return incomplete;
1488
2573
  }
1489
2574
  try {
1490
2575
  remainingOwned = await listNamespacesOwnedByRgd(this.getKubeConfig(), this.rgdName, {
1491
2576
  k8sApi,
1492
2577
  logger: this.logger,
2578
+ abortSignal,
1493
2579
  });
1494
2580
  }
1495
2581
  catch (listError) {
2582
+ const reason = `Cannot confirm descendant drain for namespaces owned by RGD ${this.rgdName}: ${ensureError(listError).message}`;
1496
2583
  this.logger.warn('Cannot confirm owned-namespace descendant drain — preserving RGD/CRD for retry', { rgdName: this.rgdName, error: ensureError(listError).message });
1497
- return;
2584
+ const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
2585
+ deletion.retained.push({
2586
+ resource: rgd,
2587
+ policy: 'safety-proof-unavailable',
2588
+ reason,
2589
+ });
2590
+ deletion.blockers.push({
2591
+ code: 'DISCOVERY_FAILED',
2592
+ message: reason,
2593
+ resource: rgd,
2594
+ retryable: true,
2595
+ retryGuidance: 'Restore Namespace list access, then retry deletion.',
2596
+ });
2597
+ return finishDeletionResult(deletion, 'blocked', {
2598
+ safe: true,
2599
+ guidance: 'Retry after Namespace descendant cleanup can be confirmed.',
2600
+ });
1498
2601
  }
1499
2602
  }
1500
2603
  }
1501
2604
  if (remainingOwned.length > 0) {
1502
2605
  this.logger.info('Owned namespace(s) still present after cleanup — preserving RGD/CRD until they are gone', { rgdName: this.rgdName, remaining: remainingOwned });
1503
- return;
2606
+ for (const namespace of remainingOwned) {
2607
+ const resource = deletionTarget('v1', 'Namespace', namespace);
2608
+ if (!deletion.retained.some((entry) => entry.resource.kind === 'Namespace' && entry.resource.name === namespace)) {
2609
+ deletion.retained.push({
2610
+ resource,
2611
+ policy: 'occupied-namespace',
2612
+ reason: 'The namespace remains owned by this RGD but contains resources that TypeKro will not delete implicitly.',
2613
+ });
2614
+ }
2615
+ }
2616
+ const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
2617
+ deletion.retained.push({
2618
+ resource: rgd,
2619
+ policy: deletion.blockers.length > 0 ? 'safety-proof-unavailable' : 'occupied-namespace',
2620
+ reason: 'The RGD is retained while an owned namespace remains, preventing orphaned lifecycle state.',
2621
+ });
2622
+ if (this.discoveredPlural) {
2623
+ deletion.retained.push({
2624
+ resource: deletionTarget('apiextensions.k8s.io/v1', 'CustomResourceDefinition', `${this.discoveredPlural}.${this.getSchemaGroup()}`),
2625
+ policy: 'generated-crd',
2626
+ reason: 'The generated CRD is retained Active while owned namespaces remain.',
2627
+ });
2628
+ }
2629
+ return finishDeletionResult(deletion, deletion.blockers.length > 0 ? 'blocked' : 'complete', {
2630
+ safe: true,
2631
+ guidance: deletion.blockers.length > 0
2632
+ ? 'Resolve the listed discovery or ownership blockers, then retry.'
2633
+ : 'The instance graph is gone. Occupied or adopted namespaces and their definitions were retained explicitly.',
2634
+ });
1504
2635
  }
1505
2636
  // 3. Delete the RGD and GATE on its 404 (findings #1 + #2). The RGD carries a KRO
1506
2637
  // finalizer; while KRO processes it, KRO's per-RGD dynamic controller is still
1507
2638
  // WATCHING the generated CRD's resources. Waiting for the RGD 404 first lets KRO
1508
2639
  // tear its controller down cleanly. The gate THROWS on timeout (no silent proceed).
1509
- await rollback.deleteResourceAndWait({ apiVersion: 'kro.run/v1alpha1', kind: 'ResourceGraphDefinition', name: this.rgdName }, { timeout });
2640
+ const rgdTarget = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
2641
+ try {
2642
+ await rollback.deleteResourceAndWait(rgdTarget, { timeout, abortSignal });
2643
+ deletion.deleted.push(rgdTarget);
2644
+ }
2645
+ catch (error) {
2646
+ if (abortSignal.aborted)
2647
+ throw abortSignal.reason ?? error;
2648
+ let live = rgdTarget;
2649
+ try {
2650
+ live = (await readDeletionResourceIdentity(k8sApi, rgdTarget)) ?? rgdTarget;
2651
+ }
2652
+ catch (readError) {
2653
+ deletion.blockers.push({
2654
+ code: 'CLEANUP_ERROR',
2655
+ message: `RGD deletion failed and blocker inspection also failed: ${ensureError(readError).message}`,
2656
+ resource: rgdTarget,
2657
+ retryable: true,
2658
+ retryGuidance: 'Restore Kubernetes API access, then retry deletion.',
2659
+ });
2660
+ }
2661
+ deletion.remaining.push(live);
2662
+ deletion.blockers.push(blockerForRemainingResource(live, `ResourceGraphDefinition ${this.rgdName} did not complete deletion: ${ensureError(error).message}`));
2663
+ return finishDeletionResult(deletion, live.deletionTimestamp ? 'progressing' : 'blocked', {
2664
+ safe: true,
2665
+ afterMs: 2_000,
2666
+ guidance: 'Retry after KRO clears the listed RGD finalizers.',
2667
+ });
2668
+ }
1510
2669
  this.logger.debug('RGD deleted and fully gone', { rgdName: this.rgdName });
1511
2670
  // 4. RETAIN the generated CRD. This matches the Alchemy path and KRO's own
1512
2671
  // `allowCRDDeletion=false` default. Initiating a best-effort delete is not harmless:
@@ -1522,6 +2681,17 @@ export class KroResourceFactoryImpl {
1522
2681
  ? { crdName: `${this.discoveredPlural}.${this.getSchemaGroup()}` }
1523
2682
  : {}),
1524
2683
  });
2684
+ if (this.discoveredPlural) {
2685
+ deletion.retained.push({
2686
+ resource: deletionTarget('apiextensions.k8s.io/v1', 'CustomResourceDefinition', `${this.discoveredPlural}.${this.getSchemaGroup()}`),
2687
+ policy: 'generated-crd',
2688
+ reason: 'Generated CRDs are retained Active by policy after the final instance is removed; administrative GC may delete them after proving they are unused.',
2689
+ });
2690
+ }
2691
+ return finishDeletionResult(deletion, 'complete', {
2692
+ safe: true,
2693
+ guidance: 'The instance graph and RGD are gone. The generated CRD was retained Active under the documented zero-instance policy.',
2694
+ });
1525
2695
  }
1526
2696
  /**
1527
2697
  * Emit this factory's KRO deployment as declarative alchemy **v2** resources — the v2
@@ -1545,6 +2715,7 @@ export class KroResourceFactoryImpl {
1545
2715
  */
1546
2716
  async toAlchemyResources(spec, opts) {
1547
2717
  this.assertNoKroPrerequisiteHookForDeclarative('toAlchemyResources()');
2718
+ this.assertNoReservedKroSpecField(spec);
1548
2719
  validateSpec(spec, this.schemaDefinition, {
1549
2720
  kind: this.schemaDefinition.kind,
1550
2721
  name: this.name,
@@ -1552,6 +2723,32 @@ export class KroResourceFactoryImpl {
1552
2723
  this.assertInstanceNamespaceOwnershipSafe(spec);
1553
2724
  const instanceNamespace = this.resolveInstanceNamespace(spec);
1554
2725
  const kubeConfigOptions = this.extractKubeConfigOptionsForAlchemy();
2726
+ const compiledBundle = this.compiledKroArtifactBundle(spec, opts);
2727
+ if (compiledBundle) {
2728
+ this.assertBundleCapabilities(compiledBundle.bundle, {
2729
+ host: 'alchemy',
2730
+ output: 'live',
2731
+ });
2732
+ const runtimeBindings = compiledBundle.bundle.operations.flatMap((operation) => {
2733
+ const strategy = operation.artifact.readiness.strategy;
2734
+ return strategy?.kind === 'runtime-binding'
2735
+ ? [
2736
+ {
2737
+ operationId: operation.id,
2738
+ binding: strategy.binding,
2739
+ classification: strategy.classification ?? {
2740
+ reason: 'unclassified-evaluator',
2741
+ },
2742
+ },
2743
+ ]
2744
+ : [];
2745
+ });
2746
+ if (runtimeBindings.length > 0) {
2747
+ throw new TypeKroError('KRO Alchemy declarations cannot persist runtime-only readiness functions. Register a portable readiness strategy or use standalone deployment.', 'KRO_ALCHEMY_RUNTIME_BINDING_UNSUPPORTED', { runtimeBindings });
2748
+ }
2749
+ return this.alchemyDeclarationsFromKroArtifactBundle(compiledBundle.bundle, kubeConfigOptions, compiledBundle.sensitiveBindings);
2750
+ }
2751
+ this.assertBundleCapabilities(undefined, { host: 'alchemy', output: 'live' });
1555
2752
  const kroDeletion = this.createAlchemyKroDeletionOptions(instanceNamespace);
1556
2753
  const prerequisiteDeclarations = this.prerequisiteAlchemyDeclarations(kubeConfigOptions);
1557
2754
  const prerequisiteIds = prerequisiteDeclarations.map((decl) => decl.id);
@@ -1570,8 +2767,12 @@ export class KroResourceFactoryImpl {
1570
2767
  // occupies it (no cross-stack refcount needed).
1571
2768
  const instanceNamespaceDeclarations = [];
1572
2769
  const instanceNamespaceIds = [];
1573
- for (const [hoistedNs, original] of this.concreteHoistedNamespaces(spec)) {
1574
- const decl = this.hoistedNamespaceAlchemyDeclaration(hoistedNs, this.buildHoistedNamespaceResource(hoistedNs, original, spec), kubeConfigOptions, prerequisiteIds);
2770
+ for (const [hoistedNs, { artifact, resource }] of this.materializedHoistedNamespaces(spec)) {
2771
+ resource.metadata.annotations = {
2772
+ ...resource.metadata.annotations,
2773
+ [NAMESPACE_OWNER_ANNOTATION]: this.rgdName,
2774
+ };
2775
+ const decl = this.hoistedNamespaceAlchemyDeclaration(hoistedNs, resource, artifact, kubeConfigOptions, prerequisiteIds);
1575
2776
  instanceNamespaceDeclarations.push(decl);
1576
2777
  instanceNamespaceIds.push(decl.id);
1577
2778
  }
@@ -1585,7 +2786,6 @@ export class KroResourceFactoryImpl {
1585
2786
  const singletonDeclarations = [];
1586
2787
  const singletonInstanceIds = [];
1587
2788
  for (const definition of this.discoverSingletonDefinitions(spec)) {
1588
- assertSingletonOwnerNamespaceOwnershipSafe(definition);
1589
2789
  const singletonFactory = this.singletonFactoryFor(definition);
1590
2790
  try {
1591
2791
  const decls = await singletonFactory.toAlchemyResources(definition.spec, {
@@ -1604,18 +2804,10 @@ export class KroResourceFactoryImpl {
1604
2804
  await singletonFactory.dispose?.();
1605
2805
  }
1606
2806
  }
1607
- // 1. RGD declaration (deployed once per factory; shared by all instances). Reuse the normal
1608
- // serializer so externalRef/forEach/includeWhen/readyWhen + singleton boundaries match
1609
- // non-alchemy KRO deploys.
1610
- //
1611
- // Load with JSON_SCHEMA to MATCH `serializeResourceGraphToYaml`'s dump schema. The dump uses
1612
- // JSON_SCHEMA (no YAML timestamp/`!!timestamp` type), so a string value that merely LOOKS like a
1613
- // date — e.g. an env var `"2026-06-01"` — is emitted UNQUOTED. Loading it back with js-yaml's
1614
- // DEFAULT schema (timestamp-aware) would coerce that scalar to a `Date` OBJECT, and the applied
1615
- // RGD would then carry an object where a string belongs — KRO rejects the whole graph
1616
- // (`GraphAccepted=False: expected string type ..., got object`) and it never reconciles. Matching
1617
- // the load schema to the dump makes the round-trip lossless (the scalar stays the string it was).
1618
- const rgdManifest = yaml.load(this.buildRgdYaml(spec), { schema: yaml.JSON_SCHEMA });
2807
+ // 1. RGD declaration (deployed once per factory; shared by all instances). The serializer
2808
+ // exposes the manifest directly, so Alchemy consumes the same representation without a
2809
+ // YAML dump/load round trip or timestamp-like scalar coercion.
2810
+ const rgdManifest = this.buildRgdManifest(spec);
1619
2811
  const rgdFactory = this.rgdProvider ??
1620
2812
  (await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
1621
2813
  const rgdEnhanced = rgdFactory(rgdManifest);
@@ -1695,7 +2887,15 @@ export class KroResourceFactoryImpl {
1695
2887
  }
1696
2888
  /** Serialize the factory's cluster connection so an alchemy resource can reconnect after rehydration. */
1697
2889
  extractKubeConfigOptionsForAlchemy() {
1698
- return extractSerializableKubeConfigOptions(this.getKubeConfig(), this.factoryOptions.skipTLSVerify === true ? true : undefined);
2890
+ const persistence = this.factoryOptions.alchemyKubeConfig ??
2891
+ (this.factoryOptions.kubeConfig === undefined
2892
+ ? { source: { kind: 'default' } }
2893
+ : undefined);
2894
+ const kubeConfig = this.factoryOptions.kubeConfig ?? (persistence?.source ? undefined : this.getKubeConfig());
2895
+ return extractSerializableKubeConfigOptions(kubeConfig, {
2896
+ ...(this.factoryOptions.skipTLSVerify === true ? { skipTLSVerifyOverride: true } : {}),
2897
+ ...(persistence ? { persistence } : {}),
2898
+ });
1699
2899
  }
1700
2900
  /** Build the finalizer-safe, shared-RGD-aware deletion metadata for this factory's instances. */
1701
2901
  createAlchemyKroDeletionOptions(instanceNamespace = this.namespace) {
@@ -1709,12 +2909,201 @@ export class KroResourceFactoryImpl {
1709
2909
  timeout: this.factoryOptions.timeout ?? 300000,
1710
2910
  };
1711
2911
  }
2912
+ kroDeletionForBundleOperation(bundle, operation) {
2913
+ if (operation.role !== 'resource-graph-definition' &&
2914
+ operation.role !== 'singleton-owner-rgd' &&
2915
+ operation.role !== 'instance' &&
2916
+ operation.role !== 'singleton-owner-instance') {
2917
+ return undefined;
2918
+ }
2919
+ const memberId = operation.sources[0]?.memberId;
2920
+ if (!memberId)
2921
+ return undefined;
2922
+ const rgdOperation = bundle.operations.find((candidate) => (candidate.role === 'resource-graph-definition' ||
2923
+ candidate.role === 'singleton-owner-rgd') &&
2924
+ candidate.sources.some((source) => source.memberId === memberId));
2925
+ const instanceOperation = bundle.operations.find((candidate) => (candidate.role === 'instance' || candidate.role === 'singleton-owner-instance') &&
2926
+ candidate.sources.some((source) => source.memberId === memberId));
2927
+ if (!rgdOperation || !instanceOperation)
2928
+ return undefined;
2929
+ const rgdIdentity = rgdOperation.artifact.identity;
2930
+ const instanceIdentity = instanceOperation.artifact.identity;
2931
+ if (!rgdIdentity || !instanceIdentity)
2932
+ return undefined;
2933
+ const apiVersion = instanceIdentity.apiVersion;
2934
+ const group = apiVersion.includes('/') ? apiVersion.split('/')[0] : undefined;
2935
+ return {
2936
+ apiVersion,
2937
+ kind: instanceIdentity.kind,
2938
+ namespace: instanceIdentity.namespace
2939
+ ? String(materializePlanValue(instanceIdentity.namespace))
2940
+ : this.namespace,
2941
+ rgdName: String(materializePlanValue(rgdIdentity.name)),
2942
+ ...(group ? { group } : {}),
2943
+ timeout: this.factoryOptions.timeout ?? 300000,
2944
+ };
2945
+ }
2946
+ async alchemyDeclarationsFromKroArtifactBundle(bundle, kubeConfigOptions, sensitiveBindings) {
2947
+ const encodedBundle = encodeKroArtifactBundle(bundle);
2948
+ const ordered = orderKroArtifactBundleOperations(bundle);
2949
+ const Redacted = Object.keys(sensitiveBindings).length > 0 ? await import('effect/Redacted') : undefined;
2950
+ const rgdFactory = this.rgdProvider ??
2951
+ (await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
2952
+ const resources = new Map();
2953
+ const operationSensitiveBindings = new Map();
2954
+ const operationArtifactUses = new Map();
2955
+ const declarationIds = new Map();
2956
+ for (const operation of ordered) {
2957
+ const bindingNames = planValueSensitiveBindingNames(operation.manifest);
2958
+ const missingBindings = bindingNames.filter((binding) => !Object.hasOwn(sensitiveBindings, binding));
2959
+ if (missingBindings.length > 0) {
2960
+ throw new TypeKroError(`KRO operation ${operation.id} has unresolved sensitive bindings: ${missingBindings.join(', ')}.`, 'KRO_SENSITIVE_BINDING_UNRESOLVED', { operationId: operation.id, bindings: missingBindings });
2961
+ }
2962
+ const redactedBindings = Redacted && bindingNames.length > 0
2963
+ ? Object.fromEntries(bindingNames.map((binding) => [binding, Redacted.make(sensitiveBindings[binding])]))
2964
+ : {};
2965
+ operationSensitiveBindings.set(operation.id, redactedBindings);
2966
+ const artifactOutputUses = collectArtifactOutputUses(operation.manifest);
2967
+ operationArtifactUses.set(operation.id, artifactOutputUses);
2968
+ const declarationArtifactOutputs = Object.fromEntries(bundle.artifactRequirements.map((requirement) => [
2969
+ requirement.id,
2970
+ Object.fromEntries(requirement.outputs.map((output) => [
2971
+ output,
2972
+ `__typekro_artifact_${encodeURIComponent(requirement.id)}_${encodeURIComponent(output)}__`,
2973
+ ])),
2974
+ ]));
2975
+ const manifest = materializeKroArtifactBundleOperation(operation, {
2976
+ sensitive: redactedBindings,
2977
+ ...(artifactOutputUses.length > 0 ? { artifactOutputs: declarationArtifactOutputs } : {}),
2978
+ });
2979
+ const artifact = operation.artifact;
2980
+ if (artifact.identity?.scope)
2981
+ setMetadataField(manifest, 'scope', artifact.identity.scope);
2982
+ setMetadataField(manifest, 'applyPolicy', artifact.apply);
2983
+ setResourceId(manifest, artifact.id);
2984
+ const strategy = artifact.readiness.strategy;
2985
+ if (strategy?.kind === 'registered') {
2986
+ const evaluator = resolvePortableReadinessStrategy(strategy);
2987
+ if (!evaluator) {
2988
+ throw new TypeKroError(`Registered readiness strategy ${strategy.id}@${strategy.revision} is unavailable.`, 'KRO_ARTIFACT_READINESS_UNAVAILABLE', { operationId: operation.id, artifactId: artifact.id });
2989
+ }
2990
+ setReadinessEvaluator(manifest, evaluator);
2991
+ }
2992
+ if (operation.role === 'hoisted-namespace') {
2993
+ const evidence = artifact.lifecycle.unusedEvidence;
2994
+ const evidenceInputs = evidence
2995
+ ? materializePlanValue(evidence.inputs)
2996
+ : undefined;
2997
+ manifest.metadata.annotations = {
2998
+ ...manifest.metadata.annotations,
2999
+ [NAMESPACE_OWNER_ANNOTATION]: String(evidenceInputs?.rgdName ?? this.rgdName),
3000
+ };
3001
+ }
3002
+ const enhanced = operation.role === 'resource-graph-definition' || operation.role === 'singleton-owner-rgd'
3003
+ ? rgdFactory(manifest)
3004
+ : manifest;
3005
+ setMetadataField(enhanced, 'applyPolicy', artifact.apply);
3006
+ resources.set(operation.id, enhanced);
3007
+ const declarationId = operation.role === 'hoisted-namespace'
3008
+ ? KroResourceFactoryImpl.hoistedNamespaceId(String(manifest.metadata.name))
3009
+ : operation.role === 'instance' || operation.role === 'singleton-owner-instance'
3010
+ ? this.instanceAlchemyId(enhanced)
3011
+ : createAlchemyResourceId(enhanced, artifact.identity?.scope === 'cluster'
3012
+ ? undefined
3013
+ : (manifest.metadata.namespace ?? this.namespace));
3014
+ if ([...declarationIds.values()].includes(declarationId)) {
3015
+ throw new TypeKroError(`KRO bundle operations collapse to duplicate Alchemy id ${declarationId}.`, 'KRO_ALCHEMY_DECLARATION_ID_COLLISION', { operationId: operation.id, declarationId });
3016
+ }
3017
+ declarationIds.set(operation.id, declarationId);
3018
+ }
3019
+ return ordered.map((operation) => {
3020
+ const resource = resources.get(operation.id);
3021
+ const declarationId = declarationIds.get(operation.id);
3022
+ if (!resource || !declarationId) {
3023
+ throw new TypeKroError(`KRO operation ${operation.id} was not prepared for Alchemy declaration emission.`, 'KRO_ALCHEMY_DECLARATION_MISSING', { operationId: operation.id });
3024
+ }
3025
+ const artifact = operation.artifact;
3026
+ const operationBindings = operationSensitiveBindings.get(operation.id);
3027
+ const artifactOutputUses = operationArtifactUses.get(operation.id) ?? [];
3028
+ const artifactRequirementIds = new Set(artifactOutputUses.map((use) => use.requirementId));
3029
+ const artifactRequirements = bundle.artifactRequirements.filter((requirement) => artifactRequirementIds.has(requirement.id));
3030
+ const manifest = resource;
3031
+ const direct = operation.role === 'kro-prerequisite' || operation.role === 'hoisted-namespace';
3032
+ const kroDeletion = this.kroDeletionForBundleOperation(bundle, operation);
3033
+ const evidence = artifact.lifecycle.unusedEvidence;
3034
+ const evidenceInputs = evidence
3035
+ ? materializePlanValue(evidence.inputs)
3036
+ : undefined;
3037
+ const namespace = operation.role === 'hoisted-namespace'
3038
+ ? String(manifest.metadata.name)
3039
+ : (manifest.metadata.namespace ?? this.namespace);
3040
+ return {
3041
+ id: declarationId,
3042
+ dependsOn: operation.dependencies.map((dependency) => {
3043
+ const declarationId = declarationIds.get(dependency);
3044
+ if (!declarationId) {
3045
+ throw new TypeKroError(`KRO operation ${operation.id} references an unemitted dependency ${dependency}.`, 'KRO_ALCHEMY_DEPENDENCY_MISSING', { operationId: operation.id, dependency });
3046
+ }
3047
+ return declarationId;
3048
+ }),
3049
+ ...(artifactRequirements.length > 0 ? { artifactRequirements } : {}),
3050
+ ...(artifactOutputUses.length > 0 ? { artifactOutputUses } : {}),
3051
+ props: {
3052
+ resource,
3053
+ resourceId: artifact.id,
3054
+ namespace,
3055
+ deploymentStrategy: direct ? 'direct' : 'kro',
3056
+ kubeConfigOptions,
3057
+ kroArtifactBundle: encodedBundle,
3058
+ kroArtifactOperationId: operation.id,
3059
+ ...(artifactRequirements.length > 0 ? { artifactRequirements } : {}),
3060
+ ...(artifactOutputUses.length > 0 ? { artifactOutputUses } : {}),
3061
+ ...(operationBindings && Object.keys(operationBindings).length > 0
3062
+ ? { sensitiveBindings: operationBindings }
3063
+ : {}),
3064
+ ...(kroDeletion ? { kroDeletion } : {}),
3065
+ ...(operation.role === 'hoisted-namespace'
3066
+ ? {
3067
+ namespaceEmptyGate: true,
3068
+ namespaceOwnerRgd: String(evidenceInputs?.rgdName ?? this.rgdName),
3069
+ namespacePreHoistQuery: {
3070
+ group: String(evidenceInputs?.group ?? this.getSchemaGroup()),
3071
+ version: String(evidenceInputs?.version ?? this.getSchemaVersion()),
3072
+ kind: String(evidenceInputs?.kind ?? this.schemaDefinition.kind),
3073
+ },
3074
+ }
3075
+ : {}),
3076
+ options: {
3077
+ waitForReady: operation.role === 'resource-graph-definition' ||
3078
+ operation.role === 'singleton-owner-rgd' ||
3079
+ operation.role === 'singleton-owner-instance' ||
3080
+ (operation.role === 'instance'
3081
+ ? (this.factoryOptions.waitForReady ?? true)
3082
+ : artifact.readiness.strategy !== undefined),
3083
+ timeout: this.factoryOptions.timeout ??
3084
+ (operation.role === 'resource-graph-definition' ||
3085
+ operation.role === 'singleton-owner-rgd'
3086
+ ? DEFAULT_RGD_TIMEOUT
3087
+ : DEFAULT_DEPLOYMENT_TIMEOUT),
3088
+ },
3089
+ },
3090
+ };
3091
+ });
3092
+ }
1712
3093
  /**
1713
3094
  * Get factory status
1714
3095
  */
1715
- async getStatus() {
1716
- const instances = await this.getInstances();
1717
- const rgdStatus = await this.getRGDStatus();
3096
+ async getStatus(opts) {
3097
+ if (opts?.operationSignal) {
3098
+ return this.getStatusWithinOperation(opts.operationSignal);
3099
+ }
3100
+ return runStandaloneOperation((abortSignal) => this.getStatusWithinOperation(abortSignal), {
3101
+ abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal],
3102
+ });
3103
+ }
3104
+ async getStatusWithinOperation(abortSignal) {
3105
+ const instances = await this.getInstancesWithinOperation(abortSignal);
3106
+ const rgdStatus = await this.getRGDStatusWithinOperation(abortSignal);
1718
3107
  return {
1719
3108
  name: this.name,
1720
3109
  mode: this.mode,
@@ -1726,7 +3115,16 @@ export class KroResourceFactoryImpl {
1726
3115
  /**
1727
3116
  * Get ResourceGraphDefinition status
1728
3117
  */
1729
- async getRGDStatus() {
3118
+ async getRGDStatus(opts) {
3119
+ if (opts?.operationSignal) {
3120
+ return this.getRGDStatusWithinOperation(opts.operationSignal);
3121
+ }
3122
+ return runStandaloneOperation((abortSignal) => this.getRGDStatusWithinOperation(abortSignal), {
3123
+ abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal],
3124
+ });
3125
+ }
3126
+ async getRGDStatusWithinOperation(abortSignal) {
3127
+ abortSignal.throwIfAborted();
1730
3128
  const kubeConfig = this.getKubeConfig();
1731
3129
  const k8sApi = createBunCompatibleKubernetesObjectApi(kubeConfig);
1732
3130
  try {
@@ -1738,6 +3136,7 @@ export class KroResourceFactoryImpl {
1738
3136
  name: this.rgdName,
1739
3137
  },
1740
3138
  });
3139
+ abortSignal.throwIfAborted();
1741
3140
  const rgd = response;
1742
3141
  // Map Kro's state to our phase enum
1743
3142
  let phase = 'pending';
@@ -1757,6 +3156,7 @@ export class KroResourceFactoryImpl {
1757
3156
  };
1758
3157
  }
1759
3158
  catch (error) {
3159
+ abortSignal.throwIfAborted();
1760
3160
  const k8sError = error;
1761
3161
  // Check for 404 in multiple ways since different API clients report it differently
1762
3162
  const bodyString = typeof k8sError.body === 'string' ? k8sError.body : JSON.stringify(k8sError.body || '');
@@ -1781,14 +3181,35 @@ export class KroResourceFactoryImpl {
1781
3181
  /**
1782
3182
  * Implementation of overloaded toYaml method
1783
3183
  */
1784
- toYaml(spec) {
3184
+ toYaml(spec, options) {
1785
3185
  this.assertNoKroPrerequisiteHookForDeclarative('toYaml()');
1786
3186
  if (spec) {
3187
+ this.assertNoReservedKroSpecField(spec);
1787
3188
  validateSpec(spec, this.schemaDefinition, {
1788
3189
  kind: this.schemaDefinition.kind,
1789
3190
  name: this.name,
1790
3191
  });
1791
3192
  this.assertInstanceNamespaceOwnershipSafe(spec);
3193
+ const compiledBundle = this.compiledKroArtifactBundle(spec);
3194
+ if (compiledBundle) {
3195
+ this.assertBundleCapabilities(compiledBundle.bundle, { host: null, output: 'static' });
3196
+ const operations = orderKroArtifactBundleOperations(compiledBundle.bundle).filter((operation) => operation.role === 'hoisted-namespace' ||
3197
+ operation.role === 'singleton-owner-instance' ||
3198
+ operation.role === 'instance');
3199
+ const requiredBindings = operations.flatMap((operation) => planValueSensitiveBindingNames(operation.manifest));
3200
+ const sensitiveBindings = resolveStaticYamlSensitiveBindings(requiredBindings, compiledBundle.sensitiveBindings, options, `KRO factory ${this.name} YAML`);
3201
+ const documents = operations.map((operation) => yaml
3202
+ .dump(materializeKroArtifactBundleOperation(operation, {
3203
+ sensitive: sensitiveBindings,
3204
+ }), {
3205
+ lineWidth: -1,
3206
+ noRefs: true,
3207
+ sortKeys: false,
3208
+ })
3209
+ .trimEnd());
3210
+ return joinYamlDocuments(documents.slice(0, -1), documents.at(-1) ?? '');
3211
+ }
3212
+ this.assertBundleCapabilities(undefined, { host: null, output: 'static' });
1792
3213
  // Generate CRD instance YAML
1793
3214
  const instanceName = generateInstanceName(spec, this.name);
1794
3215
  const customResource = this.createCustomResourceInstance(instanceName, spec);
@@ -1800,15 +3221,46 @@ export class KroResourceFactoryImpl {
1800
3221
  // so the consuming RGD's externalRef resolves. No-op without singletons.
1801
3222
  // Any owned workload Namespace hoisted out of the RGD graph leads, so the CR
1802
3223
  // always has a namespace to apply into and KRO never garbage-collects it.
1803
- const ownerYamls = singletonOwnerInstanceYamls(this.discoverSingletonDefinitions(spec));
3224
+ const ownerYamls = this.materializedSingletonOwnerInstances(spec).map((resource) => yaml
3225
+ .dump(JSON.parse(JSON.stringify(resource)), {
3226
+ lineWidth: -1,
3227
+ noRefs: true,
3228
+ sortKeys: false,
3229
+ })
3230
+ .trimEnd());
1804
3231
  const leadingInstanceYamls = [
1805
- ...[...this.concreteHoistedNamespaces(spec)].map(([ns, original]) => this.hoistedNamespaceYaml(ns, original, spec)),
3232
+ ...[...this.materializedHoistedNamespaces(spec).values()].map(({ resource }) => yaml
3233
+ .dump(JSON.parse(JSON.stringify(resource)), {
3234
+ lineWidth: -1,
3235
+ noRefs: true,
3236
+ sortKeys: false,
3237
+ })
3238
+ .trimEnd()),
1806
3239
  ...ownerYamls,
1807
3240
  ];
1808
3241
  return leadingInstanceYamls.length === 0
1809
3242
  ? instanceYaml
1810
3243
  : joinYamlDocuments(leadingInstanceYamls, instanceYaml);
1811
3244
  }
3245
+ const compiledBundle = this.compiledKroArtifactBundle();
3246
+ if (compiledBundle) {
3247
+ this.assertBundleCapabilities(compiledBundle.bundle, { host: null, output: 'static' });
3248
+ const documents = orderKroArtifactBundleOperations(compiledBundle.bundle)
3249
+ .filter((operation) => operation.role === 'kro-prerequisite' ||
3250
+ operation.role === 'singleton-owner-rgd' ||
3251
+ operation.role === 'resource-graph-definition')
3252
+ .map((operation) => yaml
3253
+ .dump(materializeKroArtifactBundleOperation(operation, {
3254
+ sensitive: compiledBundle.sensitiveBindings,
3255
+ }), {
3256
+ lineWidth: -1,
3257
+ noRefs: true,
3258
+ sortKeys: false,
3259
+ })
3260
+ .trimEnd());
3261
+ return joinYamlDocuments(documents.slice(0, -1), documents.at(-1) ?? '');
3262
+ }
3263
+ this.assertBundleCapabilities(undefined, { host: null, output: 'static' });
1812
3264
  const rgdYaml = this.buildRgdYaml();
1813
3265
  const prerequisiteYamls = this.prerequisiteResourceYamls();
1814
3266
  const leadingYamls = [...prerequisiteYamls, ...singletonRgdYamls(this.singletonDefinitions)];
@@ -1832,7 +3284,7 @@ export class KroResourceFactoryImpl {
1832
3284
  * symmetry with the callers and is deliberately NOT used to decide hoisting — see
1833
3285
  * {@link hoistedNamespaceRefs} / {@link resourcesForRgd}.
1834
3286
  */
1835
- buildRgdYaml(_spec) {
3287
+ buildRgdManifest(_spec) {
1836
3288
  if (this.factoryOptions.compositionAnalysis && !this.compositionAnalysisApplied) {
1837
3289
  this.compositionAnalysisApplied = true;
1838
3290
  applyAnalysisToResources(this.resources, this.factoryOptions.compositionAnalysis);
@@ -1842,13 +3294,15 @@ export class KroResourceFactoryImpl {
1842
3294
  // to it. typekro NEVER emits a Namespace into RGD YAML; each is applied as a
1843
3295
  // sibling (toYaml/toAlchemyResources/imperative deploy), so deleting the instance
1844
3296
  // can never garbage-collect the namespace holding its own finalizer.
1845
- const hoistIds = this.hoistedNamespaceRefs(this.resources);
1846
- const graphResources = this.resourcesForRgd(this.resources);
3297
+ const plannedResources = this.plannedResources();
3298
+ const hoistIds = this.hoistedNamespaceRefs(plannedResources);
3299
+ const graphResources = this.resourcesForRgd(plannedResources);
1847
3300
  // Rewrite references to a hoisted Namespace in the STATUS mappings too
1848
3301
  // (finding #6): a status CEL like `ns-${string(ownedNamespace.metadata.name)}`
1849
3302
  // would otherwise reference a resource removed from the graph and KRO would
1850
3303
  // reject the RGD. Each becomes the referenced Namespace's own name expression.
1851
3304
  const statusMappings = rewriteHoistedNamespaceRefsInValue(this.statusMappings, hoistIds);
3305
+ copyCompositionAnalysisMetadata(this.statusMappings, statusMappings);
1852
3306
  const nestedCel = rewriteHoistedNamespaceRefsInValue(this.getNestedStatusCel(), hoistIds);
1853
3307
  // #6 — REJECT (throw) HONESTLY, never silently drop: a status field whose only
1854
3308
  // reference was a hoisted Namespace becomes a schema-only expression
@@ -1857,10 +3311,12 @@ export class KroResourceFactoryImpl {
1857
3311
  // the declared one (the old warn-and-drop), fail loudly naming the field(s).
1858
3312
  // Resource-derived sibling fields are unaffected.
1859
3313
  assertNoHoistWeakenedStatusFields(this.statusMappings, hoistIds, this.name);
1860
- const aspectResources = applyAspects(graphResources, {
1861
- mode: 'kro',
1862
- aspects: this.factoryOptions.aspects ?? [],
1863
- });
3314
+ const aspectResources = this.factoryOptions.semanticCapture
3315
+ ? graphResources
3316
+ : applyAspects(graphResources, {
3317
+ mode: 'kro',
3318
+ aspects: this.factoryOptions.aspects ?? [],
3319
+ });
1864
3320
  // Strict CEL diagnostics gate: fail fast if the status CEL that is about
1865
3321
  // to be emitted references resources that are not part of this graph,
1866
3322
  // instead of shipping an RGD that KRO will mark Inactive on the cluster.
@@ -1869,6 +3325,19 @@ export class KroResourceFactoryImpl {
1869
3325
  }
1870
3326
  const kroSchema = generateKroSchemaFromArktype(this.name, this.schemaDefinition, aspectResources, statusMappings, nestedCel, this.factoryOptions.compositionOptions
1871
3327
  ?.schemaFieldValidations);
3328
+ const artifactOutputUses = this.kroArtifactOutputUses();
3329
+ if (artifactOutputUses.length > 0) {
3330
+ const byRequirement = new Map();
3331
+ for (const use of artifactOutputUses) {
3332
+ const current = byRequirement.get(use.requirementId) ?? [];
3333
+ current.push(use);
3334
+ byRequirement.set(use.requirementId, current);
3335
+ }
3336
+ kroSchema.spec[KRO_ARTIFACT_BINDINGS_SPEC_FIELD] = Object.fromEntries([...byRequirement.entries()].map(([requirementId, uses]) => [
3337
+ kroArtifactRequirementField(requirementId),
3338
+ Object.fromEntries(uses.map((use) => [kroArtifactOutputField(use.output), 'string'])),
3339
+ ]));
3340
+ }
1872
3341
  // Attach nested status CEL mappings as non-enumerable property so
1873
3342
  // serializeResourceGraphToYaml can inline virtual composition IDs.
1874
3343
  if (nestedCel && Object.keys(nestedCel).length > 0) {
@@ -1907,13 +3376,34 @@ export class KroResourceFactoryImpl {
1907
3376
  applyTernaryConditionalsToResources(aspectResources, kroSchema.__ternaryConditionals, kroSchema.__nestedStatusCel);
1908
3377
  }
1909
3378
  }
1910
- const rgdYaml = serializeResourceGraphToYaml(this.rgdName, aspectResources, { namespace: this.namespace }, kroSchema);
3379
+ const rgdManifest = serializeResourceGraphToManifest(this.rgdName, aspectResources, {
3380
+ ...this.factoryOptions.compositionOptions,
3381
+ namespace: this.namespace,
3382
+ }, kroSchema);
1911
3383
  // VALIDATE (finding #6): after hoisting, NO reference to any removed resource id
1912
3384
  // may remain anywhere in the emitted RGD. If one slipped through (a form the
1913
3385
  // rewrite didn't structurally cover), fail LOUDLY here rather than shipping an
1914
3386
  // RGD KRO will reject at runtime with a dangling `${...}` reference.
1915
- this.assertNoDanglingHoistedReferences(rgdYaml, new Set(hoistIds.keys()));
1916
- return rgdYaml;
3387
+ this.assertNoDanglingHoistedReferences(JSON.stringify(rgdManifest), new Set(hoistIds.keys()));
3388
+ return rgdManifest;
3389
+ }
3390
+ kroArtifactOutputUses() {
3391
+ const capture = this.factoryOptions.semanticCapture;
3392
+ if (!capture)
3393
+ return [];
3394
+ const configuredPlan = this.factoryOptions.plan ?? {};
3395
+ const plan = capture.planTemplate({
3396
+ ...configuredPlan,
3397
+ aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
3398
+ });
3399
+ return collectArtifactOutputUses({
3400
+ nodes: plan.nodes,
3401
+ outputs: plan.outputs,
3402
+ representationRequirements: plan.representationRequirements,
3403
+ });
3404
+ }
3405
+ buildRgdYaml(spec) {
3406
+ return serializeResourceGraphDefinitionToYaml(this.buildRgdManifest(spec), this.factoryOptions.compositionOptions);
1917
3407
  }
1918
3408
  /**
1919
3409
  * Assert the emitted RGD carries NO leftover reference to a hoisted (removed)
@@ -2019,7 +3509,8 @@ export class KroResourceFactoryImpl {
2019
3509
  * The `spec` (when the caller has one) lets the RGD hoist the composition's own
2020
3510
  * workload Namespace out of the graph with its schema-driven name resolved.
2021
3511
  */
2022
- async ensureRGDDeployed(spec) {
3512
+ async ensureRGDDeployed(spec, abortSignal) {
3513
+ abortSignal?.throwIfAborted();
2023
3514
  // Create DirectDeploymentEngine instance with KRO mode for CEL string generation
2024
3515
  const deploymentEngine = new DirectDeploymentEngine(this.getKubeConfig(), undefined, undefined, DeploymentMode.KRO);
2025
3516
  // Build the RGD YAML — shared with toYaml() so both call sites emit
@@ -2061,7 +3552,7 @@ export class KroResourceFactoryImpl {
2061
3552
  rgdManifest: JSON.stringify(rgdWithMetadata, null, 2),
2062
3553
  });
2063
3554
  try {
2064
- await this.applyKroPrerequisites(deploymentEngine);
3555
+ await this.applyKroPrerequisites(deploymentEngine, abortSignal);
2065
3556
  // Deploy RGD using DirectDeploymentEngine with readiness checking
2066
3557
  this.logger.info('Deploying RGD via engine', { rgdName: this.rgdName });
2067
3558
  await deploymentEngine.deployResource(deployableRGD, {
@@ -2069,10 +3560,11 @@ export class KroResourceFactoryImpl {
2069
3560
  namespace: this.namespace,
2070
3561
  waitForReady: true,
2071
3562
  timeout: this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT,
3563
+ ...(abortSignal ? { abortSignal } : {}),
2072
3564
  });
2073
3565
  this.logger.info('RGD accepted, waiting for CRD', { rgdName: this.rgdName });
2074
3566
  // Wait for the CRD to be created by Kro using DirectDeploymentEngine
2075
- await this.waitForCRDReadyWithEngine(deploymentEngine);
3567
+ await this.waitForCRDReadyWithEngine(deploymentEngine, abortSignal);
2076
3568
  this.logger.info('CRD ready', { rgdName: this.rgdName });
2077
3569
  }
2078
3570
  catch (error) {
@@ -2102,13 +3594,22 @@ export class KroResourceFactoryImpl {
2102
3594
  await deploymentEngine.dispose();
2103
3595
  }
2104
3596
  }
2105
- async applyKroPrerequisites(deploymentEngine) {
3597
+ async applyKroPrerequisites(deploymentEngine, abortSignal) {
2106
3598
  const prerequisites = this.factoryOptions.kroPrerequisites;
2107
3599
  if (!prerequisites?.resources?.length && !prerequisites?.beforeResourceGraphDefinition) {
2108
3600
  return;
2109
3601
  }
3602
+ const deployResource = this.kroPrerequisiteDeployResource(deploymentEngine, abortSignal);
3603
+ for (const resource of prerequisites.resources ?? []) {
3604
+ abortSignal?.throwIfAborted();
3605
+ await deployResource(resource);
3606
+ }
3607
+ await this.runKroPrerequisiteHook(deploymentEngine, deployResource, abortSignal);
3608
+ }
3609
+ kroPrerequisiteDeployResource(deploymentEngine, abortSignal) {
2110
3610
  const timeout = this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT;
2111
- const deployResource = async (resource, options = {}) => {
3611
+ return async (resource, options = {}) => {
3612
+ abortSignal?.throwIfAborted();
2112
3613
  const deployable = this.normalizePrerequisiteResource(resource, {
2113
3614
  ensureReadiness: options.waitForReady ?? false,
2114
3615
  });
@@ -2120,27 +3621,32 @@ export class KroResourceFactoryImpl {
2120
3621
  namespace: this.namespace,
2121
3622
  waitForReady: options.waitForReady ?? false,
2122
3623
  timeout,
3624
+ ...(abortSignal ? { abortSignal } : {}),
2123
3625
  });
2124
3626
  const crdName = this.prerequisiteCRDName(resource);
2125
3627
  if (crdName) {
2126
- await deploymentEngine.waitForCRDReady(crdName, timeout);
3628
+ await deploymentEngine.waitForCRDReady(crdName, timeout, abortSignal);
2127
3629
  }
2128
3630
  return deployed;
2129
3631
  };
2130
- for (const resource of prerequisites.resources ?? []) {
2131
- await deployResource(resource);
2132
- }
2133
- if (prerequisites.beforeResourceGraphDefinition) {
2134
- const context = {
2135
- kubernetesApi: deploymentEngine.getKubernetesApi(),
2136
- kubeConfig: this.getKubeConfig(),
2137
- namespace: this.namespace,
2138
- timeout,
2139
- deployResource,
2140
- waitForCRDReady: (crdName, waitTimeout = timeout) => deploymentEngine.waitForCRDReady(crdName, waitTimeout),
2141
- };
2142
- await prerequisites.beforeResourceGraphDefinition(context);
2143
- }
3632
+ }
3633
+ async runKroPrerequisiteHook(deploymentEngine, deployResource, abortSignal) {
3634
+ const hook = this.factoryOptions.kroPrerequisites?.beforeResourceGraphDefinition;
3635
+ if (!hook)
3636
+ return;
3637
+ abortSignal?.throwIfAborted();
3638
+ const timeout = this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT;
3639
+ const deploy = deployResource ?? this.kroPrerequisiteDeployResource(deploymentEngine, abortSignal);
3640
+ const context = {
3641
+ kubernetesApi: deploymentEngine.getKubernetesApi(),
3642
+ kubeConfig: this.getKubeConfig(),
3643
+ namespace: this.namespace,
3644
+ timeout,
3645
+ ...(abortSignal ? { abortSignal } : {}),
3646
+ deployResource: deploy,
3647
+ waitForCRDReady: (crdName, waitTimeout = timeout) => deploymentEngine.waitForCRDReady(crdName, waitTimeout, abortSignal),
3648
+ };
3649
+ await hook(context);
2144
3650
  }
2145
3651
  normalizePrerequisiteResource(resource, options = {}) {
2146
3652
  const name = resource.metadata?.name ?? 'unnamed';
@@ -2171,24 +3677,7 @@ export class KroResourceFactoryImpl {
2171
3677
  deployable.metadata.namespace = this.namespace;
2172
3678
  }
2173
3679
  if (this.prerequisiteCRDName(resource) && !getReadinessEvaluator(deployable)) {
2174
- setReadinessEvaluator(deployable, (liveResource) => {
2175
- const conditions = liveResource
2176
- .status?.conditions ?? [];
2177
- const established = conditions.find((condition) => condition.type === 'Established');
2178
- const namesAccepted = conditions.find((condition) => condition.type === 'NamesAccepted');
2179
- const ready = established?.status === 'True' && namesAccepted?.status === 'True';
2180
- return ready
2181
- ? {
2182
- ready: true,
2183
- message: 'CustomResourceDefinition is established and names are accepted',
2184
- }
2185
- : {
2186
- ready: false,
2187
- reason: 'ConditionsNotMet',
2188
- message: 'CustomResourceDefinition is not established and names accepted yet',
2189
- details: { conditions },
2190
- };
2191
- });
3680
+ setReadinessEvaluator(deployable, createCustomResourceDefinitionReadinessEvaluator());
2192
3681
  }
2193
3682
  else if (options.attachFallbackReadiness && !getReadinessEvaluator(deployable)) {
2194
3683
  setReadinessEvaluator(deployable, createAlwaysReadyEvaluator(resource.kind));
@@ -2199,6 +3688,34 @@ export class KroResourceFactoryImpl {
2199
3688
  return deployable;
2200
3689
  }
2201
3690
  prerequisiteResourceYamls() {
3691
+ const capture = this.factoryOptions.semanticCapture;
3692
+ if (capture) {
3693
+ const configuredPlan = this.factoryOptions.plan ?? {};
3694
+ const plan = capture.planTemplate({
3695
+ ...configuredPlan,
3696
+ aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
3697
+ });
3698
+ const artifacts = compileKroArtifactPlan(plan, {
3699
+ strict: true,
3700
+ rgdName: this.rgdName,
3701
+ ...this.prerequisiteArtifactCompilationInputs(),
3702
+ ...(this.factoryOptions.applyPolicy
3703
+ ? { outerApplyPolicy: this.factoryOptions.applyPolicy }
3704
+ : {}),
3705
+ });
3706
+ return kroArtifactPlanToSupportingResources(artifacts, {
3707
+ readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
3708
+ resolveReadinessStrategy: resolvePortableReadinessStrategy,
3709
+ })
3710
+ .filter(({ artifact }) => artifact.role === 'kro-prerequisite')
3711
+ .map(({ resource }) => yaml
3712
+ .dump(JSON.parse(JSON.stringify(resource)), {
3713
+ lineWidth: -1,
3714
+ noRefs: true,
3715
+ sortKeys: false,
3716
+ })
3717
+ .trimEnd());
3718
+ }
2202
3719
  return (this.factoryOptions.kroPrerequisites?.resources ?? []).map((resource) => yaml
2203
3720
  .dump(this.toSerializablePrerequisiteResource(resource), {
2204
3721
  lineWidth: -1,
@@ -2207,6 +3724,196 @@ export class KroResourceFactoryImpl {
2207
3724
  })
2208
3725
  .trimEnd());
2209
3726
  }
3727
+ prerequisiteArtifactCompilationInputs() {
3728
+ const supportingArtifacts = [];
3729
+ const outerEdges = [];
3730
+ let previousId;
3731
+ for (const prerequisite of this.factoryOptions.kroPrerequisites?.resources ?? []) {
3732
+ const normalized = this.normalizePrerequisiteResource(prerequisite, {
3733
+ attachFallbackReadiness: true,
3734
+ });
3735
+ const id = normalized.id;
3736
+ const serialized = this.toSerializablePrerequisiteResource(prerequisite);
3737
+ const scope = getMetadataField(normalized, 'scope') === 'cluster' ? 'cluster' : 'namespaced';
3738
+ const namespace = scope === 'namespaced' && typeof normalized.metadata.namespace === 'string'
3739
+ ? lowerPlanValue(normalized.metadata.namespace).value
3740
+ : undefined;
3741
+ const evaluator = getReadinessEvaluator(normalized);
3742
+ const portableStrategy = evaluator ? getPortableReadinessStrategy(evaluator) : undefined;
3743
+ const runtimeBinding = `readiness:kro-prerequisite:${id}`;
3744
+ supportingArtifacts.push({
3745
+ id,
3746
+ role: 'kro-prerequisite',
3747
+ desired: lowerPlanValue(serialized).value,
3748
+ identity: {
3749
+ apiVersion: normalized.apiVersion,
3750
+ kind: normalized.kind,
3751
+ name: lowerPlanValue(normalized.metadata.name).value,
3752
+ ...(namespace ? { namespace } : {}),
3753
+ scope,
3754
+ },
3755
+ lifecycle: {
3756
+ creation: 'adopt',
3757
+ management: 'authoritative',
3758
+ deletion: 'delete',
3759
+ instancing: { kind: 'per-cluster' },
3760
+ sharing: 'exclusive',
3761
+ },
3762
+ readiness: {
3763
+ activation: [],
3764
+ readyWhen: [],
3765
+ ...(portableStrategy
3766
+ ? { strategy: portableStrategy }
3767
+ : evaluator
3768
+ ? {
3769
+ strategy: {
3770
+ kind: 'runtime-binding',
3771
+ binding: runtimeBinding,
3772
+ version: 1,
3773
+ },
3774
+ }
3775
+ : {}),
3776
+ },
3777
+ });
3778
+ if (previousId) {
3779
+ outerEdges.push({ kind: 'existence', prerequisite: previousId, dependent: id });
3780
+ }
3781
+ previousId = id;
3782
+ }
3783
+ if (previousId) {
3784
+ outerEdges.push({
3785
+ kind: 'existence',
3786
+ prerequisite: previousId,
3787
+ dependent: '__typekro_rgd__',
3788
+ });
3789
+ }
3790
+ return { supportingArtifacts, outerEdges };
3791
+ }
3792
+ outerArtifactCompilationInputs(spec) {
3793
+ const prerequisiteInputs = this.prerequisiteArtifactCompilationInputs();
3794
+ if (spec === undefined)
3795
+ return prerequisiteInputs;
3796
+ const supportingArtifacts = [...prerequisiteInputs.supportingArtifacts];
3797
+ const outerEdges = [...prerequisiteInputs.outerEdges];
3798
+ const lastPrerequisite = prerequisiteInputs.supportingArtifacts.at(-1)?.id;
3799
+ for (const [name, original] of this.concreteHoistedNamespaces(spec)) {
3800
+ const id = KroResourceFactoryImpl.hoistedNamespaceId(name);
3801
+ const manifest = this.hoistedNamespaceManifest(name, original, spec);
3802
+ supportingArtifacts.push({
3803
+ id,
3804
+ role: 'hoisted-namespace',
3805
+ desired: lowerPlanValue(manifest).value,
3806
+ identity: {
3807
+ apiVersion: 'v1',
3808
+ kind: 'Namespace',
3809
+ name: lowerPlanValue(name).value,
3810
+ scope: 'cluster',
3811
+ },
3812
+ lifecycle: {
3813
+ creation: 'adopt',
3814
+ management: 'authoritative',
3815
+ deletion: 'delete-when-unused',
3816
+ instancing: { kind: 'per-scope', key: lowerPlanValue(name).value },
3817
+ sharing: 'shareable',
3818
+ unusedEvidence: {
3819
+ provider: 'typekro.hoisted-namespace-empty-owned',
3820
+ version: 1,
3821
+ inputs: lowerPlanValue({
3822
+ rgdName: this.rgdName,
3823
+ group: this.getSchemaGroup(),
3824
+ version: this.getSchemaVersion(),
3825
+ kind: this.schemaDefinition.kind,
3826
+ }).value,
3827
+ },
3828
+ },
3829
+ apply: {
3830
+ strategy: 'server-side-apply',
3831
+ fieldManager: 'typekro',
3832
+ fieldConflictPolicy: 'force-owned-fields',
3833
+ immutableFieldPolicy: 'fail',
3834
+ },
3835
+ });
3836
+ if (lastPrerequisite) {
3837
+ outerEdges.push({
3838
+ kind: 'existence',
3839
+ prerequisite: lastPrerequisite,
3840
+ dependent: id,
3841
+ });
3842
+ }
3843
+ outerEdges.push({
3844
+ kind: 'existence',
3845
+ prerequisite: id,
3846
+ dependent: '__typekro_rgd__',
3847
+ });
3848
+ }
3849
+ return { supportingArtifacts, outerEdges };
3850
+ }
3851
+ materializedHoistedNamespaces(spec) {
3852
+ const capture = this.factoryOptions.semanticCapture;
3853
+ if (!capture) {
3854
+ return new Map([...this.concreteHoistedNamespaces(spec)].map(([name, original]) => [
3855
+ name,
3856
+ { resource: this.hoistedNamespaceManifest(name, original, spec) },
3857
+ ]));
3858
+ }
3859
+ const configuredPlan = this.factoryOptions.plan ?? {};
3860
+ const plan = capture.planTemplate({
3861
+ ...configuredPlan,
3862
+ aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
3863
+ });
3864
+ const artifacts = compileKroArtifactPlan(plan, {
3865
+ strict: true,
3866
+ rgdName: this.rgdName,
3867
+ ...this.outerArtifactCompilationInputs(spec),
3868
+ ...(this.factoryOptions.applyPolicy
3869
+ ? { outerApplyPolicy: this.factoryOptions.applyPolicy }
3870
+ : {}),
3871
+ });
3872
+ return new Map(kroArtifactPlanToSupportingResources(artifacts, {
3873
+ readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
3874
+ resolveReadinessStrategy: resolvePortableReadinessStrategy,
3875
+ })
3876
+ .filter(({ artifact }) => artifact.role === 'hoisted-namespace')
3877
+ .map(({ artifact, resource }) => [String(resource.metadata.name), { artifact, resource }]));
3878
+ }
3879
+ materializedSingletonOwnerInstances(spec) {
3880
+ const capture = this.factoryOptions.semanticCapture;
3881
+ if (!capture) {
3882
+ return singletonOwnerInstanceManifests(this.discoverSingletonDefinitions(spec));
3883
+ }
3884
+ const configuredPlan = this.factoryOptions.plan ?? {};
3885
+ const plan = capture.planTemplate({
3886
+ ...configuredPlan,
3887
+ aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
3888
+ });
3889
+ const artifacts = compileKroArtifactPlan(plan, {
3890
+ strict: true,
3891
+ rgdName: this.rgdName,
3892
+ ...this.outerArtifactCompilationInputs(spec),
3893
+ ...(this.factoryOptions.applyPolicy
3894
+ ? { outerApplyPolicy: this.factoryOptions.applyPolicy }
3895
+ : {}),
3896
+ });
3897
+ return kroArtifactPlanToSupportingResources(artifacts, {
3898
+ readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
3899
+ resolveReadinessStrategy: resolvePortableReadinessStrategy,
3900
+ })
3901
+ .filter(({ artifact }) => artifact.role === 'singleton-owner')
3902
+ .map(({ resource }) => resource);
3903
+ }
3904
+ prerequisiteRuntimeReadinessEvaluators() {
3905
+ const bindings = {};
3906
+ for (const prerequisite of this.factoryOptions.kroPrerequisites?.resources ?? []) {
3907
+ const normalized = this.normalizePrerequisiteResource(prerequisite, {
3908
+ attachFallbackReadiness: true,
3909
+ });
3910
+ const evaluator = getReadinessEvaluator(normalized);
3911
+ if (evaluator && !getPortableReadinessStrategy(evaluator)) {
3912
+ bindings[`readiness:kro-prerequisite:${normalized.id}`] = evaluator;
3913
+ }
3914
+ }
3915
+ return bindings;
3916
+ }
2210
3917
  toSerializablePrerequisiteResource(resource) {
2211
3918
  const normalized = this.normalizePrerequisiteResource(resource);
2212
3919
  const toJSON = normalized.toJSON;
@@ -2218,6 +3925,60 @@ export class KroResourceFactoryImpl {
2218
3925
  }
2219
3926
  prerequisiteAlchemyDeclarations(kubeConfigOptions) {
2220
3927
  const timeout = this.factoryOptions.timeout;
3928
+ const capture = this.factoryOptions.semanticCapture;
3929
+ if (capture) {
3930
+ const configuredPlan = this.factoryOptions.plan ?? {};
3931
+ const plan = capture.planTemplate({
3932
+ ...configuredPlan,
3933
+ aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
3934
+ });
3935
+ const artifacts = compileKroArtifactPlan(plan, {
3936
+ strict: true,
3937
+ rgdName: this.rgdName,
3938
+ ...this.prerequisiteArtifactCompilationInputs(),
3939
+ ...(this.factoryOptions.applyPolicy
3940
+ ? { outerApplyPolicy: this.factoryOptions.applyPolicy }
3941
+ : {}),
3942
+ });
3943
+ const materialized = kroArtifactPlanToSupportingResources(artifacts, {
3944
+ readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
3945
+ resolveReadinessStrategy: resolvePortableReadinessStrategy,
3946
+ }).filter(({ artifact }) => artifact.role === 'kro-prerequisite');
3947
+ const declarationIdByArtifactId = new Map(materialized.map(({ artifact, resource }) => {
3948
+ const namespaceForId = artifact.identity?.scope === 'cluster' ? undefined : this.namespace;
3949
+ return [artifact.id, createAlchemyResourceId(resource, namespaceForId)];
3950
+ }));
3951
+ return materialized.map(({ artifact, resource }) => {
3952
+ const declarationId = declarationIdByArtifactId.get(artifact.id);
3953
+ if (!declarationId) {
3954
+ throw new TypeKroError(`KRO prerequisite ${artifact.id} has no Alchemy declaration identity.`, 'KRO_ALCHEMY_DECLARATION_MISSING', { artifactId: artifact.id });
3955
+ }
3956
+ const dependsOn = artifacts.edges.flatMap((edge) => {
3957
+ if ((edge.kind !== 'existence' && edge.kind !== 'ready') ||
3958
+ edge.dependent !== artifact.id) {
3959
+ return [];
3960
+ }
3961
+ const dependencyId = declarationIdByArtifactId.get(edge.prerequisite);
3962
+ return dependencyId ? [dependencyId] : [];
3963
+ });
3964
+ return {
3965
+ id: declarationId,
3966
+ dependsOn,
3967
+ props: {
3968
+ resource: resource,
3969
+ resourceId: artifact.id,
3970
+ namespace: this.namespace,
3971
+ deploymentStrategy: 'direct',
3972
+ kubeConfigOptions,
3973
+ options: {
3974
+ waitForReady: resource.apiVersion === 'apiextensions.k8s.io/v1' &&
3975
+ resource.kind === 'CustomResourceDefinition',
3976
+ ...(timeout !== undefined && { timeout }),
3977
+ },
3978
+ },
3979
+ };
3980
+ });
3981
+ }
2221
3982
  const declarations = [];
2222
3983
  for (const resource of this.factoryOptions.kroPrerequisites?.resources ?? []) {
2223
3984
  const normalized = this.normalizePrerequisiteResource(resource, {
@@ -2262,7 +4023,10 @@ export class KroResourceFactoryImpl {
2262
4023
  return getResourceScope(resource);
2263
4024
  }
2264
4025
  canDefaultPrerequisiteNamespace(resource) {
2265
- return this.prerequisiteScope(resource) === 'namespaced';
4026
+ if (Reflect.get(resource, 'scope') === 'namespaced')
4027
+ return true;
4028
+ return (this.prerequisiteScope(resource) === 'namespaced' &&
4029
+ getMetadataField(resource, 'scopeProvenance') === 'explicit');
2266
4030
  }
2267
4031
  /**
2268
4032
  * Wait for the CRD to be created by Kro using DirectDeploymentEngine.
@@ -2272,11 +4036,11 @@ export class KroResourceFactoryImpl {
2272
4036
  * is authoritative, and client-side heuristics cannot handle all cases
2273
4037
  * (e.g., already-plural kind names that shouldn't get an extra "s").
2274
4038
  */
2275
- async waitForCRDReadyWithEngine(deploymentEngine) {
4039
+ async waitForCRDReadyWithEngine(deploymentEngine, abortSignal) {
2276
4040
  if (typeof deploymentEngine.waitForCRDByKindAndGroup !== 'function') {
2277
4041
  throw new ResourceGraphFactoryError(`deploymentEngine.waitForCRDByKindAndGroup is not a function. Available methods: ${Object.getOwnPropertyNames(Object.getPrototypeOf(deploymentEngine)).join(', ')}`, this.name, 'deployment');
2278
4042
  }
2279
- const { plural } = await deploymentEngine.waitForCRDByKindAndGroup(this.schemaDefinition.kind, this.getSchemaGroup(), this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT);
4043
+ const { plural } = await deploymentEngine.waitForCRDByKindAndGroup(this.schemaDefinition.kind, this.getSchemaGroup(), this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT, abortSignal);
2280
4044
  this.discoveredPlural = plural;
2281
4045
  }
2282
4046
  /**
@@ -2512,6 +4276,22 @@ export class KroResourceFactoryImpl {
2512
4276
  /**
2513
4277
  * Create custom resource instance
2514
4278
  */
4279
+ instanceManifestMetadata(spec, singletonSpecFingerprint) {
4280
+ const hoistedNamespaceNames = [...this.concreteHoistedNamespaces(spec).keys()];
4281
+ return {
4282
+ labels: {
4283
+ 'typekro.io/factory': this.name,
4284
+ 'typekro.io/mode': this.mode,
4285
+ [INSTANCE_RGD_LABEL]: this.rgdName,
4286
+ },
4287
+ annotations: {
4288
+ ...(singletonSpecFingerprint
4289
+ ? { 'typekro.io/singleton-spec-fingerprint': singletonSpecFingerprint }
4290
+ : {}),
4291
+ [HOISTED_NAMESPACES_ANNOTATION]: JSON.stringify(hoistedNamespaceNames),
4292
+ },
4293
+ };
4294
+ }
2515
4295
  createCustomResourceInstance(instanceName, spec, singletonSpecFingerprint) {
2516
4296
  const apiVersion = this.getInstanceApiVersion();
2517
4297
  // DURABLE RECORD (finding #4): stamp the CONCRETE hoisted-namespace names on the CR so
@@ -2521,28 +4301,27 @@ export class KroResourceFactoryImpl {
2521
4301
  // empty [] — so the pre-hoist guard can distinguish "this instance hoists zero namespaces"
2522
4302
  // (a valid, safe record) from a genuinely-legacy CR that predates the annotation (missing →
2523
4303
  // fail closed). Omitting [] made an ordinary namespace-less composition's 2nd deploy throw.
2524
- const hoistedNamespaceNames = [...this.concreteHoistedNamespaces(spec).keys()];
2525
- const annotations = {
2526
- ...(singletonSpecFingerprint
2527
- ? { 'typekro.io/singleton-spec-fingerprint': singletonSpecFingerprint }
2528
- : {}),
2529
- [HOISTED_NAMESPACES_ANNOTATION]: JSON.stringify(hoistedNamespaceNames),
2530
- };
2531
- return {
4304
+ const { labels, annotations } = this.instanceManifestMetadata(spec, singletonSpecFingerprint);
4305
+ const legacyManifest = {
2532
4306
  apiVersion,
2533
4307
  kind: this.schemaDefinition.kind,
2534
4308
  metadata: {
2535
4309
  name: instanceName,
2536
4310
  namespace: this.resolveInstanceNamespace(spec),
2537
- labels: {
2538
- 'typekro.io/factory': this.name,
2539
- 'typekro.io/mode': this.mode,
2540
- [INSTANCE_RGD_LABEL]: this.rgdName,
2541
- },
4311
+ labels,
2542
4312
  ...(Object.keys(annotations).length > 0 ? { annotations } : {}),
2543
4313
  },
2544
4314
  spec,
2545
4315
  };
4316
+ const compilation = this.compiledKroArtifactPlan(spec, {
4317
+ instanceNameOverride: instanceName,
4318
+ ...(singletonSpecFingerprint ? { singletonSpecFingerprint } : {}),
4319
+ });
4320
+ if (!compilation)
4321
+ return legacyManifest;
4322
+ return kroArtifactPlanToInstanceResource(compilation.artifacts, {
4323
+ sensitive: compilation.sensitiveBindings,
4324
+ });
2546
4325
  }
2547
4326
  /**
2548
4327
  * Labels/annotations marking a hoisted workload Namespace as SHARED, RETAINED
@@ -2670,34 +4449,16 @@ export class KroResourceFactoryImpl {
2670
4449
  ...(originalSpec !== undefined && originalSpec !== null ? { spec: originalSpec } : {}),
2671
4450
  };
2672
4451
  }
2673
- /**
2674
- * A workload Namespace this composition owns, HOISTED out of the RGD graph and
2675
- * emitted as a retained resource instead. It is created OUTSIDE the KRO graph —
2676
- * deps-first in GitOps/alchemy, and applied directly in the imperative path — so
2677
- * KRO never garbage-collects it and the instance's finalizer can never be
2678
- * stranded by its own namespace terminating.
2679
- *
2680
- * The COMPLETE original Namespace configuration is preserved (finding #5) — see
2681
- * {@link mergedHoistedNamespaceMetadata}. It is a SHARED SINGLETON deduped by the
2682
- * namespace NAME (collision-free id, see {@link hoistedNamespaceId}), so every
2683
- * consumer/stack targeting the SAME workload namespace emits the SAME declaration
2684
- * — alchemy dedupes them to one retained owner rather than N fighting copies.
2685
- */
2686
- buildHoistedNamespaceResource(workloadNamespace, original, spec) {
4452
+ /** Host-neutral desired state for one hoisted Namespace. */
4453
+ hoistedNamespaceManifest(workloadNamespace, original, spec) {
2687
4454
  const merged = KroResourceFactoryImpl.mergedHoistedNamespaceMetadata(workloadNamespace, original, spec);
2688
- return namespaceResource({
4455
+ return {
4456
+ apiVersion: 'v1',
4457
+ kind: 'Namespace',
2689
4458
  metadata: {
2690
4459
  name: merged.name,
2691
4460
  labels: merged.labels,
2692
- // OWNERSHIP RECORD (findings #4b + #2): the declarative alchemy path stamps this
2693
- // namespace as owned by THIS composition's RGD (the annotation the empty-gated
2694
- // teardown checks). This build step is cluster-free, so it cannot yet distinguish
2695
- // create-vs-adopt — that is resolved at DEPLOY time by
2696
- // `_preserveHoistedNamespaceAdoption` (resource-registration.ts), which reads the
2697
- // live namespace and STRIPS this stamp when adopting a pre-existing namespace, so
2698
- // teardown never deletes a namespace typekro merely adopted. Ownership is the
2699
- // PRIMARY teardown guard; the empty-gate is the secondary one.
2700
- annotations: { ...merged.annotations, [NAMESPACE_OWNER_ANNOTATION]: this.rgdName },
4461
+ annotations: merged.annotations,
2701
4462
  ...(merged.finalizers !== undefined ? { finalizers: merged.finalizers } : {}),
2702
4463
  ...(merged.ownerReferences !== undefined
2703
4464
  ? {
@@ -2705,24 +4466,29 @@ export class KroResourceFactoryImpl {
2705
4466
  }
2706
4467
  : {}),
2707
4468
  },
2708
- ...(merged.spec !== undefined
2709
- ? {
2710
- spec: merged.spec,
2711
- }
2712
- : {}),
2713
- id: KroResourceFactoryImpl.hoistedNamespaceId(workloadNamespace),
2714
- });
4469
+ ...(merged.spec !== undefined ? { spec: merged.spec } : {}),
4470
+ };
2715
4471
  }
2716
- hoistedNamespaceAlchemyDeclaration(workloadNamespace, nsEnhanced, kubeConfigOptions, dependsOn) {
4472
+ hoistedNamespaceAlchemyDeclaration(workloadNamespace, nsResource, artifact, kubeConfigOptions, dependsOn) {
2717
4473
  const timeout = this.factoryOptions.timeout;
2718
- const resourceId = nsEnhanced.id;
4474
+ const resourceId = artifact?.id ?? nsResource.id;
4475
+ const evidence = artifact?.lifecycle.unusedEvidence;
4476
+ if (artifact &&
4477
+ (artifact.lifecycle.creation !== 'adopt' ||
4478
+ artifact.lifecycle.deletion !== 'delete-when-unused' ||
4479
+ evidence?.provider !== 'typekro.hoisted-namespace-empty-owned')) {
4480
+ throw new TypeKroError(`Hoisted Namespace artifact ${artifact.id} has an unsupported lifecycle contract.`, 'INVALID_ARTIFACT_LIFECYCLE', { artifactId: artifact.id });
4481
+ }
4482
+ const evidenceInputs = evidence
4483
+ ? materializePlanValue(evidence.inputs)
4484
+ : undefined;
2719
4485
  return {
2720
4486
  // Collision-free (finding #7) and cluster-scoped (no namespace segment):
2721
4487
  // distinct workload namespaces → distinct ids; same name → same id (dedup).
2722
4488
  id: KroResourceFactoryImpl.hoistedNamespaceId(workloadNamespace),
2723
4489
  dependsOn,
2724
4490
  props: {
2725
- resource: nsEnhanced,
4491
+ resource: nsResource,
2726
4492
  ...(resourceId !== undefined ? { resourceId } : {}),
2727
4493
  namespace: workloadNamespace,
2728
4494
  deploymentStrategy: 'direct',
@@ -2733,16 +4499,16 @@ export class KroResourceFactoryImpl {
2733
4499
  // declaration) are gone — the load-bearing delete-after-RGD ordering (a
2734
4500
  // namespace deleted while the CR is still inside it re-creates the finalizer
2735
4501
  // deadlock).
2736
- namespaceEmptyGate: true,
4502
+ namespaceEmptyGate: artifact?.lifecycle.deletion === 'delete-when-unused' || artifact === undefined,
2737
4503
  // Ownership record for the empty-gated delete (finding #4): only a namespace
2738
4504
  // carrying this RGD's `created-by-rgd` annotation is a candidate.
2739
- namespaceOwnerRgd: this.rgdName,
4505
+ namespaceOwnerRgd: String(evidenceInputs?.rgdName ?? this.rgdName),
2740
4506
  // CRD coordinates so the alchemy pre-hoist check enumerates EVERY existing
2741
4507
  // instance of this shared RGD (finding #7), not just the incoming namespace.
2742
4508
  namespacePreHoistQuery: {
2743
- group: this.getSchemaGroup(),
2744
- version: this.getSchemaVersion(),
2745
- kind: this.schemaDefinition.kind,
4509
+ group: String(evidenceInputs?.group ?? this.getSchemaGroup()),
4510
+ version: String(evidenceInputs?.version ?? this.getSchemaVersion()),
4511
+ kind: String(evidenceInputs?.kind ?? this.schemaDefinition.kind),
2746
4512
  },
2747
4513
  kubeConfigOptions,
2748
4514
  options: {
@@ -2752,25 +4518,6 @@ export class KroResourceFactoryImpl {
2752
4518
  },
2753
4519
  };
2754
4520
  }
2755
- hoistedNamespaceYaml(workloadNamespace, original, spec) {
2756
- const merged = KroResourceFactoryImpl.mergedHoistedNamespaceMetadata(workloadNamespace, original, spec);
2757
- return yaml
2758
- .dump({
2759
- apiVersion: 'v1',
2760
- kind: 'Namespace',
2761
- metadata: {
2762
- name: merged.name,
2763
- labels: merged.labels,
2764
- annotations: merged.annotations,
2765
- ...(merged.finalizers !== undefined ? { finalizers: merged.finalizers } : {}),
2766
- ...(merged.ownerReferences !== undefined
2767
- ? { ownerReferences: merged.ownerReferences }
2768
- : {}),
2769
- },
2770
- ...(merged.spec !== undefined ? { spec: merged.spec } : {}),
2771
- }, { lineWidth: -1, noRefs: true, sortKeys: false })
2772
- .trimEnd();
2773
- }
2774
4521
  assertInstanceNamespaceOwnershipSafe(spec) {
2775
4522
  const compositionFn = this.factoryOptions.compositionFn;
2776
4523
  assertKroInstanceNamespaceOwnershipSafe({
@@ -2786,11 +4533,6 @@ export class KroResourceFactoryImpl {
2786
4533
  ...(compositionFn ? { compositionFn } : {}),
2787
4534
  });
2788
4535
  }
2789
- assertSingletonOwnersNamespaceOwnershipSafe(spec) {
2790
- for (const definition of this.discoverSingletonDefinitions(spec)) {
2791
- assertSingletonOwnerNamespaceOwnershipSafe(definition);
2792
- }
2793
- }
2794
4536
  /**
2795
4537
  * Create an Enhanced proxy for the instance with mixed static/dynamic hydration
2796
4538
  */
@@ -3013,7 +4755,7 @@ export class KroResourceFactoryImpl {
3013
4755
  * Wait for Kro instance to be ready with Kro-specific logic.
3014
4756
  * Delegates to the shared `waitForKroInstanceReady` in `kro-readiness.ts`.
3015
4757
  */
3016
- async waitForKroInstanceReady(instanceName, timeout, instanceNamespace = this.resolveInstanceNamespace()) {
4758
+ async waitForKroInstanceReady(instanceName, timeout, instanceNamespace = this.resolveInstanceNamespace(), abortSignal) {
3017
4759
  const apiVersion = this.getInstanceApiVersion();
3018
4760
  const kubeConfig = this.getKubeConfig();
3019
4761
  const k8sApi = createBunCompatibleKubernetesObjectApi(kubeConfig);
@@ -3027,6 +4769,7 @@ export class KroResourceFactoryImpl {
3027
4769
  kind: this.schemaDefinition.kind,
3028
4770
  rgdName: this.rgdName,
3029
4771
  factoryContext: this.name,
4772
+ ...(abortSignal ? { abortSignal } : {}),
3030
4773
  });
3031
4774
  }
3032
4775
  /**