typekro 0.7.0 → 0.9.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 (532) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/alchemy/deployers.d.ts +16 -1
  3. package/dist/alchemy/deployers.d.ts.map +1 -1
  4. package/dist/alchemy/deployers.js +131 -18
  5. package/dist/alchemy/deployers.js.map +1 -1
  6. package/dist/alchemy/deployment.d.ts +1 -1
  7. package/dist/alchemy/deployment.d.ts.map +1 -1
  8. package/dist/alchemy/deployment.js +1 -1
  9. package/dist/alchemy/deployment.js.map +1 -1
  10. package/dist/alchemy/kro-delete.d.ts +66 -0
  11. package/dist/alchemy/kro-delete.d.ts.map +1 -0
  12. package/dist/alchemy/kro-delete.js +183 -0
  13. package/dist/alchemy/kro-delete.js.map +1 -0
  14. package/dist/alchemy/resource-registration.d.ts +16 -0
  15. package/dist/alchemy/resource-registration.d.ts.map +1 -1
  16. package/dist/alchemy/resource-registration.js +138 -24
  17. package/dist/alchemy/resource-registration.js.map +1 -1
  18. package/dist/alchemy/types.d.ts +8 -4
  19. package/dist/alchemy/types.d.ts.map +1 -1
  20. package/dist/compositions/typekro-runtime/typekro-runtime.d.ts +1 -1
  21. package/dist/compositions/typekro-runtime/typekro-runtime.d.ts.map +1 -1
  22. package/dist/compositions/typekro-runtime/typekro-runtime.js +39 -7
  23. package/dist/compositions/typekro-runtime/typekro-runtime.js.map +1 -1
  24. package/dist/core/composition/context.d.ts +58 -2
  25. package/dist/core/composition/context.d.ts.map +1 -1
  26. package/dist/core/composition/context.js +4 -0
  27. package/dist/core/composition/context.js.map +1 -1
  28. package/dist/core/composition/imperative.d.ts +9 -0
  29. package/dist/core/composition/imperative.d.ts.map +1 -1
  30. package/dist/core/composition/imperative.js +538 -54
  31. package/dist/core/composition/imperative.js.map +1 -1
  32. package/dist/core/composition/nested-status-cel.d.ts +34 -1
  33. package/dist/core/composition/nested-status-cel.d.ts.map +1 -1
  34. package/dist/core/composition/nested-status-cel.js +379 -41
  35. package/dist/core/composition/nested-status-cel.js.map +1 -1
  36. package/dist/core/composition-debugger.d.ts +1 -1
  37. package/dist/core/composition-debugger.d.ts.map +1 -1
  38. package/dist/core/composition-debugger.js.map +1 -1
  39. package/dist/core/constants/brands.d.ts +1 -1
  40. package/dist/core/constants/brands.d.ts.map +1 -1
  41. package/dist/core/constants/brands.js +1 -1
  42. package/dist/core/constants/brands.js.map +1 -1
  43. package/dist/core/containers/build.d.ts +4 -4
  44. package/dist/core/containers/build.d.ts.map +1 -1
  45. package/dist/core/containers/build.js +44 -16
  46. package/dist/core/containers/build.js.map +1 -1
  47. package/dist/core/containers/registries/types.d.ts +1 -1
  48. package/dist/core/containers/registries/types.d.ts.map +1 -1
  49. package/dist/core/dependencies/resolver.d.ts +19 -0
  50. package/dist/core/dependencies/resolver.d.ts.map +1 -1
  51. package/dist/core/dependencies/resolver.js +261 -1
  52. package/dist/core/dependencies/resolver.js.map +1 -1
  53. package/dist/core/deployment/client-provider-manager.d.ts +9 -3
  54. package/dist/core/deployment/client-provider-manager.d.ts.map +1 -1
  55. package/dist/core/deployment/client-provider-manager.js +12 -0
  56. package/dist/core/deployment/client-provider-manager.js.map +1 -1
  57. package/dist/core/deployment/crd-manager.d.ts +24 -0
  58. package/dist/core/deployment/crd-manager.d.ts.map +1 -1
  59. package/dist/core/deployment/crd-manager.js +79 -1
  60. package/dist/core/deployment/crd-manager.js.map +1 -1
  61. package/dist/core/deployment/deployment-state-discovery.d.ts +116 -0
  62. package/dist/core/deployment/deployment-state-discovery.d.ts.map +1 -0
  63. package/dist/core/deployment/deployment-state-discovery.js +400 -0
  64. package/dist/core/deployment/deployment-state-discovery.js.map +1 -0
  65. package/dist/core/deployment/direct-factory.d.ts +65 -6
  66. package/dist/core/deployment/direct-factory.d.ts.map +1 -1
  67. package/dist/core/deployment/direct-factory.js +551 -56
  68. package/dist/core/deployment/direct-factory.js.map +1 -1
  69. package/dist/core/deployment/engine.d.ts +64 -3
  70. package/dist/core/deployment/engine.d.ts.map +1 -1
  71. package/dist/core/deployment/engine.js +194 -27
  72. package/dist/core/deployment/engine.js.map +1 -1
  73. package/dist/core/deployment/event-filter.js +1 -1
  74. package/dist/core/deployment/event-filter.js.map +1 -1
  75. package/dist/core/deployment/event-monitor.d.ts +11 -0
  76. package/dist/core/deployment/event-monitor.d.ts.map +1 -1
  77. package/dist/core/deployment/event-monitor.js +14 -0
  78. package/dist/core/deployment/event-monitor.js.map +1 -1
  79. package/dist/core/deployment/handle-tracing.d.ts +14 -0
  80. package/dist/core/deployment/handle-tracing.d.ts.map +1 -0
  81. package/dist/core/deployment/handle-tracing.js +38 -0
  82. package/dist/core/deployment/handle-tracing.js.map +1 -0
  83. package/dist/core/deployment/kro-factory.d.ts +136 -3
  84. package/dist/core/deployment/kro-factory.d.ts.map +1 -1
  85. package/dist/core/deployment/kro-factory.js +945 -268
  86. package/dist/core/deployment/kro-factory.js.map +1 -1
  87. package/dist/core/deployment/kro-readiness.d.ts.map +1 -1
  88. package/dist/core/deployment/kro-readiness.js +21 -12
  89. package/dist/core/deployment/kro-readiness.js.map +1 -1
  90. package/dist/core/deployment/nested-composition-status.d.ts +1 -1
  91. package/dist/core/deployment/nested-composition-status.d.ts.map +1 -1
  92. package/dist/core/deployment/nested-composition-status.js +96 -53
  93. package/dist/core/deployment/nested-composition-status.js.map +1 -1
  94. package/dist/core/deployment/resource-applier.d.ts +15 -2
  95. package/dist/core/deployment/resource-applier.d.ts.map +1 -1
  96. package/dist/core/deployment/resource-applier.js +75 -25
  97. package/dist/core/deployment/resource-applier.js.map +1 -1
  98. package/dist/core/deployment/resource-tagging.d.ts +220 -0
  99. package/dist/core/deployment/resource-tagging.d.ts.map +1 -0
  100. package/dist/core/deployment/resource-tagging.js +292 -0
  101. package/dist/core/deployment/resource-tagging.js.map +1 -0
  102. package/dist/core/deployment/rollback-manager.d.ts +25 -4
  103. package/dist/core/deployment/rollback-manager.d.ts.map +1 -1
  104. package/dist/core/deployment/rollback-manager.js +70 -57
  105. package/dist/core/deployment/rollback-manager.js.map +1 -1
  106. package/dist/core/deployment/shared-utilities.d.ts +6 -0
  107. package/dist/core/deployment/shared-utilities.d.ts.map +1 -1
  108. package/dist/core/deployment/shared-utilities.js +32 -2
  109. package/dist/core/deployment/shared-utilities.js.map +1 -1
  110. package/dist/core/deployment/singleton-owner-drift.d.ts +16 -0
  111. package/dist/core/deployment/singleton-owner-drift.d.ts.map +1 -0
  112. package/dist/core/deployment/singleton-owner-drift.js +54 -0
  113. package/dist/core/deployment/singleton-owner-drift.js.map +1 -0
  114. package/dist/core/deployment/strategies/alchemy-strategy.d.ts +3 -1
  115. package/dist/core/deployment/strategies/alchemy-strategy.d.ts.map +1 -1
  116. package/dist/core/deployment/strategies/alchemy-strategy.js +121 -18
  117. package/dist/core/deployment/strategies/alchemy-strategy.js.map +1 -1
  118. package/dist/core/deployment/strategies/base-strategy.d.ts +9 -3
  119. package/dist/core/deployment/strategies/base-strategy.d.ts.map +1 -1
  120. package/dist/core/deployment/strategies/base-strategy.js +32 -4
  121. package/dist/core/deployment/strategies/base-strategy.js.map +1 -1
  122. package/dist/core/deployment/strategies/direct-strategy.d.ts +12 -4
  123. package/dist/core/deployment/strategies/direct-strategy.d.ts.map +1 -1
  124. package/dist/core/deployment/strategies/direct-strategy.js +112 -8
  125. package/dist/core/deployment/strategies/direct-strategy.js.map +1 -1
  126. package/dist/core/errors.d.ts +2 -2
  127. package/dist/core/errors.d.ts.map +1 -1
  128. package/dist/core/errors.js.map +1 -1
  129. package/dist/core/expressions/analysis/cache.d.ts +2 -2
  130. package/dist/core/expressions/analysis/cache.d.ts.map +1 -1
  131. package/dist/core/expressions/analysis/cache.js +4 -0
  132. package/dist/core/expressions/analysis/cache.js.map +1 -1
  133. package/dist/core/expressions/analysis/fn-toString-self-test.d.ts.map +1 -1
  134. package/dist/core/expressions/analysis/fn-toString-self-test.js +0 -1
  135. package/dist/core/expressions/analysis/fn-toString-self-test.js.map +1 -1
  136. package/dist/core/expressions/analysis/shared-types.d.ts +2 -2
  137. package/dist/core/expressions/analysis/shared-types.d.ts.map +1 -1
  138. package/dist/core/expressions/analysis/source-map.d.ts.map +1 -1
  139. package/dist/core/expressions/analysis/source-map.js +1 -0
  140. package/dist/core/expressions/analysis/source-map.js.map +1 -1
  141. package/dist/core/expressions/analysis/types.d.ts +7 -7
  142. package/dist/core/expressions/analysis/types.d.ts.map +1 -1
  143. package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts +37 -3
  144. package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts.map +1 -1
  145. package/dist/core/expressions/composition/composition-analyzer-helpers.js +370 -7
  146. package/dist/core/expressions/composition/composition-analyzer-helpers.js.map +1 -1
  147. package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts +2 -2
  148. package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts.map +1 -1
  149. package/dist/core/expressions/composition/composition-analyzer-ternary.js +264 -29
  150. package/dist/core/expressions/composition/composition-analyzer-ternary.js.map +1 -1
  151. package/dist/core/expressions/composition/composition-analyzer-traversal.d.ts.map +1 -1
  152. package/dist/core/expressions/composition/composition-analyzer-traversal.js +78 -6
  153. package/dist/core/expressions/composition/composition-analyzer-traversal.js.map +1 -1
  154. package/dist/core/expressions/composition/composition-analyzer-types.d.ts +60 -0
  155. package/dist/core/expressions/composition/composition-analyzer-types.d.ts.map +1 -1
  156. package/dist/core/expressions/composition/composition-analyzer.d.ts +1 -1
  157. package/dist/core/expressions/composition/composition-analyzer.d.ts.map +1 -1
  158. package/dist/core/expressions/composition/composition-analyzer.js +158 -8
  159. package/dist/core/expressions/composition/composition-analyzer.js.map +1 -1
  160. package/dist/core/expressions/composition/expression-analyzer.d.ts.map +1 -1
  161. package/dist/core/expressions/composition/expression-analyzer.js +21 -4
  162. package/dist/core/expressions/composition/expression-analyzer.js.map +1 -1
  163. package/dist/core/expressions/composition/imperative-analyzer.d.ts +1 -1
  164. package/dist/core/expressions/composition/imperative-analyzer.d.ts.map +1 -1
  165. package/dist/core/expressions/composition/imperative-analyzer.js +31 -7
  166. package/dist/core/expressions/composition/imperative-analyzer.js.map +1 -1
  167. package/dist/core/expressions/composition/scope-manager.d.ts +2 -2
  168. package/dist/core/expressions/composition/scope-manager.d.ts.map +1 -1
  169. package/dist/core/expressions/composition/scope-manager.js.map +1 -1
  170. package/dist/core/expressions/conditional/conditional-expression-processor.d.ts +3 -3
  171. package/dist/core/expressions/conditional/conditional-expression-processor.d.ts.map +1 -1
  172. package/dist/core/expressions/conditional/conditional-expression-processor.js.map +1 -1
  173. package/dist/core/expressions/conditional/conditional-integration.d.ts.map +1 -1
  174. package/dist/core/expressions/conditional/conditional-integration.js.map +1 -1
  175. package/dist/core/expressions/context/context-aware-generator.d.ts +5 -5
  176. package/dist/core/expressions/context/context-aware-generator.d.ts.map +1 -1
  177. package/dist/core/expressions/context/context-aware-generator.js.map +1 -1
  178. package/dist/core/expressions/context/context-detector.d.ts +3 -3
  179. package/dist/core/expressions/context/context-detector.d.ts.map +1 -1
  180. package/dist/core/expressions/context/context-detector.js.map +1 -1
  181. package/dist/core/expressions/context/context-validator.d.ts +6 -6
  182. package/dist/core/expressions/context/context-validator.d.ts.map +1 -1
  183. package/dist/core/expressions/context/context-validator.js +2 -2
  184. package/dist/core/expressions/context/context-validator.js.map +1 -1
  185. package/dist/core/expressions/factory/cel-conversion-engine.d.ts +4 -4
  186. package/dist/core/expressions/factory/cel-conversion-engine.d.ts.map +1 -1
  187. package/dist/core/expressions/factory/cel-conversion-engine.js.map +1 -1
  188. package/dist/core/expressions/factory/dependency-tracker.d.ts +2 -2
  189. package/dist/core/expressions/factory/dependency-tracker.d.ts.map +1 -1
  190. package/dist/core/expressions/factory/dependency-tracker.js +21 -5
  191. package/dist/core/expressions/factory/dependency-tracker.js.map +1 -1
  192. package/dist/core/expressions/factory/factory-integration.d.ts +2 -4
  193. package/dist/core/expressions/factory/factory-integration.d.ts.map +1 -1
  194. package/dist/core/expressions/factory/factory-integration.js +0 -6
  195. package/dist/core/expressions/factory/factory-integration.js.map +1 -1
  196. package/dist/core/expressions/factory/factory-pattern-handler.d.ts +3 -3
  197. package/dist/core/expressions/factory/factory-pattern-handler.d.ts.map +1 -1
  198. package/dist/core/expressions/factory/factory-pattern-handler.js +1 -0
  199. package/dist/core/expressions/factory/factory-pattern-handler.js.map +1 -1
  200. package/dist/core/expressions/factory/migration-helpers.js.map +1 -1
  201. package/dist/core/expressions/factory/resource-analyzer.d.ts +4 -4
  202. package/dist/core/expressions/factory/resource-analyzer.d.ts.map +1 -1
  203. package/dist/core/expressions/factory/resource-analyzer.js.map +1 -1
  204. package/dist/core/expressions/factory/resource-type-validator.d.ts +5 -5
  205. package/dist/core/expressions/factory/resource-type-validator.d.ts.map +1 -1
  206. package/dist/core/expressions/factory/resource-type-validator.js.map +1 -1
  207. package/dist/core/expressions/factory/status-builder-analyzer.d.ts +6 -7
  208. package/dist/core/expressions/factory/status-builder-analyzer.d.ts.map +1 -1
  209. package/dist/core/expressions/factory/status-builder-analyzer.js +0 -3
  210. package/dist/core/expressions/factory/status-builder-analyzer.js.map +1 -1
  211. package/dist/core/expressions/factory/status-builder-types.d.ts +1 -1
  212. package/dist/core/expressions/factory/status-builder-types.d.ts.map +1 -1
  213. package/dist/core/expressions/factory/status-cel-generation.d.ts +1 -1
  214. package/dist/core/expressions/factory/status-cel-generation.d.ts.map +1 -1
  215. package/dist/core/expressions/factory/status-cel-generation.js +1 -1
  216. package/dist/core/expressions/factory/status-cel-generation.js.map +1 -1
  217. package/dist/core/expressions/factory/status-field-analysis.d.ts +3 -3
  218. package/dist/core/expressions/factory/status-field-analysis.d.ts.map +1 -1
  219. package/dist/core/expressions/factory/status-field-analysis.js.map +1 -1
  220. package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.d.ts +5 -5
  221. package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.d.ts.map +1 -1
  222. package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.js +1 -1
  223. package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.js.map +1 -1
  224. package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.d.ts +10 -10
  225. package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.d.ts.map +1 -1
  226. package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.js +5 -1
  227. package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.js.map +1 -1
  228. package/dist/core/expressions/magic-proxy/magic-proxy-ast.d.ts +2 -2
  229. package/dist/core/expressions/magic-proxy/magic-proxy-ast.d.ts.map +1 -1
  230. package/dist/core/expressions/magic-proxy/magic-proxy-ast.js.map +1 -1
  231. package/dist/core/expressions/magic-proxy/magic-proxy-detector.d.ts +5 -5
  232. package/dist/core/expressions/magic-proxy/magic-proxy-detector.d.ts.map +1 -1
  233. package/dist/core/expressions/magic-proxy/magic-proxy-detector.js.map +1 -1
  234. package/dist/core/expressions/magic-proxy/magic-proxy-types.d.ts +2 -2
  235. package/dist/core/expressions/magic-proxy/magic-proxy-types.d.ts.map +1 -1
  236. package/dist/core/expressions/magic-proxy/optionality-handler.d.ts +1 -2
  237. package/dist/core/expressions/magic-proxy/optionality-handler.d.ts.map +1 -1
  238. package/dist/core/expressions/magic-proxy/optionality-handler.js +2 -15
  239. package/dist/core/expressions/magic-proxy/optionality-handler.js.map +1 -1
  240. package/dist/core/expressions/validation/compile-time-checker.d.ts +1 -1
  241. package/dist/core/expressions/validation/compile-time-checker.d.ts.map +1 -1
  242. package/dist/core/expressions/validation/compile-time-checker.js.map +1 -1
  243. package/dist/core/expressions/validation/compile-time-types.d.ts +2 -2
  244. package/dist/core/expressions/validation/compile-time-types.d.ts.map +1 -1
  245. package/dist/core/expressions/validation/kubernetes-field-types.d.ts +4 -4
  246. package/dist/core/expressions/validation/kubernetes-field-types.d.ts.map +1 -1
  247. package/dist/core/expressions/validation/kubernetes-field-types.js.map +1 -1
  248. package/dist/core/expressions/validation/resource-field-utils.d.ts +10 -10
  249. package/dist/core/expressions/validation/resource-field-utils.d.ts.map +1 -1
  250. package/dist/core/expressions/validation/resource-field-utils.js.map +1 -1
  251. package/dist/core/expressions/validation/resource-validation.d.ts +3 -3
  252. package/dist/core/expressions/validation/resource-validation.d.ts.map +1 -1
  253. package/dist/core/expressions/validation/resource-validation.js.map +1 -1
  254. package/dist/core/expressions/validation/type-inference-types.d.ts +2 -2
  255. package/dist/core/expressions/validation/type-inference-types.d.ts.map +1 -1
  256. package/dist/core/expressions/validation/type-safety.d.ts +2 -2
  257. package/dist/core/expressions/validation/type-safety.d.ts.map +1 -1
  258. package/dist/core/expressions/validation/type-safety.js +1 -0
  259. package/dist/core/expressions/validation/type-safety.js.map +1 -1
  260. package/dist/core/kubernetes/bun-api-client.js.map +1 -1
  261. package/dist/core/kubernetes/bun-http-library.d.ts.map +1 -1
  262. package/dist/core/kubernetes/bun-http-library.js +29 -3
  263. package/dist/core/kubernetes/bun-http-library.js.map +1 -1
  264. package/dist/core/kubernetes/client-provider.d.ts +12 -0
  265. package/dist/core/kubernetes/client-provider.d.ts.map +1 -1
  266. package/dist/core/kubernetes/client-provider.js +35 -0
  267. package/dist/core/kubernetes/client-provider.js.map +1 -1
  268. package/dist/core/metadata/resource-metadata.d.ts +46 -1
  269. package/dist/core/metadata/resource-metadata.d.ts.map +1 -1
  270. package/dist/core/metadata/resource-metadata.js.map +1 -1
  271. package/dist/core/proxy/create-resource.d.ts +15 -0
  272. package/dist/core/proxy/create-resource.d.ts.map +1 -1
  273. package/dist/core/proxy/create-resource.js +56 -2
  274. package/dist/core/proxy/create-resource.js.map +1 -1
  275. package/dist/core/readiness/registry.js +2 -2
  276. package/dist/core/readiness/registry.js.map +1 -1
  277. package/dist/core/references/cel-evaluator.d.ts +1 -4
  278. package/dist/core/references/cel-evaluator.d.ts.map +1 -1
  279. package/dist/core/references/cel-evaluator.js +3 -7
  280. package/dist/core/references/cel-evaluator.js.map +1 -1
  281. package/dist/core/references/cel.d.ts +70 -0
  282. package/dist/core/references/cel.d.ts.map +1 -1
  283. package/dist/core/references/cel.js +188 -8
  284. package/dist/core/references/cel.js.map +1 -1
  285. package/dist/core/references/external-refs.d.ts.map +1 -1
  286. package/dist/core/references/external-refs.js +3 -0
  287. package/dist/core/references/external-refs.js.map +1 -1
  288. package/dist/core/references/resolver.d.ts.map +1 -1
  289. package/dist/core/references/resolver.js +28 -17
  290. package/dist/core/references/resolver.js.map +1 -1
  291. package/dist/core/references/schema-proxy.d.ts +18 -10
  292. package/dist/core/references/schema-proxy.d.ts.map +1 -1
  293. package/dist/core/references/schema-proxy.js +174 -23
  294. package/dist/core/references/schema-proxy.js.map +1 -1
  295. package/dist/core/runtime-patches/crd-schema-fix.d.ts.map +1 -1
  296. package/dist/core/runtime-patches/crd-schema-fix.js +4 -1
  297. package/dist/core/runtime-patches/crd-schema-fix.js.map +1 -1
  298. package/dist/core/serialization/cel-optimizer.d.ts.map +1 -1
  299. package/dist/core/serialization/cel-optimizer.js +2 -0
  300. package/dist/core/serialization/cel-optimizer.js.map +1 -1
  301. package/dist/core/serialization/cel-references.d.ts +75 -1
  302. package/dist/core/serialization/cel-references.d.ts.map +1 -1
  303. package/dist/core/serialization/cel-references.js +723 -145
  304. package/dist/core/serialization/cel-references.js.map +1 -1
  305. package/dist/core/serialization/core.d.ts +13 -8
  306. package/dist/core/serialization/core.d.ts.map +1 -1
  307. package/dist/core/serialization/core.js +973 -12
  308. package/dist/core/serialization/core.js.map +1 -1
  309. package/dist/core/serialization/kro-post-processing.d.ts +46 -0
  310. package/dist/core/serialization/kro-post-processing.d.ts.map +1 -0
  311. package/dist/core/serialization/kro-post-processing.js +150 -0
  312. package/dist/core/serialization/kro-post-processing.js.map +1 -0
  313. package/dist/core/serialization/schema.d.ts +62 -3
  314. package/dist/core/serialization/schema.d.ts.map +1 -1
  315. package/dist/core/serialization/schema.js +819 -6
  316. package/dist/core/serialization/schema.js.map +1 -1
  317. package/dist/core/serialization/status-analysis-pipeline.d.ts +1 -1
  318. package/dist/core/serialization/status-analysis-pipeline.d.ts.map +1 -1
  319. package/dist/core/serialization/status-analysis-pipeline.js.map +1 -1
  320. package/dist/core/serialization/yaml.d.ts +3 -2
  321. package/dist/core/serialization/yaml.d.ts.map +1 -1
  322. package/dist/core/serialization/yaml.js +404 -56
  323. package/dist/core/serialization/yaml.js.map +1 -1
  324. package/dist/core/singleton/singleton.d.ts +16 -0
  325. package/dist/core/singleton/singleton.d.ts.map +1 -0
  326. package/dist/core/singleton/singleton.js +135 -0
  327. package/dist/core/singleton/singleton.js.map +1 -0
  328. package/dist/core/types/common.d.ts +2 -2
  329. package/dist/core/types/common.d.ts.map +1 -1
  330. package/dist/core/types/composable.d.ts +1 -1
  331. package/dist/core/types/composable.d.ts.map +1 -1
  332. package/dist/core/types/deployment.d.ts +126 -6
  333. package/dist/core/types/deployment.d.ts.map +1 -1
  334. package/dist/core/types/deployment.js +1 -1
  335. package/dist/core/types/deployment.js.map +1 -1
  336. package/dist/core/types/kubernetes.d.ts +25 -17
  337. package/dist/core/types/kubernetes.d.ts.map +1 -1
  338. package/dist/core/types/references.d.ts +1 -1
  339. package/dist/core/types/references.d.ts.map +1 -1
  340. package/dist/core/types/references.js.map +1 -1
  341. package/dist/core/types/resource-graph.d.ts +1 -1
  342. package/dist/core/types/resource-graph.d.ts.map +1 -1
  343. package/dist/core/types/schema.d.ts +1 -1
  344. package/dist/core/types/schema.d.ts.map +1 -1
  345. package/dist/core/types/serialization.d.ts +62 -6
  346. package/dist/core/types/serialization.d.ts.map +1 -1
  347. package/dist/core/validation/cel-validator.d.ts +15 -2
  348. package/dist/core/validation/cel-validator.d.ts.map +1 -1
  349. package/dist/core/validation/cel-validator.js +144 -63
  350. package/dist/core/validation/cel-validator.js.map +1 -1
  351. package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts +2 -41
  352. package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts.map +1 -1
  353. package/dist/factories/apisix/compositions/apisix-bootstrap.js +262 -217
  354. package/dist/factories/apisix/compositions/apisix-bootstrap.js.map +1 -1
  355. package/dist/factories/apisix/index.d.ts +2 -2
  356. package/dist/factories/apisix/index.js +2 -2
  357. package/dist/factories/apisix/resources/helm.d.ts +2 -2
  358. package/dist/factories/apisix/resources/helm.d.ts.map +1 -1
  359. package/dist/factories/apisix/resources/helm.js.map +1 -1
  360. package/dist/factories/apisix/types.d.ts +21 -11
  361. package/dist/factories/apisix/types.d.ts.map +1 -1
  362. package/dist/factories/apisix/types.js +106 -4
  363. package/dist/factories/apisix/types.js.map +1 -1
  364. package/dist/factories/apisix/utils/admin-credentials.d.ts +5 -3
  365. package/dist/factories/apisix/utils/admin-credentials.d.ts.map +1 -1
  366. package/dist/factories/apisix/utils/admin-credentials.js +14 -10
  367. package/dist/factories/apisix/utils/admin-credentials.js.map +1 -1
  368. package/dist/factories/apisix/utils/helm-values-mapper.d.ts.map +1 -1
  369. package/dist/factories/apisix/utils/helm-values-mapper.js +4 -2
  370. package/dist/factories/apisix/utils/helm-values-mapper.js.map +1 -1
  371. package/dist/factories/cert-manager/resources/challenges.js.map +1 -1
  372. package/dist/factories/cert-manager/types.d.ts +3 -3
  373. package/dist/factories/cert-manager/types.d.ts.map +1 -1
  374. package/dist/factories/cilium/compositions/cilium-bootstrap.d.ts +4 -4
  375. package/dist/factories/cilium/types.d.ts +3 -3
  376. package/dist/factories/cilium/types.d.ts.map +1 -1
  377. package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts +1 -0
  378. package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts.map +1 -1
  379. package/dist/factories/cnpg/compositions/cnpg-bootstrap.js +48 -0
  380. package/dist/factories/cnpg/compositions/cnpg-bootstrap.js.map +1 -1
  381. package/dist/factories/cnpg/resources/cluster.js +1 -1
  382. package/dist/factories/cnpg/resources/cluster.js.map +1 -1
  383. package/dist/factories/cnpg/resources/helm.d.ts.map +1 -1
  384. package/dist/factories/cnpg/resources/helm.js +1 -0
  385. package/dist/factories/cnpg/resources/helm.js.map +1 -1
  386. package/dist/factories/cnpg/resources/pooler.js +1 -1
  387. package/dist/factories/cnpg/resources/pooler.js.map +1 -1
  388. package/dist/factories/cnpg/types.d.ts +9 -8
  389. package/dist/factories/cnpg/types.d.ts.map +1 -1
  390. package/dist/factories/cnpg/types.js +11 -0
  391. package/dist/factories/cnpg/types.js.map +1 -1
  392. package/dist/factories/external-dns/compositions/external-dns-bootstrap.d.ts.map +1 -1
  393. package/dist/factories/external-dns/compositions/external-dns-bootstrap.js +153 -41
  394. package/dist/factories/external-dns/compositions/external-dns-bootstrap.js.map +1 -1
  395. package/dist/factories/external-dns/resources/dns-endpoint.js +1 -1
  396. package/dist/factories/external-dns/resources/dns-endpoint.js.map +1 -1
  397. package/dist/factories/external-dns/resources/helm.d.ts +1 -1
  398. package/dist/factories/external-dns/resources/helm.d.ts.map +1 -1
  399. package/dist/factories/external-dns/resources/helm.js +17 -10
  400. package/dist/factories/external-dns/resources/helm.js.map +1 -1
  401. package/dist/factories/external-dns/types.d.ts +5 -2
  402. package/dist/factories/external-dns/types.d.ts.map +1 -1
  403. package/dist/factories/external-dns/types.js.map +1 -1
  404. package/dist/factories/flux/git-repository.d.ts.map +1 -1
  405. package/dist/factories/flux/git-repository.js +1 -1
  406. package/dist/factories/flux/git-repository.js.map +1 -1
  407. package/dist/factories/flux/kustomize/kustomization.d.ts +2 -2
  408. package/dist/factories/flux/kustomize/kustomization.d.ts.map +1 -1
  409. package/dist/factories/flux/kustomize/readiness-evaluators.d.ts +1 -1
  410. package/dist/factories/flux/kustomize/readiness-evaluators.d.ts.map +1 -1
  411. package/dist/factories/flux/kustomize/readiness-evaluators.js +1 -1
  412. package/dist/factories/flux/kustomize/readiness-evaluators.js.map +1 -1
  413. package/dist/factories/helm/helm-release.d.ts +3 -2
  414. package/dist/factories/helm/helm-release.d.ts.map +1 -1
  415. package/dist/factories/helm/helm-release.js +1 -0
  416. package/dist/factories/helm/helm-release.js.map +1 -1
  417. package/dist/factories/helm/helm-repository.d.ts +1 -1
  418. package/dist/factories/helm/helm-repository.d.ts.map +1 -1
  419. package/dist/factories/helm/helm-repository.js +6 -4
  420. package/dist/factories/helm/helm-repository.js.map +1 -1
  421. package/dist/factories/helm/readiness-evaluators.d.ts +6 -6
  422. package/dist/factories/helm/readiness-evaluators.d.ts.map +1 -1
  423. package/dist/factories/helm/readiness-evaluators.js +15 -9
  424. package/dist/factories/helm/readiness-evaluators.js.map +1 -1
  425. package/dist/factories/helm/types.d.ts +5 -1
  426. package/dist/factories/helm/types.d.ts.map +1 -1
  427. package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts +1 -0
  428. package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts.map +1 -1
  429. package/dist/factories/inngest/compositions/inngest-bootstrap.js +4 -3
  430. package/dist/factories/inngest/compositions/inngest-bootstrap.js.map +1 -1
  431. package/dist/factories/inngest/resources/helm.js +1 -1
  432. package/dist/factories/inngest/resources/helm.js.map +1 -1
  433. package/dist/factories/inngest/types.d.ts +5 -4
  434. package/dist/factories/inngest/types.d.ts.map +1 -1
  435. package/dist/factories/inngest/types.js +2 -0
  436. package/dist/factories/inngest/types.js.map +1 -1
  437. package/dist/factories/kro/kro-custom-resource.js +1 -1
  438. package/dist/factories/kro/kro-custom-resource.js.map +1 -1
  439. package/dist/factories/kubernetes/config/config-map.d.ts +2 -2
  440. package/dist/factories/kubernetes/config/config-map.d.ts.map +1 -1
  441. package/dist/factories/kubernetes/config/secret.d.ts +2 -2
  442. package/dist/factories/kubernetes/config/secret.d.ts.map +1 -1
  443. package/dist/factories/kubernetes/config/secret.js +11 -1
  444. package/dist/factories/kubernetes/config/secret.js.map +1 -1
  445. package/dist/factories/kubernetes/networking/service.js +1 -1
  446. package/dist/factories/kubernetes/networking/service.js.map +1 -1
  447. package/dist/factories/kubernetes/yaml/yaml-directory.d.ts.map +1 -1
  448. package/dist/factories/kubernetes/yaml/yaml-directory.js +9 -0
  449. package/dist/factories/kubernetes/yaml/yaml-directory.js.map +1 -1
  450. package/dist/factories/kubernetes/yaml/yaml-file.d.ts.map +1 -1
  451. package/dist/factories/kubernetes/yaml/yaml-file.js +9 -0
  452. package/dist/factories/kubernetes/yaml/yaml-file.js.map +1 -1
  453. package/dist/factories/pebble/resources/helm.js.map +1 -1
  454. package/dist/factories/pebble/types.d.ts +2 -2
  455. package/dist/factories/searxng/compositions/index.d.ts +2 -0
  456. package/dist/factories/searxng/compositions/index.d.ts.map +1 -0
  457. package/dist/factories/searxng/compositions/index.js +2 -0
  458. package/dist/factories/searxng/compositions/index.js.map +1 -0
  459. package/dist/factories/searxng/compositions/searxng-bootstrap.d.ts +66 -0
  460. package/dist/factories/searxng/compositions/searxng-bootstrap.d.ts.map +1 -0
  461. package/dist/factories/searxng/compositions/searxng-bootstrap.js +275 -0
  462. package/dist/factories/searxng/compositions/searxng-bootstrap.js.map +1 -0
  463. package/dist/factories/searxng/index.d.ts +29 -0
  464. package/dist/factories/searxng/index.d.ts.map +1 -0
  465. package/dist/factories/searxng/index.js +27 -0
  466. package/dist/factories/searxng/index.js.map +1 -0
  467. package/dist/factories/searxng/resources/index.d.ts +2 -0
  468. package/dist/factories/searxng/resources/index.d.ts.map +1 -0
  469. package/dist/factories/searxng/resources/index.js +2 -0
  470. package/dist/factories/searxng/resources/index.js.map +1 -0
  471. package/dist/factories/searxng/resources/searxng.d.ts +65 -0
  472. package/dist/factories/searxng/resources/searxng.d.ts.map +1 -0
  473. package/dist/factories/searxng/resources/searxng.js +188 -0
  474. package/dist/factories/searxng/resources/searxng.js.map +1 -0
  475. package/dist/factories/searxng/types.d.ts +127 -0
  476. package/dist/factories/searxng/types.d.ts.map +1 -0
  477. package/dist/factories/searxng/types.js +177 -0
  478. package/dist/factories/searxng/types.js.map +1 -0
  479. package/dist/factories/searxng/utils/settings-builder.d.ts +47 -0
  480. package/dist/factories/searxng/utils/settings-builder.d.ts.map +1 -0
  481. package/dist/factories/searxng/utils/settings-builder.js +53 -0
  482. package/dist/factories/searxng/utils/settings-builder.js.map +1 -0
  483. package/dist/factories/simple/config/config-map.d.ts +2 -2
  484. package/dist/factories/simple/config/config-map.d.ts.map +1 -1
  485. package/dist/factories/simple/config/secret.d.ts +2 -2
  486. package/dist/factories/simple/config/secret.d.ts.map +1 -1
  487. package/dist/factories/simple/config/secret.js +28 -0
  488. package/dist/factories/simple/config/secret.js.map +1 -1
  489. package/dist/factories/simple/helm/index.d.ts +1 -1
  490. package/dist/factories/simple/helm/index.d.ts.map +1 -1
  491. package/dist/factories/simple/helm/index.js.map +1 -1
  492. package/dist/factories/simple/storage/persistent-volume.js.map +1 -1
  493. package/dist/factories/simple/types.d.ts +11 -1
  494. package/dist/factories/simple/types.d.ts.map +1 -1
  495. package/dist/factories/simple/workloads/deployment.d.ts.map +1 -1
  496. package/dist/factories/simple/workloads/deployment.js +3 -0
  497. package/dist/factories/simple/workloads/deployment.js.map +1 -1
  498. package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts +1 -0
  499. package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts.map +1 -1
  500. package/dist/factories/valkey/compositions/valkey-bootstrap.js +116 -0
  501. package/dist/factories/valkey/compositions/valkey-bootstrap.js.map +1 -1
  502. package/dist/factories/valkey/resources/valkey.js +1 -1
  503. package/dist/factories/valkey/resources/valkey.js.map +1 -1
  504. package/dist/factories/valkey/types.d.ts +6 -5
  505. package/dist/factories/valkey/types.d.ts.map +1 -1
  506. package/dist/factories/valkey/types.js +10 -0
  507. package/dist/factories/valkey/types.js.map +1 -1
  508. package/dist/factories/webapp/compositions/web-app-with-processing.d.ts +95 -12
  509. package/dist/factories/webapp/compositions/web-app-with-processing.d.ts.map +1 -1
  510. package/dist/factories/webapp/compositions/web-app-with-processing.js +185 -26
  511. package/dist/factories/webapp/compositions/web-app-with-processing.js.map +1 -1
  512. package/dist/factories/webapp/index.d.ts +3 -4
  513. package/dist/factories/webapp/index.d.ts.map +1 -1
  514. package/dist/factories/webapp/index.js +3 -4
  515. package/dist/factories/webapp/index.js.map +1 -1
  516. package/dist/factories/webapp/types.d.ts +60 -2
  517. package/dist/factories/webapp/types.d.ts.map +1 -1
  518. package/dist/factories/webapp/types.js +80 -3
  519. package/dist/factories/webapp/types.js.map +1 -1
  520. package/dist/index.d.ts +2 -0
  521. package/dist/index.d.ts.map +1 -1
  522. package/dist/index.js +1 -0
  523. package/dist/index.js.map +1 -1
  524. package/dist/shared/brands.d.ts +18 -8
  525. package/dist/shared/brands.d.ts.map +1 -1
  526. package/dist/shared/brands.js +19 -9
  527. package/dist/shared/brands.js.map +1 -1
  528. package/dist/utils/cel-escape.d.ts +12 -0
  529. package/dist/utils/cel-escape.d.ts.map +1 -0
  530. package/dist/utils/cel-escape.js +19 -0
  531. package/dist/utils/cel-escape.js.map +1 -0
  532. package/package.json +7 -2
@@ -4,17 +4,22 @@
4
4
  * This module provides the main serialization functions to convert
5
5
  * TypeScript resource definitions to Kro ResourceGraphDefinition YAML manifests.
6
6
  */
7
+ import { createCompositionContext, getCurrentCompositionContext, runInStatusBuilderContext, runWithCompositionContext, } from '../composition/context.js';
7
8
  import { createDirectResourceFactory } from '../deployment/direct-factory.js';
8
9
  import { createKroResourceFactory } from '../deployment/kro-factory.js';
9
10
  import { ensureError, ValidationError } from '../errors.js';
11
+ import { CEL_EXPRESSION_BRAND, KUBERNETES_REF_MARKER_SOURCE } from '../../shared/brands.js';
10
12
  import { analyzeCompositionBody, applyAnalysisToResources, } from '../expressions/composition/composition-analyzer.js';
13
+ import { remapResourceStatusReferences } from '../expressions/composition/composition-analyzer-helpers.js';
11
14
  import { StatusBuilderAnalyzer } from '../expressions/factory/status-builder-analyzer.js';
12
15
  import { getComponentLogger } from '../logging/index.js';
13
- import { setResourceId } from '../metadata/index.js';
16
+ import { getMetadataField, setResourceId } from '../metadata/index.js';
14
17
  import { createExternalRefWithoutRegistration, createSchemaProxy } from '../references/index.js';
15
18
  import { getKindInfo, getSemanticCandidateKinds } from '../resources/factory-registry.js';
16
19
  import { validateResourceGraphDefinition } from '../validation/cel-validator.js';
17
20
  import { optimizeStatusMappings } from './cel-optimizer.js';
21
+ import { finalizeCelForKro } from './cel-references.js';
22
+ import { applyTernaryConditionalsToResources } from './kro-post-processing.js';
18
23
  import { generateKroSchemaFromArktype } from './schema.js';
19
24
  import { runStatusAnalysisPipeline } from './status-analysis-pipeline.js';
20
25
  import { serializeResourceGraphToYaml } from './yaml.js';
@@ -236,16 +241,170 @@ function analyzeAndConvertStatusMappings(definition, statusBuilder, schema, reso
236
241
  *
237
242
  * @internal Exported for testing only
238
243
  */
239
- function processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analyzedStatusMappings, serializationLogger) {
244
+ function processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analyzedStatusMappings, serializationLogger, schemaDefinition) {
240
245
  let compositionAnalysis = null;
241
- const analysisState = { appliedToResources: false };
246
+ const analysisState = { appliedToResources: false, ternaryAndOmitApplied: false };
242
247
  const originalCompositionFnForAnalysis = statusMappings
243
248
  ?.__originalCompositionFn;
244
249
  if (originalCompositionFnForAnalysis) {
245
250
  try {
246
251
  const resourceIds = new Set(Object.keys(resourcesWithKeys));
247
- compositionAnalysis = analyzeCompositionBody(originalCompositionFnForAnalysis, resourceIds);
248
- // Create stub resources for factory calls that weren't registered at runtime
252
+ const specJson = schemaDefinition?.spec?.json;
253
+ const optionalFieldNames = specJson ? collectOptionalSpecPaths(specJson) : undefined;
254
+ const nestedStatusDescriptor = Object.getOwnPropertyDescriptor(statusMappings, '__nestedStatusCel');
255
+ const nestedStatusCel = nestedStatusDescriptor?.value;
256
+ compositionAnalysis = analyzeCompositionBody(originalCompositionFnForAnalysis, resourceIds, optionalFieldNames);
257
+ // Differential execution to capture untaken-branch resources.
258
+ //
259
+ // When a composition uses plain JS control flow (`if (!spec.optional) { ... }`),
260
+ // the truthy proxy pass takes the OPPOSITE branch because `!proxy === false`,
261
+ // which means the resource inside the branch is never registered at runtime.
262
+ // We re-execute the composition with optional fields set to `undefined`
263
+ // (where `!undefined === true` triggers those branches) in an ISOLATED
264
+ // composition context and merge any resources captured there into the main
265
+ // resource map. The AST analyzer has already recorded the correct
266
+ // `includeWhen` for each such resource, and `applyAnalysisToResources`
267
+ // (called later during toYaml()) attaches that metadata — so the final
268
+ // RGD emits the resource with a CEL `has()` / `!has()` conditional
269
+ // derived from the composition's native `if`/`else` statements.
270
+ //
271
+ // Prerequisites: we need the schema definition to know which fields
272
+ // are optional (so we set them to undefined) and which are required
273
+ // (so we set them to a sentinel that prevents the composition from
274
+ // dereferencing undefined). Both runs use the same composition
275
+ // function, so resource IDs and factory calls are deterministic.
276
+ //
277
+ // SKIP when this composition is being executed as a nested call
278
+ // (`context.isNestedCall === true`). The inner composition's own
279
+ // definition-time pass already captured its hybrid-branch analysis
280
+ // with the INNER schema proxy. Re-running that hybrid capture here —
281
+ // against the fresh inner schema proxy that `captureHybridRunResources`
282
+ // creates — would emit differential CEL conditionals that reference
283
+ // inner-schema fields (e.g., `has(schema.spec.secretKeyRef)`) which
284
+ // don't exist in the outer RGD. The outer composition is the
285
+ // authority on branch conditions for its own calls; the inner's
286
+ // branch shape is driven by what the outer passed in.
287
+ // ── Resource-status ternary compilation (Phases 3+4) ────────────
288
+ //
289
+ // When the AST detects ternaries conditioned on resource status
290
+ // fields (e.g., `cache.status.ready ? 'redis' : 'memory'`), proxy
291
+ // JS evaluation does not necessarily match CEL truth evaluation.
292
+ // To emit the CEL conditional, re-execute explicit true and false
293
+ // branches using `liveStatusMap`, then diff those branch outputs.
294
+ //
295
+ // To avoid false positives (other fields changing due to the status
296
+ // flip), direct factory calls diff only `callSiteResourceId`; nested
297
+ // composition call arguments diff only resources registered under known
298
+ // nested composition base IDs.
299
+ const resourceStatusTernaries = compositionAnalysis.resourceStatusTernaries;
300
+ // Deduplicate by call site and condition. Conditionalization is scoped to
301
+ // one callSiteResourceId, so two resources using the same status condition
302
+ // must both be processed.
303
+ const seenConditions = new Set();
304
+ const uniqueTernaries = resourceStatusTernaries.filter((t) => {
305
+ const key = `${t.callSiteResourceId}:${t.variableName}:${t.conditionExpression ?? t.statusField}`;
306
+ if (seenConditions.has(key))
307
+ return false;
308
+ seenConditions.add(key);
309
+ return true;
310
+ });
311
+ // Process EACH resource-status ternary independently to avoid
312
+ // cross-contamination: flip ONE condition → run → diff → apply.
313
+ // Multiple ternaries on the same resource get independent conditionals.
314
+ for (const ternary of uniqueTernaries) {
315
+ const resId = compositionAnalysis.variableToResourceId.get(ternary.variableName) ??
316
+ ternary.variableName;
317
+ if (!resourceIds.has(resId))
318
+ continue;
319
+ const conditionCel = ternary.conditionExpression
320
+ ? remapResourceStatusReferences(ternary.conditionExpression, new Map(compositionAnalysis.variableToResourceId).set(ternary.variableName, resId))
321
+ : `${resId}.status.${ternary.statusField}`;
322
+ const trueCtx = runResourceStatusBranch(originalCompositionFnForAnalysis, schemaDefinition, compositionAnalysis, ternary, true);
323
+ const falseCtx = runResourceStatusBranch(originalCompositionFnForAnalysis, schemaDefinition, compositionAnalysis, ternary, false);
324
+ // Diff ONLY the targeted resource(s)
325
+ const targetIds = ternary.callSiteResourceId && ternary.callSiteResourceId !== '__non_factory_call__'
326
+ ? [ternary.callSiteResourceId]
327
+ : getNestedResourceStatusTargetIds(trueCtx.resources, trueCtx.nestedCompositionIds);
328
+ for (const id of targetIds) {
329
+ const targetRes = resourcesWithKeys[id];
330
+ const trueRes = trueCtx.resources[id];
331
+ const falseRes = falseCtx.resources[id];
332
+ if (targetRes && trueRes && falseRes) {
333
+ applyResourceStatusBranchDiff(targetRes, trueRes, falseRes, conditionCel, nestedStatusCel, resourceIds);
334
+ }
335
+ }
336
+ serializationLogger.debug('Resource-status branch runs applied', {
337
+ conditionCel,
338
+ targetIds,
339
+ });
340
+ }
341
+ const currentCtx = getCurrentCompositionContext();
342
+ const skipHybridCapture = currentCtx?.isNestedCall === true;
343
+ if (!skipHybridCapture &&
344
+ schemaDefinition &&
345
+ (compositionAnalysis.unregisteredFactories.length > 0 ||
346
+ collectOverridableOptionalFields(schemaDefinition, compositionAnalysis).size > 0)) {
347
+ const { captured, overriddenFields, overrideConditions } = captureHybridRunResources(originalCompositionFnForAnalysis, schemaDefinition, compositionAnalysis);
348
+ // (a) Merge resources that exist ONLY in the hybrid run — these
349
+ // come from branches the proxy run didn't take (e.g., `if (!spec.x)`).
350
+ // The AST analyzer has already attached the appropriate includeWhen.
351
+ for (const [id, resource] of Object.entries(captured)) {
352
+ if (!resourceIds.has(id)) {
353
+ resourcesWithKeys[id] = resource;
354
+ resourceIds.add(id);
355
+ serializationLogger.debug('Captured resource from untaken branch', {
356
+ resourceId: id,
357
+ });
358
+ }
359
+ }
360
+ // (b) For resources that exist in BOTH runs, walk them in parallel
361
+ // and detect field-level differences. Each differing leaf becomes
362
+ // a CEL `has(...) ? <proxy value> : <hybrid value>` conditional
363
+ // applied in place on the proxy-run resource. This covers the
364
+ // "ternary inside a custom factory's internal transformation" case
365
+ // where the AST analyzer can't see the final template path, since
366
+ // the comparison works on the emitted resource structure directly.
367
+ //
368
+ // MUTATION SAFETY: this step replaces LEAF values (strings, numbers,
369
+ // KubernetesRef proxies) inside the proxy-run resources with CEL
370
+ // conditional strings. It runs after status analysis has captured
371
+ // its own references (so status builders see the pre-mutation state)
372
+ // and before YAML serialization (so the mutated state is what gets
373
+ // emitted). The tree structure is preserved; only leaves change.
374
+ // `processCompositionBodyAnalysis` is called exactly once per
375
+ // `toResourceGraph` invocation, and the walk itself is idempotent
376
+ // (a second pass finds `leafEquals` true for every leaf and no-ops),
377
+ // so multiple `toYaml()` calls on the resulting TypedResourceGraph
378
+ // all see a consistent final state.
379
+ if (overriddenFields.size > 0) {
380
+ const baselineResources = Object.fromEntries(Object.entries(resourcesWithKeys).map(([id, resource]) => [
381
+ id,
382
+ cloneResourceTree(resource),
383
+ ]));
384
+ const differentialFields = collectDifferentialOptionalFields(compositionAnalysis);
385
+ const fieldsToDiff = Array.from(differentialFields).filter((field) => overriddenFields.has(field));
386
+ for (const field of fieldsToDiff) {
387
+ const singleFieldSet = new Set([field]);
388
+ const { captured: fieldCaptured } = captureHybridRunResources(originalCompositionFnForAnalysis, schemaDefinition, compositionAnalysis, singleFieldSet);
389
+ const fieldConditions = new Map();
390
+ const explicitCondition = overrideConditions.get(field);
391
+ if (explicitCondition) {
392
+ fieldConditions.set(field, explicitCondition);
393
+ }
394
+ for (const id of Object.keys(resourcesWithKeys)) {
395
+ const proxyRes = resourcesWithKeys[id];
396
+ const baselineRes = baselineResources[id];
397
+ const hybridRes = fieldCaptured[id];
398
+ if (proxyRes && baselineRes && hybridRes) {
399
+ applyDifferentialFieldConditionals(proxyRes, baselineRes, hybridRes, singleFieldSet, fieldConditions, nestedStatusCel, resourceIds);
400
+ }
401
+ }
402
+ }
403
+ }
404
+ }
405
+ // Create stub resources for factory calls that STILL weren't registered
406
+ // (e.g., branches the differential run also didn't take because they
407
+ // required a different condition to be truthy).
249
408
  for (const unregistered of compositionAnalysis.unregisteredFactories) {
250
409
  if (!resourceIds.has(unregistered.resourceId)) {
251
410
  const stub = createStubResource(unregistered.factoryName, unregistered.resourceId);
@@ -276,6 +435,752 @@ function processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analy
276
435
  }
277
436
  return { compositionAnalysis, analysisState };
278
437
  }
438
+ /**
439
+ * Collect the top-level optional spec fields that should be overridden
440
+ * when running the composition for differential branch/field capture.
441
+ *
442
+ * We only override fields that the AST analyzer observed being TESTED
443
+ * in a condition — `if (spec.x)`, `spec.x ? a : b`, or similar.
444
+ * Overriding fields that the composition only READS unconditionally
445
+ * (e.g., `spec.server.secret_key` inside a ConfigMap's stringData) is
446
+ * counterproductive: it would replace those proxy references with
447
+ * `undefined` in the hybrid run, producing incorrect captured
448
+ * resources.
449
+ *
450
+ * The tested fields are extracted from the AST analyzer's recorded
451
+ * `includeWhen` expressions (which, after the `conditionToCel` fix,
452
+ * wrap bare truthiness checks with `has(...)`) by scanning for
453
+ * `schema.spec.<field>` paths.
454
+ */
455
+ function collectOverridableOptionalFields(schemaDefinition, analysis) {
456
+ const specJson = schemaDefinition.spec.json;
457
+ if (!specJson)
458
+ return new Set();
459
+ const differentialFields = collectDifferentialOptionalFields(analysis);
460
+ return new Set(Array.from(collectOptionalSpecPaths(specJson))
461
+ .filter((field) => differentialFields.has(field)));
462
+ }
463
+ function collectOptionalSpecPaths(schemaJson, prefix = '') {
464
+ const paths = new Set();
465
+ if (!schemaJson || typeof schemaJson !== 'object')
466
+ return paths;
467
+ const node = schemaJson;
468
+ for (const entry of node.optional ?? []) {
469
+ if (!entry.key)
470
+ continue;
471
+ const path = prefix ? `${prefix}.${entry.key}` : entry.key;
472
+ paths.add(path);
473
+ for (const childPath of collectOptionalSpecPaths(entry.value, path)) {
474
+ paths.add(childPath);
475
+ }
476
+ }
477
+ for (const entry of node.required ?? []) {
478
+ if (!entry.key)
479
+ continue;
480
+ const path = prefix ? `${prefix}.${entry.key}` : entry.key;
481
+ for (const childPath of collectOptionalSpecPaths(entry.value, path)) {
482
+ paths.add(childPath);
483
+ }
484
+ }
485
+ return paths;
486
+ }
487
+ function collectDifferentialOptionalFields(analysis) {
488
+ const fields = new Set(analysis.hybridOverrideConditions.keys());
489
+ for (const field of analysis.differentialConditionFields) {
490
+ fields.add(field);
491
+ }
492
+ for (const controlFlow of analysis.resources.values()) {
493
+ for (const condition of controlFlow.includeWhen) {
494
+ const expression = condition.expression;
495
+ const matches = expression.matchAll(/schema\.spec\.([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)/g);
496
+ for (const match of matches) {
497
+ const field = match[1];
498
+ if (field) {
499
+ fields.add(field);
500
+ }
501
+ }
502
+ }
503
+ }
504
+ return fields;
505
+ }
506
+ function collectHybridOverrideValues(analysis) {
507
+ const overrideValues = new Map();
508
+ for (const [field, expression] of analysis.hybridOverrideConditions.entries()) {
509
+ const match = expression.match(/^schema\.spec\.([a-zA-Z0-9_.]+)\s*!=\s*false$/);
510
+ const path = match?.[1];
511
+ if (!path || path !== field || overrideValues.has(field))
512
+ continue;
513
+ overrideValues.set(field, false);
514
+ }
515
+ return overrideValues;
516
+ }
517
+ function collectHybridOverrideConditions(analysis) {
518
+ return new Map(analysis.hybridOverrideConditions);
519
+ }
520
+ /**
521
+ * Re-execute the composition function in an isolated composition context
522
+ * with a HYBRID schema spec — the original schema proxy for most fields
523
+ * (so `spec.name`, `spec.server.secret_key`, etc. still produce
524
+ * `KubernetesRef` proxy values that serialize into CEL references) but
525
+ * with specific top-level optional fields overridden to `undefined` so
526
+ * that `if (!spec.x)`, `if (spec.x === undefined)`, and similar
527
+ * field-presence tests take the opposite branch from the proxy run.
528
+ *
529
+ * Used by {@link processCompositionBodyAnalysis} to capture BOTH:
530
+ * (a) resources registered inside branches that the main proxy run
531
+ * skipped because `!proxy === false`
532
+ * (b) resources that exist in both runs but with field-level
533
+ * differences caused by ternary/fallback logic that depends on
534
+ * the overridden fields — used for differential CEL conditional
535
+ * emission by {@link applyDifferentialFieldConditionals}
536
+ *
537
+ * The branches run with real proxy values for every field EXCEPT the
538
+ * overridden ones, so the captured resources contain the correct CEL
539
+ * references just like the proxy-run resources.
540
+ *
541
+ * Composition functions with external side effects will fire those
542
+ * effects a second time during this re-execution — see integration-skill
543
+ * rule #30 for the full contract.
544
+ */
545
+ function captureHybridRunResources(compositionFn, schemaDefinition, analysis, fieldsToOverride) {
546
+ try {
547
+ const allOverriddenFields = collectOverridableOptionalFields(schemaDefinition, analysis);
548
+ const overriddenFields = fieldsToOverride
549
+ ? new Set(Array.from(allOverriddenFields).filter((field) => fieldsToOverride.has(field)))
550
+ : allOverriddenFields;
551
+ if (overriddenFields.size === 0) {
552
+ return { captured: {}, overriddenFields, overrideConditions: new Map() };
553
+ }
554
+ const overrideValues = new Map(Array.from(collectHybridOverrideValues(analysis)).filter(([field]) => overriddenFields.has(field)));
555
+ const overrideConditions = new Map(Array.from(collectHybridOverrideConditions(analysis)).filter(([field]) => overriddenFields.has(field)));
556
+ // Build the hybrid spec: the real schema proxy (so all other field
557
+ // accesses produce KubernetesRef values) wrapped in a Proxy that
558
+ // intercepts the overridden keys and returns `undefined`. Accessing
559
+ // an overridden key's sub-property (e.g., `spec.secretKeyRef.name`)
560
+ // will throw — but that's exactly the code path the override is
561
+ // meant to skip, so the thrown access lives inside the `else`
562
+ // branch that this run intentionally does not execute.
563
+ const realSchema = createSchemaProxy(schemaDefinition.spec?.json, schemaDefinition.status?.json);
564
+ const hybridSpec = createHybridSpecProxy(realSchema.spec, overriddenFields, overrideValues);
565
+ const tempCtx = createCompositionContext('hybrid-capture');
566
+ runWithCompositionContext(tempCtx, () => {
567
+ compositionFn(hybridSpec);
568
+ });
569
+ return {
570
+ captured: tempCtx.resources,
571
+ overriddenFields,
572
+ overrideConditions,
573
+ };
574
+ }
575
+ catch {
576
+ // Best-effort: compositions that throw when running with a hybrid spec
577
+ // degrade gracefully — stub resources still cover the missing factories
578
+ // and the proxy-run resources are used as-is.
579
+ return { captured: {}, overriddenFields: new Set(), overrideConditions: new Map() };
580
+ }
581
+ }
582
+ function createHybridSpecProxy(target, overriddenFields, overrideValues, pathPrefix = '') {
583
+ return new Proxy(target, {
584
+ get(proxyTarget, prop, receiver) {
585
+ if (typeof prop !== 'string') {
586
+ return Reflect.get(proxyTarget, prop, receiver);
587
+ }
588
+ const path = pathPrefix ? `${pathPrefix}.${prop}` : prop;
589
+ if (overriddenFields.has(path)) {
590
+ return overrideValues.has(path) ? overrideValues.get(path) : undefined;
591
+ }
592
+ const hasNestedOverride = Array.from(overriddenFields).some((field) => field.startsWith(`${path}.`));
593
+ const value = Reflect.get(proxyTarget, prop, receiver);
594
+ if (hasNestedOverride && value && (typeof value === 'object' || typeof value === 'function')) {
595
+ return createHybridSpecProxy(value, overriddenFields, overrideValues, path);
596
+ }
597
+ return value;
598
+ },
599
+ has(proxyTarget, prop) {
600
+ if (typeof prop !== 'string') {
601
+ return Reflect.has(proxyTarget, prop);
602
+ }
603
+ const path = pathPrefix ? `${pathPrefix}.${prop}` : prop;
604
+ if (overriddenFields.has(path)) {
605
+ return overrideValues.has(path);
606
+ }
607
+ return Reflect.has(proxyTarget, prop);
608
+ },
609
+ });
610
+ }
611
+ function cloneResourceTree(value) {
612
+ if (Array.isArray(value)) {
613
+ return value.map((item) => cloneResourceTree(item));
614
+ }
615
+ if (isPlainObject(value)) {
616
+ return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, cloneResourceTree(entryValue)]));
617
+ }
618
+ return value;
619
+ }
620
+ function structuralEquals(a, b) {
621
+ if (isLeafValue(a) || isLeafValue(b)) {
622
+ return leafEquals(a, b);
623
+ }
624
+ if (Array.isArray(a) && Array.isArray(b)) {
625
+ return a.length === b.length && a.every((item, index) => structuralEquals(item, b[index]));
626
+ }
627
+ if (isWalkableRecord(a) && isWalkableRecord(b)) {
628
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
629
+ for (const key of keys) {
630
+ if (!structuralEquals(a[key], b[key])) {
631
+ return false;
632
+ }
633
+ }
634
+ return true;
635
+ }
636
+ return a === b;
637
+ }
638
+ /**
639
+ * Walk `proxyRes` and `hybridRes` in parallel and replace any leaf value
640
+ * in `proxyRes` that differs from the corresponding leaf in `hybridRes`
641
+ * with a CEL `has(...) ? <proxy value> : <hybrid value>` conditional.
642
+ *
643
+ * This is how differential field-level branch detection works: when a
644
+ * composition writes `spec.x ? foo(spec.y) : bar()` at a deep nested
645
+ * location inside a custom factory's emitted resource, the proxy run
646
+ * produces one leaf and the hybrid run (with `spec.x` overridden to
647
+ * `undefined`) produces the other. Comparing the two resource trees
648
+ * field-by-field surfaces the divergence and lets us emit the correct
649
+ * KRO CEL conditional without needing to thread AST information through
650
+ * the factory's internal transformations.
651
+ *
652
+ * Assumptions and caveats:
653
+ * - Both resources come from the same factory call with the same
654
+ * resource ID, so their structures are shape-compatible.
655
+ * - The `overriddenFields` set drives the `has(...)` check — with a
656
+ * single overridden field we can emit a direct `has(schema.spec.X)`
657
+ * test. With multiple fields, we use the first one that appears in
658
+ * either leaf value; this is a heuristic, but in practice
659
+ * compositions rarely have multiple optional fields driving the
660
+ * same leaf.
661
+ * - Marker strings (`__KUBERNETES_REF__...__`) and CEL expressions
662
+ * are converted to their dollar-wrapped forms before embedding in
663
+ * the conditional.
664
+ */
665
+ function applyDifferentialFieldConditionals(currentRes, baselineRes, hybridRes, overriddenFields, overrideConditions, nestedStatusCel, resourceIds) {
666
+ walkAndConditionalize(currentRes, baselineRes, hybridRes, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
667
+ }
668
+ function walkAndConditionalize(current, baseline, hybrid, overriddenFields, overrideConditions, nestedStatusCel, resourceIds) {
669
+ if (Array.isArray(current) && Array.isArray(baseline) && Array.isArray(hybrid)) {
670
+ if (baseline.length !== hybrid.length) {
671
+ return structuralEquals(current, baseline)
672
+ ? buildCelConditional(baseline, hybrid, overriddenFields, overrideConditions, nestedStatusCel, resourceIds)
673
+ : current;
674
+ }
675
+ const maxLen = Math.max(current.length, hybrid.length);
676
+ for (let i = 0; i < maxLen; i++) {
677
+ const c = current[i];
678
+ const b = baseline[i];
679
+ const h = hybrid[i];
680
+ if (i >= current.length) {
681
+ // New element added by hybrid run — copy it over.
682
+ current.push(h);
683
+ continue;
684
+ }
685
+ if (i >= hybrid.length) {
686
+ // Element removed in hybrid — leave the proxy value as-is.
687
+ continue;
688
+ }
689
+ if (isLeafValue(b) && isLeafValue(h)) {
690
+ if (!leafEquals(b, h) && leafEquals(c, b)) {
691
+ current[i] = buildCelConditional(b, h, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
692
+ }
693
+ }
694
+ else {
695
+ const conditionalized = walkAndConditionalize(c, b, h, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
696
+ if (conditionalized !== c) {
697
+ current[i] = conditionalized;
698
+ }
699
+ }
700
+ }
701
+ return current;
702
+ }
703
+ if (isWalkableRecord(current) && isWalkableRecord(baseline) && isWalkableRecord(hybrid)) {
704
+ const keys = new Set([...Object.keys(current), ...Object.keys(baseline), ...Object.keys(hybrid)]);
705
+ for (const key of keys) {
706
+ const c = current[key];
707
+ const b = baseline[key];
708
+ const h = hybrid[key];
709
+ if (!(key in hybrid)) {
710
+ continue;
711
+ }
712
+ if (!(key in current)) {
713
+ current[key] = h;
714
+ continue;
715
+ }
716
+ if (isLeafValue(b) && isLeafValue(h)) {
717
+ if (!leafEquals(b, h) && leafEquals(c, b)) {
718
+ current[key] = buildCelConditional(b, h, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
719
+ }
720
+ }
721
+ else {
722
+ const conditionalized = walkAndConditionalize(c, b, h, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
723
+ if (conditionalized !== c) {
724
+ current[key] = conditionalized;
725
+ }
726
+ }
727
+ }
728
+ return current;
729
+ }
730
+ return current;
731
+ }
732
+ function isLeafValue(v) {
733
+ return (v === null ||
734
+ v === undefined ||
735
+ typeof v === 'string' ||
736
+ typeof v === 'number' ||
737
+ typeof v === 'boolean' ||
738
+ isCelExpressionLike(v) ||
739
+ // KubernetesRef proxies register as functions (typeof fn is 'function')
740
+ typeof v === 'function');
741
+ }
742
+ function runResourceStatusBranch(compositionFn, schemaDefinition, analysis, ternary, desiredConditionValue) {
743
+ const branchCtx = createCompositionContext('resource-status-branch', {
744
+ isReExecution: true,
745
+ });
746
+ branchCtx.liveStatusMap = createResourceStatusBranchMap(analysis, ternary, desiredConditionValue);
747
+ const branchSchema = createSchemaProxy(schemaDefinition?.spec?.json, schemaDefinition?.status?.json);
748
+ const specOverrides = createSpecConditionOverrideMap(ternary.conditionExpression, desiredConditionValue);
749
+ const branchSpec = specOverrides.size > 0
750
+ ? createSpecOverrideProxy(branchSchema.spec, specOverrides)
751
+ : branchSchema.spec;
752
+ runWithCompositionContext(branchCtx, () => {
753
+ runInStatusBuilderContext(() => {
754
+ compositionFn(branchSpec);
755
+ });
756
+ });
757
+ return branchCtx;
758
+ }
759
+ function createResourceStatusBranchMap(analysis, ternary, desiredConditionValue) {
760
+ const conditionExpression = ternary.conditionExpression ?? `${ternary.variableName}.status.${ternary.statusField}`;
761
+ const statusRefs = collectStatusRefs(conditionExpression);
762
+ if (statusRefs.length === 0) {
763
+ statusRefs.push({ variableName: ternary.variableName, statusField: ternary.statusField });
764
+ }
765
+ const statusMap = new Map();
766
+ for (const statusRef of statusRefs) {
767
+ const resourceId = analysis.variableToResourceId.get(statusRef.variableName) ?? statusRef.variableName;
768
+ const existing = statusMap.get(resourceId) ?? {};
769
+ setNestedBranchStatusValue(existing, statusRef.statusField, getBranchStatusValue(conditionExpression, `${statusRef.variableName}.status.${statusRef.statusField}`, desiredConditionValue));
770
+ statusMap.set(resourceId, existing);
771
+ }
772
+ return statusMap;
773
+ }
774
+ function setNestedBranchStatusValue(target, statusField, value) {
775
+ const parts = statusField.split('.').filter(Boolean);
776
+ if (parts.length === 0)
777
+ return;
778
+ let cursor = target;
779
+ for (let i = 0; i < parts.length - 1; i++) {
780
+ const part = parts[i];
781
+ if (!part)
782
+ continue;
783
+ const next = cursor[part];
784
+ if (!isPlainObject(next)) {
785
+ cursor[part] = {};
786
+ }
787
+ cursor = cursor[part];
788
+ }
789
+ const leaf = parts[parts.length - 1];
790
+ if (leaf) {
791
+ cursor[leaf] = value;
792
+ }
793
+ }
794
+ function collectStatusRefs(conditionExpression) {
795
+ const refs = [];
796
+ const seen = new Set();
797
+ const statusRefPattern = /\b([A-Za-z_$][\w$]*)\.status\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
798
+ for (const match of conditionExpression.matchAll(statusRefPattern)) {
799
+ const variableName = match[1];
800
+ const statusField = match[2];
801
+ if (!variableName || !statusField)
802
+ continue;
803
+ const key = `${variableName}:${statusField}`;
804
+ if (seen.has(key))
805
+ continue;
806
+ seen.add(key);
807
+ refs.push({ variableName, statusField });
808
+ }
809
+ return refs;
810
+ }
811
+ function createSpecConditionOverrideMap(conditionExpression, desiredConditionValue) {
812
+ const overrides = new Map();
813
+ if (!conditionExpression)
814
+ return overrides;
815
+ const specRefPattern = /\b(?:schema\.)?spec\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
816
+ for (const match of conditionExpression.matchAll(specRefPattern)) {
817
+ const specPath = match[1];
818
+ const fullRef = match[0];
819
+ if (!specPath || !fullRef || overrides.has(specPath))
820
+ continue;
821
+ overrides.set(specPath, getBranchStatusValue(conditionExpression, fullRef, desiredConditionValue));
822
+ }
823
+ return overrides;
824
+ }
825
+ function createSpecOverrideProxy(target, overrides, path = []) {
826
+ return new Proxy(target, {
827
+ get(obj, prop, receiver) {
828
+ if (typeof prop !== 'string')
829
+ return Reflect.get(obj, prop, receiver);
830
+ const fullPath = [...path, prop].join('.');
831
+ if (overrides.has(fullPath))
832
+ return overrides.get(fullPath);
833
+ const hasNestedOverride = [...overrides.keys()].some((key) => key.startsWith(`${fullPath}.`));
834
+ const value = Reflect.get(obj, prop, receiver);
835
+ if (hasNestedOverride && value && (typeof value === 'object' || typeof value === 'function')) {
836
+ return createSpecOverrideProxy(value, overrides, [...path, prop]);
837
+ }
838
+ return value;
839
+ },
840
+ ownKeys: (obj) => Reflect.ownKeys(obj),
841
+ getOwnPropertyDescriptor: (obj, prop) => Reflect.getOwnPropertyDescriptor(obj, prop),
842
+ });
843
+ }
844
+ function getBranchStatusValue(conditionExpression, statusRefExpression, desiredConditionValue) {
845
+ const escapedRef = statusRefExpression.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
846
+ const comparison = conditionExpression.match(new RegExp(`${escapedRef}\\s*(>=|>|<=|<|==|!=)\\s*(-?\\d+(?:\\.\\d+)?)`));
847
+ if (comparison?.[1] && comparison[2] !== undefined) {
848
+ const operator = comparison[1];
849
+ const numberValue = Number(comparison[2]);
850
+ if (operator === '>=')
851
+ return desiredConditionValue ? numberValue : numberValue - 1;
852
+ if (operator === '>')
853
+ return desiredConditionValue ? numberValue + 1 : numberValue;
854
+ if (operator === '<=')
855
+ return desiredConditionValue ? numberValue : numberValue + 1;
856
+ if (operator === '<')
857
+ return desiredConditionValue ? numberValue - 1 : numberValue;
858
+ if (operator === '==')
859
+ return desiredConditionValue ? numberValue : numberValue + 1;
860
+ if (operator === '!=')
861
+ return desiredConditionValue ? numberValue + 1 : numberValue;
862
+ }
863
+ const stringComparison = conditionExpression.match(new RegExp(`${escapedRef}\\s*(==|!=)\\s*(['"])(.*?)\\2`));
864
+ if (stringComparison?.[1] && stringComparison[3] !== undefined) {
865
+ const operator = stringComparison[1];
866
+ const stringValue = stringComparison[3];
867
+ if (operator === '==')
868
+ return desiredConditionValue ? stringValue : `__typekro_not_${stringValue}`;
869
+ if (operator === '!=')
870
+ return desiredConditionValue ? `__typekro_not_${stringValue}` : stringValue;
871
+ }
872
+ const booleanComparison = conditionExpression.match(new RegExp(`${escapedRef}\\s*(==|!=)\\s*(true|false)`));
873
+ if (booleanComparison?.[1] && booleanComparison[2] !== undefined) {
874
+ const operator = booleanComparison[1];
875
+ const booleanValue = booleanComparison[2] === 'true';
876
+ if (operator === '==')
877
+ return desiredConditionValue ? booleanValue : !booleanValue;
878
+ if (operator === '!=')
879
+ return desiredConditionValue ? !booleanValue : booleanValue;
880
+ }
881
+ const negatedRef = new RegExp(`!\\s*${escapedRef}(?![A-Za-z0-9_$.])`).test(conditionExpression);
882
+ return negatedRef ? !desiredConditionValue : desiredConditionValue;
883
+ }
884
+ function applyResourceStatusBranchDiff(targetRes, trueRes, falseRes, conditionCel, nestedStatusCel, resourceIds) {
885
+ for (const key of new Set([...Object.keys(trueRes), ...Object.keys(falseRes)])) {
886
+ if (key === '__resourceId' || key === 'id' || key.startsWith('__'))
887
+ continue;
888
+ const tv = trueRes[key];
889
+ const fv = falseRes[key];
890
+ if (tv === undefined && fv !== undefined) {
891
+ const falseRepr = celValueRepr(fv, nestedStatusCel, resourceIds);
892
+ targetRes[key] = `\${${conditionCel} ? omit() : ${falseRepr}}`;
893
+ continue;
894
+ }
895
+ if (fv === undefined && tv !== undefined) {
896
+ const trueRepr = celValueRepr(tv, nestedStatusCel, resourceIds);
897
+ targetRes[key] = `\${${conditionCel} ? ${trueRepr} : omit()}`;
898
+ continue;
899
+ }
900
+ if (tv === undefined || fv === undefined)
901
+ continue;
902
+ if (isCelExpressionLike(tv) || isCelExpressionLike(fv)) {
903
+ if (!leafEquals(tv, fv)) {
904
+ const trueRepr = celValueRepr(tv, nestedStatusCel, resourceIds);
905
+ const falseRepr = celValueRepr(fv, nestedStatusCel, resourceIds);
906
+ targetRes[key] = `\${${conditionCel} ? ${trueRepr} : ${falseRepr}}`;
907
+ }
908
+ continue;
909
+ }
910
+ const targetValue = targetRes[key];
911
+ if (isPlainObject(tv) && isPlainObject(fv)) {
912
+ if (!isPlainObject(targetValue))
913
+ targetRes[key] = {};
914
+ applyResourceStatusBranchDiff(targetRes[key], tv, fv, conditionCel, nestedStatusCel, resourceIds);
915
+ }
916
+ else if (Array.isArray(tv) && Array.isArray(fv) && tv.length === fv.length) {
917
+ if (!Array.isArray(targetValue))
918
+ targetRes[key] = [...tv];
919
+ const targetArray = targetRes[key];
920
+ for (let i = 0; i < tv.length; i++) {
921
+ if (isPlainObject(tv[i]) && isPlainObject(fv[i])) {
922
+ if (!isPlainObject(targetArray[i]))
923
+ targetArray[i] = {};
924
+ applyResourceStatusBranchDiff(targetArray[i], tv[i], fv[i], conditionCel, nestedStatusCel, resourceIds);
925
+ }
926
+ else if (!leafEquals(tv[i], fv[i])) {
927
+ const trueRepr = celValueRepr(tv[i], nestedStatusCel, resourceIds);
928
+ const falseRepr = celValueRepr(fv[i], nestedStatusCel, resourceIds);
929
+ targetArray[i] = `\${${conditionCel} ? ${trueRepr} : ${falseRepr}}`;
930
+ }
931
+ }
932
+ }
933
+ else if (Array.isArray(tv) && Array.isArray(fv) && tv.length !== fv.length) {
934
+ const trueRepr = celValueRepr(tv, nestedStatusCel, resourceIds);
935
+ const falseRepr = celValueRepr(fv, nestedStatusCel, resourceIds);
936
+ targetRes[key] = `\${${conditionCel} ? ${trueRepr} : ${falseRepr}}`;
937
+ }
938
+ else if (!leafEquals(tv, fv)) {
939
+ const trueRepr = celValueRepr(tv, nestedStatusCel, resourceIds);
940
+ const falseRepr = celValueRepr(fv, nestedStatusCel, resourceIds);
941
+ targetRes[key] = `\${${conditionCel} ? ${trueRepr} : ${falseRepr}}`;
942
+ }
943
+ }
944
+ }
945
+ function leafEquals(a, b) {
946
+ // For KubernetesRef proxies, compare their string coercions (marker tokens).
947
+ if (typeof a === 'function' || typeof b === 'function') {
948
+ return String(a) === String(b);
949
+ }
950
+ return a === b;
951
+ }
952
+ function getNestedResourceStatusTargetIds(resources, nestedCompositionIds) {
953
+ if (!nestedCompositionIds || nestedCompositionIds.size === 0)
954
+ return [];
955
+ return Object.entries(resources)
956
+ .filter(([resourceId, resource]) => [...nestedCompositionIds].some((nestedId) => isNestedCompositionChild(resourceId, resource, nestedId)))
957
+ .map(([resourceId]) => resourceId);
958
+ }
959
+ function isNestedCompositionChild(resourceId, resource, nestedId) {
960
+ if (resourceId === nestedId)
961
+ return true;
962
+ const boundaryChar = resourceId[nestedId.length];
963
+ if (resourceId.startsWith(nestedId) && boundaryChar !== undefined && /[A-Z_-]/.test(boundaryChar)) {
964
+ return true;
965
+ }
966
+ const aliases = getMetadataField(resource, 'resourceAliases');
967
+ return aliases?.some((alias) => {
968
+ if (alias === nestedId)
969
+ return true;
970
+ const aliasBoundaryChar = alias[nestedId.length];
971
+ return alias.startsWith(nestedId) && aliasBoundaryChar !== undefined && /[A-Z_-]/.test(aliasBoundaryChar);
972
+ }) ?? false;
973
+ }
974
+ function isPlainObject(v) {
975
+ return typeof v === 'object' && v !== null && !Array.isArray(v) && !isCelExpressionLike(v);
976
+ }
977
+ function isWalkableRecord(v) {
978
+ if (isPlainObject(v)) {
979
+ return true;
980
+ }
981
+ return typeof v === 'function' && Object.keys(v).length > 0;
982
+ }
983
+ /** Duck-type check for CelExpression objects — avoids importing from cel.ts (cycle risk). */
984
+ function isCelExpressionLike(v) {
985
+ if (typeof v !== 'object' || v === null)
986
+ return false;
987
+ // Plain Cel.expr() objects carry the symbol brand; template expressions also
988
+ // carry `__isTemplate`. Support both shapes without importing cel.ts.
989
+ return 'expression' in v && typeof v.expression === 'string'
990
+ && (v[CEL_EXPRESSION_BRAND] === true || '__isTemplate' in v);
991
+ }
992
+ /**
993
+ * Build a CEL conditional string from two diverging leaf values.
994
+ *
995
+ * Both values may be concrete primitives, marker strings (from template
996
+ * literals containing proxy references), or KubernetesRef proxies. The
997
+ * returned string is a KRO mixed-template value like
998
+ * `${has(schema.spec.X) ? <proxy repr> : <hybrid repr>}` that later
999
+ * serialization phases treat as a final CEL expression.
1000
+ */
1001
+ function buildCelConditional(proxyValue, hybridValue, overriddenFields, overrideConditions, nestedStatusCel, resourceIds) {
1002
+ const field = pickConditionField(proxyValue, hybridValue, overriddenFields);
1003
+ const proxyRepr = celValueRepr(proxyValue, nestedStatusCel, resourceIds);
1004
+ const hybridRepr = celValueRepr(hybridValue, nestedStatusCel, resourceIds);
1005
+ const explicitCondition = overrideConditions.get(field);
1006
+ if (explicitCondition) {
1007
+ return `\${${explicitCondition} ? ${proxyRepr} : ${hybridRepr}}`;
1008
+ }
1009
+ // Chain has() guards when the proxy value references a sub-field deeper
1010
+ // than the controlling optional field. A single has(schema.spec.X) is
1011
+ // insufficient when the value accesses X.Y — the user may provide X: {}
1012
+ // without Y, and KRO would fail with "no such key: Y".
1013
+ const guardField = `schema.spec.${field}`;
1014
+ let guard = `has(${guardField})`;
1015
+ // Extract the full schema path from the proxy repr to check depth.
1016
+ // proxyRepr may be a bare path like `schema.spec.cnpgOperator.version`
1017
+ // or wrapped in string() like `string(schema.spec.cnpgOperator.version)`.
1018
+ // Chain has() guards for ALL intermediate levels between the controlling
1019
+ // field and the leaf. For `cnpgOperator.monitoring.enabled`, we need:
1020
+ // has(cnpgOperator) && has(cnpgOperator.monitoring) && has(cnpgOperator.monitoring.enabled)
1021
+ const schemaPathMatch = proxyRepr.match(/schema\.spec\.([a-zA-Z0-9_.]+)/);
1022
+ if (schemaPathMatch) {
1023
+ const fullRefPath = schemaPathMatch[1]?.replace(/\.+$/, '');
1024
+ if (!fullRefPath)
1025
+ return `\${has(${guardField}) ? ${proxyRepr} : ${hybridRepr}}`;
1026
+ const fullPath = `schema.spec.${fullRefPath}`;
1027
+ if (fullPath !== guardField && fullPath.startsWith(`${guardField}.`)) {
1028
+ const guardSegments = field.split('.').length;
1029
+ const leafSegments = fullRefPath.split('.');
1030
+ const guards = [`has(${guardField})`];
1031
+ for (let j = guardSegments + 1; j <= leafSegments.length; j++) {
1032
+ const intermediatePath = `schema.spec.${leafSegments.slice(0, j).join('.')}`;
1033
+ guards.push(`has(${intermediatePath})`);
1034
+ }
1035
+ guard = guards.join(' && ');
1036
+ }
1037
+ }
1038
+ return `\${${guard} ? ${proxyRepr} : ${hybridRepr}}`;
1039
+ }
1040
+ /**
1041
+ * Pick the "controlling" optional field for a diverging leaf. If exactly
1042
+ * one overridden field's marker appears in either value, use that. If
1043
+ * neither appears explicitly, default to the first field in the set —
1044
+ * this covers the common case of a single overridden field driving the
1045
+ * whole divergence.
1046
+ */
1047
+ function pickConditionField(proxyValue, hybridValue, overriddenFields) {
1048
+ const proxyStr = String(proxyValue);
1049
+ const hybridStr = String(hybridValue);
1050
+ for (const field of overriddenFields) {
1051
+ const marker = `__KUBERNETES_REF___schema___spec.${field}`;
1052
+ if (proxyStr.includes(marker) || hybridStr.includes(marker)) {
1053
+ return field;
1054
+ }
1055
+ }
1056
+ // Fallback: first overridden field (iteration order = insertion order).
1057
+ // Single-field overrides are the common case so this is usually correct;
1058
+ // the explicit-marker check above handles the multi-field case. When the
1059
+ // fallback fires, the emitted CEL may pick the wrong `has()` condition
1060
+ // for compositions with multiple optional fields driving the same leaf —
1061
+ // log it at debug level so the case is diagnosable if anyone reports
1062
+ // incorrect CEL output.
1063
+ const fallback = overriddenFields.values().next().value ?? '';
1064
+ getComponentLogger('serialization').debug('pickConditionField fallback: no marker matched, using first overridden field', {
1065
+ fallbackField: fallback,
1066
+ overriddenFields: Array.from(overriddenFields),
1067
+ proxyValuePreview: proxyStr.slice(0, 120),
1068
+ hybridValuePreview: hybridStr.slice(0, 120),
1069
+ });
1070
+ return fallback;
1071
+ }
1072
+ /**
1073
+ * Convert a leaf value into its CEL expression representation.
1074
+ *
1075
+ * - Marker strings become `schema.spec.X` paths (bare, no `${}`)
1076
+ * - KubernetesRef proxies become their inner CEL path
1077
+ * - String literals become double-quoted CEL string literals (with
1078
+ * embedded markers converted to string() concatenations so the
1079
+ * result is valid CEL, not just a raw string)
1080
+ * - Numbers and booleans are emitted verbatim
1081
+ */
1082
+ // Re-export from shared utility for local use. This was previously an
1083
+ // inline copy; the canonical implementation lives in utils/cel-escape.ts.
1084
+ import { escapeCelString as escapeCelLiteral } from '../../utils/cel-escape.js';
1085
+ function celValueRepr(value, nestedStatusCel, resourceIds) {
1086
+ if (value === null || value === undefined)
1087
+ return '""';
1088
+ if (typeof value === 'number' || typeof value === 'boolean')
1089
+ return String(value);
1090
+ // CelExpression object — use the expression string directly.
1091
+ if (isCelExpressionLike(value)) {
1092
+ return unwrapKroExpression(finalizeCelForKro(value.expression, nestedStatusCel, createBranchCelContext(nestedStatusCel, resourceIds)));
1093
+ }
1094
+ if (typeof value === 'function') {
1095
+ // KubernetesRef proxy — toString yields the marker token which we
1096
+ // convert to a bare CEL path via the marker → CEL rules.
1097
+ return markerStringToCelBare(String(value));
1098
+ }
1099
+ if (typeof value === 'string') {
1100
+ if (value.includes('__KUBERNETES_REF_')) {
1101
+ return markerStringToCelExpr(value);
1102
+ }
1103
+ // Plain string literal — escape for CEL embedding
1104
+ return `"${escapeCelLiteral(value)}"`;
1105
+ }
1106
+ if (Array.isArray(value)) {
1107
+ return `[${value.map((item) => celValueRepr(item, nestedStatusCel, resourceIds)).join(', ')}]`;
1108
+ }
1109
+ if (isPlainObject(value)) {
1110
+ return `{${Object.entries(value)
1111
+ .map(([key, entryValue]) => `"${escapeCelLiteral(key)}": ${celValueRepr(entryValue, nestedStatusCel, resourceIds)}`)
1112
+ .join(', ')}}`;
1113
+ }
1114
+ return '""';
1115
+ }
1116
+ function createBranchCelContext(nestedStatusCel, resourceIds) {
1117
+ if (!nestedStatusCel && !resourceIds)
1118
+ return undefined;
1119
+ return {
1120
+ celPrefix: '',
1121
+ resourceIdStrategy: 'deterministic',
1122
+ ...(nestedStatusCel ? { nestedStatusCel } : {}),
1123
+ ...(resourceIds ? { resourceIds } : {}),
1124
+ };
1125
+ }
1126
+ function unwrapKroExpression(value) {
1127
+ if (value.startsWith('${') && value.endsWith('}') && value.indexOf('${', 2) === -1) {
1128
+ return value.slice(2, -1);
1129
+ }
1130
+ return value;
1131
+ }
1132
+ /**
1133
+ * Convert a single-marker string (the whole string is one marker) to
1134
+ * its bare CEL path form: `schema.spec.X` or `resources.X.field`.
1135
+ */
1136
+ function markerStringToCelBare(str) {
1137
+ const m = str.match(new RegExp(`^${KUBERNETES_REF_MARKER_SOURCE}$`));
1138
+ if (!m)
1139
+ return markerStringToCelExpr(str);
1140
+ const [, resourceId, fieldPath] = m;
1141
+ return resourceId === '__schema__' ? `schema.${fieldPath}` : `${resourceId}.${fieldPath}`;
1142
+ }
1143
+ /**
1144
+ * Convert a mixed string containing literal text and markers to a CEL
1145
+ * concatenation expression using `string()` wrappers, suitable for
1146
+ * embedding inside a CEL ternary.
1147
+ */
1148
+ function markerStringToCelExpr(str) {
1149
+ const markerSource = KUBERNETES_REF_MARKER_SOURCE;
1150
+ // Fast path: whole string is a single marker
1151
+ const singleMatch = str.match(new RegExp(`^${markerSource}$`));
1152
+ if (singleMatch) {
1153
+ const [, resourceId, fieldPath] = singleMatch;
1154
+ return resourceId === '__schema__' ? `schema.${fieldPath}` : `${resourceId}.${fieldPath}`;
1155
+ }
1156
+ // Slow path: interleave literal text and markers via CEL string concatenation
1157
+ const parts = [];
1158
+ let lastIndex = 0;
1159
+ const pattern = new RegExp(markerSource, 'g');
1160
+ let m = pattern.exec(str);
1161
+ while (m !== null) {
1162
+ if (m.index > lastIndex) {
1163
+ const literal = str.slice(lastIndex, m.index);
1164
+ parts.push(`"${escapeCelLiteral(literal)}"`);
1165
+ }
1166
+ const resourceId = m[1];
1167
+ const fieldPath = m[2];
1168
+ if (!resourceId || !fieldPath) {
1169
+ m = pattern.exec(str);
1170
+ continue;
1171
+ }
1172
+ const celPath = resourceId === '__schema__' ? `schema.${fieldPath}` : `${resourceId}.${fieldPath}`;
1173
+ parts.push(`string(${celPath})`);
1174
+ lastIndex = m.index + m[0].length;
1175
+ m = pattern.exec(str);
1176
+ }
1177
+ if (lastIndex < str.length) {
1178
+ const literal = str.slice(lastIndex);
1179
+ parts.push(`"${escapeCelLiteral(literal)}"`);
1180
+ }
1181
+ const [firstPart] = parts;
1182
+ return parts.length === 1 && firstPart !== undefined ? firstPart : parts.join(' + ');
1183
+ }
279
1184
  // =============================================================================
280
1185
  // Extracted helper: Direct factory status re-analysis
281
1186
  // =============================================================================
@@ -427,16 +1332,20 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
427
1332
  const schemaDefinition = {
428
1333
  apiVersion: definition.apiVersion || 'v1alpha1',
429
1334
  kind: definition.kind,
1335
+ ...(definition.group && { group: definition.group }),
430
1336
  spec: definition.spec,
431
1337
  status: definition.status,
432
1338
  };
433
- const schema = createSchemaProxy();
1339
+ // Pass the Arktype JSON so the proxy is shape-aware — spread
1340
+ // (`{ ...spec.X }`) and `Object.keys(spec.X)` enumerate declared
1341
+ // fields instead of returning an opaque empty object.
1342
+ const schema = createSchemaProxy(definition.spec?.json, definition.status?.json);
434
1343
  const builderResult = resourceBuilder(schema);
435
1344
  const { resources: resourcesWithKeys, closures } = separateResourcesAndClosures(builderResult);
436
1345
  // 3. Analyze status builder and convert JS expressions to CEL
437
1346
  const { statusMappings, analyzedStatusMappings, mappingAnalysis, phaseBStatusMappings } = analyzeAndConvertStatusMappings(definition, statusBuilder, schema, resourcesWithKeys, serializationLogger);
438
1347
  // 4. Analyze composition body for control flow patterns (must run before validation)
439
- const { compositionAnalysis, analysisState } = processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analyzedStatusMappings, serializationLogger);
1348
+ const { compositionAnalysis, analysisState } = processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analyzedStatusMappings, serializationLogger, schemaDefinition);
440
1349
  // 5. Validate resource IDs and CEL expressions
441
1350
  const validation = validateResourceGraphDefinition(resourcesWithKeys, analyzedStatusMappings);
442
1351
  if (!validation.isValid) {
@@ -455,6 +1364,18 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
455
1364
  // Evaluate and optimize CEL expressions
456
1365
  const evaluationContext = { resources: resourcesWithKeys, schema };
457
1366
  const { mappings: optimizedStatusMappings, optimizations } = optimizeStatusMappings(analyzedStatusMappings, evaluationContext);
1367
+ for (const metadataKey of [
1368
+ '__originalCompositionFn',
1369
+ '__nestedCompositionFns',
1370
+ '__nestedCompositionDefinitions',
1371
+ '__nestedCompositionResources',
1372
+ '__nestedCompositionSpecMappings',
1373
+ ]) {
1374
+ const descriptor = Object.getOwnPropertyDescriptor(statusMappings, metadataKey);
1375
+ if (descriptor) {
1376
+ Object.defineProperty(optimizedStatusMappings, metadataKey, descriptor);
1377
+ }
1378
+ }
458
1379
  if (optimizations.length > 0) {
459
1380
  serializationLogger.info('CEL expression optimizations applied', { optimizations });
460
1381
  }
@@ -489,6 +1410,11 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
489
1410
  statusMappings: directStatusMappings,
490
1411
  compositionFn: declarativeCompositionFn,
491
1412
  compositionDefinition: definition,
1413
+ ...(this._singletonDefinitions
1414
+ ? {
1415
+ singletonDefinitions: this._singletonDefinitions,
1416
+ }
1417
+ : {}),
492
1418
  });
493
1419
  }
494
1420
  else if (mode === 'kro') {
@@ -497,13 +1423,22 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
497
1423
  closures,
498
1424
  factoryType: 'kro',
499
1425
  compositionFn: declarativeCompositionFn,
1426
+ compositionAnalysis,
1427
+ ...(this._singletonDefinitions
1428
+ ? {
1429
+ singletonDefinitions: this._singletonDefinitions,
1430
+ }
1431
+ : {}),
500
1432
  });
501
1433
  }
502
1434
  else {
503
1435
  throw new ValidationError(`Unsupported factory mode: ${mode}`, 'ResourceGraphDefinition', definition.name, 'mode', ['Use "kro" or "direct" as the factory mode']);
504
1436
  }
505
1437
  },
506
- toYaml() {
1438
+ toYaml(spec) {
1439
+ if (spec !== undefined) {
1440
+ return this.factory('kro').toYaml(spec);
1441
+ }
507
1442
  // Apply composition body analysis results (guard: only once)
508
1443
  if (compositionAnalysis && !analysisState.appliedToResources) {
509
1444
  analysisState.appliedToResources = true;
@@ -520,10 +1455,13 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
520
1455
  // Collect nested composition status CEL mappings from the composition context.
521
1456
  // These enable inlining the inner composition's real CEL expressions instead
522
1457
  // of referencing virtual nested composition IDs.
523
- // Extract nested composition status CEL mappings attached by executeCompositionCore.
524
- // These are on the raw statusMappings (capturedStatus from the composition function),
525
- // not on the optimizedStatusMappings (which is a processed copy).
526
- const nestedStatusCel = statusMappings.__nestedStatusCel ?? {};
1458
+ // Extract nested composition status CEL mappings attached by
1459
+ // executeCompositionCore via Reflect.set. Must use
1460
+ // Object.getOwnPropertyDescriptor to bypass the Enhanced proxy's
1461
+ // get handler which would return a KubernetesRef instead of the
1462
+ // actual Record<string, string>.
1463
+ const nestedStatusDescriptor = Object.getOwnPropertyDescriptor(statusMappings, '__nestedStatusCel');
1464
+ const nestedStatusCel = nestedStatusDescriptor?.value ?? {};
527
1465
  serializationLogger.debug('Nested status CEL extraction', {
528
1466
  hasNestedStatusCel: Object.keys(nestedStatusCel).length > 0,
529
1467
  keys: Object.keys(nestedStatusCel),
@@ -533,6 +1471,17 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
533
1471
  if (definition.group) {
534
1472
  kroSchema.group = definition.group;
535
1473
  }
1474
+ // Attach nested status CEL mappings to the schema as a non-enumerable
1475
+ // property (same pattern as __ternaryConditionals, __omitFields).
1476
+ // Non-enumerable so it doesn't appear in the YAML output, but
1477
+ // accessible via KroSimpleSchemaWithMetadata for the YAML serializer
1478
+ // to resolve virtual composition IDs in resource templates.
1479
+ if (Object.keys(nestedStatusCel).length > 0) {
1480
+ Object.defineProperty(kroSchema, '__nestedStatusCel', {
1481
+ value: nestedStatusCel,
1482
+ enumerable: false,
1483
+ });
1484
+ }
536
1485
  // Inject status overrides into schema status section.
537
1486
  // Convert "..." to '...' in CEL string literals for YAML compatibility.
538
1487
  const statusOverrides = compositionAnalysis?.statusOverrides ?? [];
@@ -545,6 +1494,18 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
545
1494
  kroSchema.status[override.propertyPath] = yamlSafe;
546
1495
  }
547
1496
  }
1497
+ // Apply ternary conditionals (once only — guard prevents
1498
+ // double-processing if toYaml() is called multiple times).
1499
+ // Note: omit() wrapping for optional fields is no longer a
1500
+ // post-processing step — it's applied inline during ref-to-CEL
1501
+ // conversion via `SerializationContext.omitFields`, which reads
1502
+ // from `kroSchema.__omitFields` inside `serializeResourceGraphToYaml`.
1503
+ if (!analysisState.ternaryAndOmitApplied) {
1504
+ analysisState.ternaryAndOmitApplied = true;
1505
+ if (kroSchema.__ternaryConditionals?.length) {
1506
+ applyTernaryConditionalsToResources(resourcesWithKeys, kroSchema.__ternaryConditionals, kroSchema.__nestedStatusCel);
1507
+ }
1508
+ }
548
1509
  return serializeResourceGraphToYaml(definition.name, resourcesWithKeys, options, kroSchema);
549
1510
  },
550
1511
  };