@opensoha/contracts 0.1.16 → 0.1.18

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 (391) hide show
  1. package/COMPATIBILITY.md +91 -0
  2. package/README.md +49 -3
  3. package/auth/catalog_generated.go +2 -2
  4. package/auth/catalog_test.go +32 -0
  5. package/auth/outpost_headers.go +17 -0
  6. package/auth/outpost_headers_test.go +16 -0
  7. package/auth/permission-catalog.json +1097 -187
  8. package/compat/consumer-revisions.json +3 -0
  9. package/compat/json-schema-ecosystem-0.1.0.json +1 -1
  10. package/compat/openapi-loose-boundaries-0.1.0.json +78 -46
  11. package/delivery/canonical.go +136 -0
  12. package/delivery/document.go +203 -0
  13. package/delivery/document.schema.json +1527 -0
  14. package/delivery/document_test.go +191 -0
  15. package/delivery/parser.go +155 -0
  16. package/evaluation/evaluation-run.schema.json +64 -10
  17. package/fixtures/json-schema/gateway-manifest/invalid/bad-execution-mode.json +73 -0
  18. package/fixtures/json-schema/gateway-manifest/invalid/unversioned-check.json +85 -0
  19. package/fixtures/json-schema/gateway-manifest/valid/checks.json +86 -0
  20. package/fixtures/json-schema/gateway-manifest/valid/versioned-task.json +73 -0
  21. package/fixtures/json-schema/network-ingest-event/invalid/duplicate-event.json +33 -0
  22. package/fixtures/json-schema/network-ingest-event/invalid/message-payload-mismatch.json +23 -0
  23. package/fixtures/json-schema/network-ingest-event/invalid/password.json +22 -0
  24. package/fixtures/json-schema/network-ingest-event/invalid/proxy-flow-destination.json +28 -0
  25. package/fixtures/json-schema/network-ingest-event/invalid/vpn-probe-negative-rtt.json +34 -0
  26. package/fixtures/json-schema/network-ingest-event/invalid/vpn-stats-secret.json +27 -0
  27. package/fixtures/json-schema/network-ingest-event/valid/endpoint-proxy-flow.json +27 -0
  28. package/fixtures/json-schema/network-ingest-event/valid/freeradius-accounting.json +25 -0
  29. package/fixtures/json-schema/network-ingest-event/valid/gateway.json +53 -0
  30. package/fixtures/json-schema/network-ingest-event/valid/vpn-probe-batch.json +45 -0
  31. package/fixtures/json-schema/network-ingest-event/valid/vpn-tunnel-stats.json +26 -0
  32. package/fixtures/json-schema/network-radius-accounting/invalid/password.json +9 -0
  33. package/fixtures/json-schema/network-radius-accounting/valid/accounting.json +16 -0
  34. package/fixtures/json-schema/network-runtime-protocol/invalid/invalid-cidr.json +31 -0
  35. package/fixtures/json-schema/network-runtime-protocol/invalid/message-payload-mismatch.json +19 -0
  36. package/fixtures/json-schema/network-runtime-protocol/invalid/mihomo-raw-subscription.json +33 -0
  37. package/fixtures/json-schema/network-runtime-protocol/invalid/nas-authorization-password.json +18 -0
  38. package/fixtures/json-schema/network-runtime-protocol/invalid/nas-eap-without-certificate.json +17 -0
  39. package/fixtures/json-schema/network-runtime-protocol/invalid/private-key.json +20 -0
  40. package/fixtures/json-schema/network-runtime-protocol/invalid/raw-credential-reference.json +16 -0
  41. package/fixtures/json-schema/network-runtime-protocol/invalid/runtime-interval-too-short.json +23 -0
  42. package/fixtures/json-schema/network-runtime-protocol/invalid/vpn-managed-bad-token.json +15 -0
  43. package/fixtures/json-schema/network-runtime-protocol/invalid/vpn-managed-mode-override.json +16 -0
  44. package/fixtures/json-schema/network-runtime-protocol/invalid/wireguard-capability-without-key.json +19 -0
  45. package/fixtures/json-schema/network-runtime-protocol/invalid/wireguard-default-route.json +31 -0
  46. package/fixtures/json-schema/network-runtime-protocol/invalid/ztna-without-access-grant.json +17 -0
  47. package/fixtures/json-schema/network-runtime-protocol/valid/configuration-desired.json +86 -0
  48. package/fixtures/json-schema/network-runtime-protocol/valid/direct-ztna-connect.json +19 -0
  49. package/fixtures/json-schema/network-runtime-protocol/valid/direct-ztna-result.json +33 -0
  50. package/fixtures/json-schema/network-runtime-protocol/valid/internal-ztna-connect.json +19 -0
  51. package/fixtures/json-schema/network-runtime-protocol/valid/managed-vpn-macos-enrollment.json +27 -0
  52. package/fixtures/json-schema/network-runtime-protocol/valid/mihomo-app-subscription-configuration.json +32 -0
  53. package/fixtures/json-schema/network-runtime-protocol/valid/mihomo-manual-node-configuration.json +32 -0
  54. package/fixtures/json-schema/network-runtime-protocol/valid/mihomo-wireguard-configuration.json +70 -0
  55. package/fixtures/json-schema/network-runtime-protocol/valid/nas-authorization-request.json +19 -0
  56. package/fixtures/json-schema/network-runtime-protocol/valid/nas-authorization-result.json +24 -0
  57. package/fixtures/json-schema/network-runtime-protocol/valid/nas-session-command-result.json +17 -0
  58. package/fixtures/json-schema/network-runtime-protocol/valid/nas-session-command.json +27 -0
  59. package/fixtures/json-schema/network-runtime-protocol/valid/vpn-connect-request.json +17 -0
  60. package/fixtures/json-schema/network-runtime-protocol/valid/vpn-connect-result.json +34 -0
  61. package/fixtures/json-schema/network-runtime-protocol/valid/vpn-managed-connect.json +15 -0
  62. package/fixtures/json-schema/network-runtime-protocol/valid/wireguard-enrollment.json +20 -0
  63. package/fixtures/json-schema/network-runtime-protocol/valid/wireguard-site-link.json +48 -0
  64. package/fixtures/openapi/AgentChatInput/invalid/empty-question.json +4 -0
  65. package/fixtures/openapi/AgentChatInput/invalid/system-role.json +9 -0
  66. package/fixtures/openapi/AgentChatInput/valid/authorized-background.json +5 -0
  67. package/fixtures/openapi/AgentChatInput/valid/conversation.json +15 -0
  68. package/fixtures/openapi/AgentRunnerToolCallRequest/invalid/model-identity.json +1 -0
  69. package/fixtures/openapi/AgentRunnerToolCallRequest/valid/search.json +1 -0
  70. package/fixtures/openapi/AnnouncementReceiptPageEnvelope/invalid/missing-read-count.json +9 -0
  71. package/fixtures/openapi/AnnouncementReceiptPageEnvelope/valid/read-and-unread.json +28 -0
  72. package/fixtures/openapi/ApplicationInput/invalid/negative-version.json +6 -0
  73. package/fixtures/openapi/ApplicationInput/invalid/zero-version.json +6 -0
  74. package/fixtures/openapi/ApplicationInput/valid/versioned.json +6 -0
  75. package/fixtures/openapi/ApprovalRequest/invalid/resolved-scopes.json +20 -0
  76. package/fixtures/openapi/ApprovalRequest/valid/resolved-scopes.json +21 -0
  77. package/fixtures/openapi/BuildTemplateInput/invalid/negative-revision.json +6 -0
  78. package/fixtures/openapi/BuildTemplateInput/valid/draft.json +11 -0
  79. package/fixtures/openapi/BuildTriggerRequest/invalid/no-ref.json +1 -0
  80. package/fixtures/openapi/BuildTriggerRequest/valid/no-environment.json +1 -0
  81. package/fixtures/openapi/BuildpacksConfiguration/invalid/mutable-builder.json +5 -0
  82. package/fixtures/openapi/BuildpacksConfiguration/invalid/privilege-override.json +6 -0
  83. package/fixtures/openapi/BuildpacksConfiguration/invalid/unsupported-platform.json +5 -0
  84. package/fixtures/openapi/BuildpacksConfiguration/valid/pinned.json +5 -0
  85. package/fixtures/openapi/CapabilityAssessment/invalid/contract.json +16 -0
  86. package/fixtures/openapi/CapabilityAssessment/valid/contract.json +16 -0
  87. package/fixtures/openapi/CapabilityPlan/invalid/contract.json +16 -0
  88. package/fixtures/openapi/CapabilityPlan/valid/contract.json +19 -0
  89. package/fixtures/openapi/CapabilityPlanValidation/invalid/contract.json +5 -0
  90. package/fixtures/openapi/CapabilityPlanValidation/valid/contract.json +11 -0
  91. package/fixtures/openapi/CapabilityTask/invalid/contract.json +33 -0
  92. package/fixtures/openapi/CapabilityTask/valid/contract.json +34 -0
  93. package/fixtures/openapi/CapabilityTaskInput/invalid/contract.json +22 -0
  94. package/fixtures/openapi/CapabilityTaskInput/valid/contract.json +22 -0
  95. package/fixtures/openapi/CapabilityValueSemantic/invalid/contract.json +4 -0
  96. package/fixtures/openapi/CapabilityValueSemantic/valid/contract.json +7 -0
  97. package/fixtures/openapi/DeliveryAccessResult/invalid/release-verification.json +8 -0
  98. package/fixtures/openapi/DeliveryAccessResult/valid/release-verification.json +8 -0
  99. package/fixtures/openapi/DeliveryBatchAssessment/invalid/release-verification.json +11 -0
  100. package/fixtures/openapi/DeliveryBatchAssessment/valid/release-verification.json +11 -0
  101. package/fixtures/openapi/DeliveryBatchAssessmentInput/invalid/release-verification.json +9 -0
  102. package/fixtures/openapi/DeliveryBatchAssessmentInput/valid/release-verification.json +8 -0
  103. package/fixtures/openapi/DeliveryBatchInput/invalid/ambiguous-source.json +19 -0
  104. package/fixtures/openapi/DeliveryBatchInput/invalid/missing-idempotency.json +16 -0
  105. package/fixtures/openapi/DeliveryBatchInput/valid/quick-deploy.json +17 -0
  106. package/fixtures/openapi/DeliveryBatchInput/valid/saved-workflow.json +5 -0
  107. package/fixtures/openapi/DeliveryBlueprintInput/invalid/unversioned-preset.json +31 -0
  108. package/fixtures/openapi/DeliveryBlueprintInput/valid/service-preset.json +32 -0
  109. package/fixtures/openapi/DeliveryDocument/invalid/unknown-field.json +24 -0
  110. package/fixtures/openapi/DeliveryDocument/invalid/unknown-kind.json +23 -0
  111. package/fixtures/openapi/DeliveryDocument/invalid/unknown-version.json +23 -0
  112. package/fixtures/openapi/DeliveryDocument/valid/build-template.json +26 -0
  113. package/fixtures/openapi/DeliveryDocument/valid/deployment-template.json +26 -0
  114. package/fixtures/openapi/DeliveryDocument/valid/legacy-dag.json +39 -0
  115. package/fixtures/openapi/DeliveryDocument/valid/workflow-template.json +23 -0
  116. package/fixtures/openapi/DeliveryDocument/valid/workflow.json +20 -0
  117. package/fixtures/openapi/DeliveryDraftInput/invalid/short-idempotency-key.json +30 -0
  118. package/fixtures/openapi/DeliveryDraftInput/valid/idempotent-create.json +30 -0
  119. package/fixtures/openapi/DeliveryDraftService/invalid/zero-version.json +7 -0
  120. package/fixtures/openapi/DeliveryDraftService/valid/versioned-template.json +9 -0
  121. package/fixtures/openapi/DeliveryExecutionHistoryPage/invalid-kind.json +9 -0
  122. package/fixtures/openapi/DeliveryExecutionHistoryPage/valid-build.json +16 -0
  123. package/fixtures/openapi/DeliveryExecutionHistoryPage/valid-empty.json +3 -0
  124. package/fixtures/openapi/DeliveryPlanApprovalInput/invalid/confirm.json +1 -0
  125. package/fixtures/openapi/DeliveryPlanApprovalInput/valid/approve.json +1 -0
  126. package/fixtures/openapi/DeliveryPlanInput/invalid/draft-manifest-revision.json +1 -0
  127. package/fixtures/openapi/DeliveryPlanInput/valid/manifest-revision.json +1 -0
  128. package/fixtures/openapi/DeliveryTargetInput/invalid/deploy-without-environment.json +7 -0
  129. package/fixtures/openapi/DeliveryTargetInput/valid/build-without-environment.json +6 -0
  130. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/duplicate-kind.json +13 -0
  131. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/embedded-credentials.json +16 -0
  132. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/empty-kinds.json +10 -0
  133. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/negative-generation.json +15 -0
  134. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/unknown-kind.json +12 -0
  135. package/fixtures/openapi/DeliveryTemplateSourceInput/valid/manual-source.json +15 -0
  136. package/fixtures/openapi/DeliveryWorkflowDefinition/invalid/unversioned-template.json +15 -0
  137. package/fixtures/openapi/DeliveryWorkflowDefinition/valid/cross-app.json +24 -0
  138. package/fixtures/openapi/DeliveryWorkflowInput/invalid/short-key.json +1 -0
  139. package/fixtures/openapi/DeliveryWorkflowInput/valid/idempotent-create.json +1 -0
  140. package/fixtures/openapi/DeploymentTemplatePreviewInput/invalid/invalid-target-override.json +7 -0
  141. package/fixtures/openapi/DeploymentTemplatePreviewInput/invalid/invalid-zero-version.json +6 -0
  142. package/fixtures/openapi/DeploymentTemplatePreviewInput/valid/valid.json +6 -0
  143. package/fixtures/openapi/DockerDeliveryConfiguration/invalid/empty-mappings.json +5 -0
  144. package/fixtures/openapi/DockerDeliveryConfiguration/valid/compose.json +7 -0
  145. package/fixtures/openapi/DockerDeliverySnapshot/invalid/missing-required-field.json +19 -0
  146. package/fixtures/openapi/DockerDeliverySnapshot/valid/frozen-project.json +20 -0
  147. package/fixtures/openapi/DockerOperationCallbackRequest/invalid/cancellation-acknowledged-not-boolean.json +13 -0
  148. package/fixtures/openapi/DockerOperationCallbackRequest/valid/cancellation-acknowledged.json +13 -0
  149. package/fixtures/openapi/DockerProjectInput/invalid/empty-host.json +4 -0
  150. package/fixtures/openapi/DockerProjectInput/valid/inline-idempotent.json +7 -0
  151. package/fixtures/openapi/DockerQuickCreateHostInput/invalid/capacity-type.json +1 -0
  152. package/fixtures/openapi/DockerQuickCreateHostInput/valid/capacity.json +1 -0
  153. package/fixtures/openapi/EndpointDevice/invalid/missing-owner.json +8 -0
  154. package/fixtures/openapi/EndpointDevice/valid/active.json +35 -0
  155. package/fixtures/openapi/EndpointDeviceRegistrationInput/invalid/client-owned-status.json +5 -0
  156. package/fixtures/openapi/EndpointDeviceRegistrationInput/invalid/oversized-network-inventory.json +18 -0
  157. package/fixtures/openapi/EndpointDeviceRegistrationInput/valid/macos.json +28 -0
  158. package/fixtures/openapi/HelmDeliveryConfiguration/invalid/plain-credential.json +21 -0
  159. package/fixtures/openapi/HelmDeliveryConfiguration/invalid/release-name.json +20 -0
  160. package/fixtures/openapi/HelmDeliveryConfiguration/valid/private-oci.json +20 -0
  161. package/fixtures/openapi/HelmDeliverySnapshot/invalid/secret-plan.json +31 -0
  162. package/fixtures/openapi/HelmDeliverySnapshot/invalid/unknown-operation.json +28 -0
  163. package/fixtures/openapi/HelmDeliverySnapshot/valid/first-install.json +28 -0
  164. package/fixtures/openapi/IdentityApplication/invalid/outpost-status.json +1 -0
  165. package/fixtures/openapi/IdentityApplicationOnboardingInput/invalid/missing-access.json +12 -0
  166. package/fixtures/openapi/IdentityApplicationOnboardingInput/valid/link.json +13 -0
  167. package/fixtures/openapi/IdentityOutpost/invalid/missing-runtime-status.json +9 -0
  168. package/fixtures/openapi/IdentityOutpost/valid/healthy.json +17 -0
  169. package/fixtures/openapi/IdentityOutpost/valid/unregistered.json +12 -0
  170. package/fixtures/openapi/IdentityOutpostDeployment/invalid/missing-protocol.json +3 -0
  171. package/fixtures/openapi/IdentityOutpostDeployment/valid/remote.json +6 -0
  172. package/fixtures/openapi/IdentityOutpostInput/invalid/forged-observation.json +7 -0
  173. package/fixtures/openapi/IdentityOutpostInput/valid/forward-auth.json +6 -0
  174. package/fixtures/openapi/IdentityOutpostInput/valid/legacy.json +8 -0
  175. package/fixtures/openapi/IdentityProviderSetup/invalid/fake-verification.json +1 -0
  176. package/fixtures/openapi/IdentityProviderSetup/valid/incomplete.json +1 -0
  177. package/fixtures/openapi/IdentityProviderSetup/valid/remote.json +1 -0
  178. package/fixtures/openapi/IdentityProviderUserMetadata/invalid/attributes.json +8 -0
  179. package/fixtures/openapi/IdentityProviderUserMetadata/valid/attributes.json +13 -0
  180. package/fixtures/openapi/KubernetesCustomResource/invalid/negative-generation.json +1 -0
  181. package/fixtures/openapi/KubernetesCustomResource/valid/deleting-operator.json +1 -0
  182. package/fixtures/openapi/KubernetesResourceCreationReceipt/invalid/manifest-content.json +20 -0
  183. package/fixtures/openapi/KubernetesResourceCreationReceipt/valid/confirmed.json +20 -0
  184. package/fixtures/openapi/KubernetesResourceCreationReceipt/valid/unknown.json +19 -0
  185. package/fixtures/openapi/LLMUpstreamTestResultEnvelope/invalid/models-not-array.json +9 -0
  186. package/fixtures/openapi/LLMUpstreamTestResultEnvelope/valid/discovered-models.json +11 -0
  187. package/fixtures/openapi/ManifestDeploymentEnvelope/invalid/negative-resource-observation.json +57 -0
  188. package/fixtures/openapi/ManifestDeploymentEnvelope/valid/operator-deleting.json +57 -0
  189. package/fixtures/openapi/ManifestExecutionTaskPayload/invalid/gitops-empty.json +7 -0
  190. package/fixtures/openapi/ManifestExecutionTaskPayload/valid/gitops-frozen-children.json +18 -0
  191. package/fixtures/openapi/ManifestKustomizeOptions/invalid/escaping-entry.json +1 -0
  192. package/fixtures/openapi/ManifestKustomizeOptions/invalid/mutable-image.json +1 -0
  193. package/fixtures/openapi/ManifestKustomizeOptions/valid/overlay-images.json +7 -0
  194. package/fixtures/openapi/ManifestRevisionInput/invalid/missing-version.json +1 -0
  195. package/fixtures/openapi/ManifestRevisionInput/valid/current-draft.json +1 -0
  196. package/fixtures/openapi/ManifestServiceTemplateInputs/invalid/mutable-image.json +8 -0
  197. package/fixtures/openapi/ManifestServiceTemplateInputs/valid/frozen-service-image.json +9 -0
  198. package/fixtures/openapi/NetworkAccessGrant/invalid/token-digest.json +15 -0
  199. package/fixtures/openapi/NetworkAccessGrant/valid/issued.json +14 -0
  200. package/fixtures/openapi/NetworkAccessGrantInput/invalid/no-resources.json +7 -0
  201. package/fixtures/openapi/NetworkAccessGrantInput/valid/direct-ztna.json +8 -0
  202. package/fixtures/openapi/NetworkAccessGrantSecret/invalid/short-token.json +17 -0
  203. package/fixtures/openapi/NetworkAccessGrantSecret/valid/issued.json +17 -0
  204. package/fixtures/openapi/NetworkAccessPolicy/invalid/missing-version.json +20 -0
  205. package/fixtures/openapi/NetworkAccessPolicy/valid/published-draft.json +21 -0
  206. package/fixtures/openapi/NetworkAccessPolicyInput/invalid/duplicate-team.json +17 -0
  207. package/fixtures/openapi/NetworkAccessPolicyInput/valid/team-and-tag-allow.json +17 -0
  208. package/fixtures/openapi/NetworkConflictAnalysisRequest/invalid/ipv6-range.json +9 -0
  209. package/fixtures/openapi/NetworkConflictAnalysisRequest/valid/runtime-ranges.json +15 -0
  210. package/fixtures/openapi/NetworkConnectionOption/invalid/secret.json +10 -0
  211. package/fixtures/openapi/NetworkConnectionOption/valid/wifi.json +9 -0
  212. package/fixtures/openapi/NetworkConnectionOption/valid/wired.json +8 -0
  213. package/fixtures/openapi/NetworkGateway/invalid/missing-site.json +7 -0
  214. package/fixtures/openapi/NetworkGateway/valid/online.json +23 -0
  215. package/fixtures/openapi/NetworkGatewayInput/invalid/default-route-overlay.json +13 -0
  216. package/fixtures/openapi/NetworkGatewayInput/valid/routed.json +14 -0
  217. package/fixtures/openapi/NetworkLease/invalid/empty-cidrs.json +12 -0
  218. package/fixtures/openapi/NetworkLease/valid/active.json +12 -0
  219. package/fixtures/openapi/NetworkMihomoProfile/invalid/subscription-leak.json +20 -0
  220. package/fixtures/openapi/NetworkMihomoProfile/valid/managed.json +21 -0
  221. package/fixtures/openapi/NetworkMihomoProfileInput/invalid/app-subscription-secret.json +14 -0
  222. package/fixtures/openapi/NetworkMihomoProfileInput/invalid/manual-node-with-subscription.json +20 -0
  223. package/fixtures/openapi/NetworkMihomoProfileInput/valid/app-subscription.json +14 -0
  224. package/fixtures/openapi/NetworkMihomoProfileInput/valid/managed.json +16 -0
  225. package/fixtures/openapi/NetworkMihomoProfileInput/valid/manual-node.json +21 -0
  226. package/fixtures/openapi/NetworkNASBinding/invalid/secret.json +13 -0
  227. package/fixtures/openapi/NetworkNASBinding/valid/default.json +16 -0
  228. package/fixtures/openapi/NetworkNASBindingInput/invalid/secret.json +10 -0
  229. package/fixtures/openapi/NetworkNASBindingInput/valid/default.json +13 -0
  230. package/fixtures/openapi/NetworkPolicyPreviewRequest/invalid/unknown-mode.json +6 -0
  231. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/external-direct-ztna.json +6 -0
  232. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/external-vpn-ztna.json +6 -0
  233. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/external-vpn.json +6 -0
  234. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/internal-direct.json +7 -0
  235. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/internal-ztna.json +7 -0
  236. package/fixtures/openapi/NetworkPolicyPreviewResult/invalid/bypass-path.json +9 -0
  237. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/onboarding.json +10 -0
  238. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/protected-broad-path-denied.json +9 -0
  239. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/protected-resource.json +9 -0
  240. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/quarantine.json +10 -0
  241. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/restricted.json +10 -0
  242. package/fixtures/openapi/NetworkPolicySnapshot/invalid/zero-version.json +7 -0
  243. package/fixtures/openapi/NetworkPolicySnapshot/valid/published.json +7 -0
  244. package/fixtures/openapi/NetworkResourceInput/invalid/invalid-port.json +10 -0
  245. package/fixtures/openapi/NetworkResourceInput/valid/protected-cidr.json +10 -0
  246. package/fixtures/openapi/NetworkRuntimeEnrollmentInput/invalid/excessive-ttl.json +7 -0
  247. package/fixtures/openapi/NetworkRuntimeEnrollmentInput/valid/endpoint.json +7 -0
  248. package/fixtures/openapi/NetworkRuntimeEnrollmentSecret/invalid/short-token.json +15 -0
  249. package/fixtures/openapi/NetworkRuntimeEnrollmentSecret/valid/pending.json +15 -0
  250. package/fixtures/openapi/NetworkSession/invalid/unknown-status.json +14 -0
  251. package/fixtures/openapi/NetworkSession/valid/restricted-vpn-ztna.json +16 -0
  252. package/fixtures/openapi/NetworkSessionActionExecuteInput/invalid/short-hash.json +6 -0
  253. package/fixtures/openapi/NetworkSessionActionExecuteInput/valid/restrict.json +6 -0
  254. package/fixtures/openapi/NetworkSessionActionInput/invalid/unknown-action.json +5 -0
  255. package/fixtures/openapi/NetworkSessionActionInput/valid/restrict.json +5 -0
  256. package/fixtures/openapi/NetworkSessionActionPlan/invalid/missing-hash.json +12 -0
  257. package/fixtures/openapi/NetworkSessionActionPlan/valid/fallback-disconnect.json +13 -0
  258. package/fixtures/openapi/NetworkSessionCommand/invalid/unknown-status.json +14 -0
  259. package/fixtures/openapi/NetworkSessionCommand/valid/pending.json +14 -0
  260. package/fixtures/openapi/NetworkSiteInput/invalid/unknown-status.json +4 -0
  261. package/fixtures/openapi/NetworkSiteInput/valid/shanghai.json +5 -0
  262. package/fixtures/openapi/NetworkSiteProfileBinding/invalid/vlan.json +9 -0
  263. package/fixtures/openapi/NetworkSiteProfileBinding/valid/default.json +10 -0
  264. package/fixtures/openapi/NetworkSiteProfileBindingInput/invalid/timeout.json +7 -0
  265. package/fixtures/openapi/NetworkSiteProfileBindingInput/valid/default.json +7 -0
  266. package/fixtures/openapi/NetworkSpaceInput/invalid/empty-cidrs.json +6 -0
  267. package/fixtures/openapi/NetworkSpaceInput/invalid/malformed-cidr.json +6 -0
  268. package/fixtures/openapi/NetworkSpaceInput/valid/corp.json +6 -0
  269. package/fixtures/openapi/NetworkTelemetrySummary/invalid/raw-destination.json +29 -0
  270. package/fixtures/openapi/NetworkTelemetrySummary/valid/proxy-flow.json +36 -0
  271. package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/auto-with-gateway.json +6 -0
  272. package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/missing-manual-gateway.json +5 -0
  273. package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/mode-override.json +7 -0
  274. package/fixtures/openapi/NetworkVPNConnectionIntentInput/valid/auto-intent.json +5 -0
  275. package/fixtures/openapi/NetworkVPNConnectionIntentInput/valid/manual-intent.json +6 -0
  276. package/fixtures/openapi/NetworkVPNProfileInput/invalid/unknown-private-key.json +25 -0
  277. package/fixtures/openapi/NetworkVPNProfileInput/valid/assigned-profile.json +24 -0
  278. package/fixtures/openapi/NetworkVPNSelectionPolicyInput/invalid/unbounded-attempts.json +21 -0
  279. package/fixtures/openapi/NetworkVPNSelectionPolicyInput/valid/provider-policy.json +21 -0
  280. package/fixtures/openapi/ObservabilityMetricAssessmentInput/invalid/arbitrary-expression.json +10 -0
  281. package/fixtures/openapi/ObservabilityMetricAssessmentInput/valid/metric-window.json +10 -0
  282. package/fixtures/openapi/PluginManifest/valid/hermes-api.json +53 -0
  283. package/fixtures/openapi/ProgressiveRolloutControlInput/invalid/missing-identity.json +5 -0
  284. package/fixtures/openapi/ProgressiveRolloutControlInput/invalid/skip-analysis.json +5 -0
  285. package/fixtures/openapi/ProgressiveRolloutControlInput/valid/pause.json +5 -0
  286. package/fixtures/openapi/ReleaseTargetInput/invalid/missing-required-field.json +15 -0
  287. package/fixtures/openapi/ReleaseTargetInput/valid/docker-compose.json +16 -0
  288. package/fixtures/openapi/RepositoryAnalysis/invalid/moving-ref.json +11 -0
  289. package/fixtures/openapi/RepositoryAnalysis/valid/go.json +29 -0
  290. package/fixtures/openapi/RepositoryAnalysisInput/invalid/arbitrary-source-url.json +6 -0
  291. package/fixtures/openapi/RepositoryAnalysisInput/invalid/missing-ref.json +4 -0
  292. package/fixtures/openapi/RepositoryAnalysisInput/valid/nested-project.json +6 -0
  293. package/fixtures/openapi/ResourceLease/invalid/missing-session.json +10 -0
  294. package/fixtures/openapi/ResourceLease/valid/active.json +11 -0
  295. package/fixtures/openapi/ServiceDeploymentTemplateBinding/invalid/invalid-unpinned.json +4 -0
  296. package/fixtures/openapi/ServiceDeploymentTemplateBinding/valid/valid.json +7 -0
  297. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-executable-schema.json +25 -0
  298. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-missing-schema.json +21 -0
  299. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-mixed-renderers.json +40 -0
  300. package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/valid-nested-parameters.json +41 -0
  301. package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/valid-yaml.json +34 -0
  302. package/fixtures/openapi/TemplatePublishInput/invalid/missing-revision.json +1 -0
  303. package/fixtures/openapi/TemplatePublishInput/invalid/unknown-field.json +4 -0
  304. package/fixtures/openapi/TemplatePublishInput/invalid/zero-revision.json +3 -0
  305. package/fixtures/openapi/TemplatePublishInput/valid/revision.json +3 -0
  306. package/fixtures/openapi/ToolCapability/invalid/bad-idempotency.json +35 -0
  307. package/fixtures/openapi/ToolCapability/invalid/unversioned-check.json +47 -0
  308. package/fixtures/openapi/ToolCapability/valid/checks.json +48 -0
  309. package/fixtures/openapi/ToolCapability/valid/versioned-task.json +35 -0
  310. package/fixtures/openapi/ToolInvocationRequest/invalid/empty-version.json +8 -0
  311. package/fixtures/openapi/ToolInvocationRequest/valid/versioned.json +8 -0
  312. package/fixtures/openapi/ToolInvocationResult/invalid/missing-status-call.json +13 -0
  313. package/fixtures/openapi/ToolInvocationResult/valid/task.json +20 -0
  314. package/fixtures/openapi/VirtualMachineCreateInput/invalid/capacity-type.json +1 -0
  315. package/fixtures/openapi/VirtualMachineCreateInput/valid/capacity.json +1 -0
  316. package/fixtures/openapi/VirtualizationCapacityInput/invalid/missing-disk.json +1 -0
  317. package/fixtures/openapi/VirtualizationCapacityInput/valid/running-vm.json +1 -0
  318. package/fixtures/openapi/VirtualizationCapacityResult/invalid/invented-status.json +1 -0
  319. package/fixtures/openapi/VirtualizationCapacityResult/valid/available.json +1 -0
  320. package/fixtures/openapi/VirtualizationWorkerCreateInput/invalid/secret-input.json +5 -0
  321. package/fixtures/openapi/VirtualizationWorkerCreateInput/valid/one-worker.json +4 -0
  322. package/fixtures/openapi/VirtualizationWorkerPoolInput/invalid/external-owner.json +27 -0
  323. package/fixtures/openapi/VirtualizationWorkerPoolInput/invalid/zero-budget.json +27 -0
  324. package/fixtures/openapi/VirtualizationWorkerPoolInput/valid/registered.json +27 -0
  325. package/fixtures/openapi/VirtualizationWorkerPoolListEnvelope/invalid/nested-data.json +5 -0
  326. package/fixtures/openapi/VirtualizationWorkerPoolListEnvelope/valid/empty.json +3 -0
  327. package/fixtures/openapi/WorkbenchContextSelection/invalid/missing-file-content.json +8 -0
  328. package/fixtures/openapi/WorkbenchContextSelection/invalid/unknown-reference-kind.json +8 -0
  329. package/fixtures/openapi/WorkbenchContextSelection/valid/text-and-resource.json +17 -0
  330. package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/trigger-without-plan.json +10 -0
  331. package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/unknown-trigger.json +28 -0
  332. package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/zero-interval.json +28 -0
  333. package/fixtures/openapi/WorkbenchInspectionTaskInput/valid/alert-capability.json +30 -0
  334. package/fixtures/openapi/WorkbenchInspectionTaskInput/valid/scheduled-capability.json +28 -0
  335. package/fixtures/openapi/WorkbenchInspectionTrigger/invalid/missing-rule.json +3 -0
  336. package/fixtures/openapi/WorkbenchInspectionTrigger/invalid/schedule-alert-selector.json +4 -0
  337. package/fixtures/openapi/WorkbenchInspectionTrigger/valid/schedule.json +3 -0
  338. package/fixtures/openapi/WorkbenchModelPreferences/invalid/unknown-effort.json +1 -0
  339. package/fixtures/openapi/WorkbenchModelPreferences/valid/selected.json +1 -0
  340. package/fixtures/openapi/WorkflowCatalogPage/invalid/unsupported-kind.json +6 -0
  341. package/fixtures/openapi/WorkflowCatalogPage/valid/build-without-environment.json +6 -0
  342. package/fixtures/openapi/WorkflowTemplateInput/invalid/zero-revision.json +6 -0
  343. package/fixtures/openapi/WorkflowTemplateInput/valid/draft.json +10 -0
  344. package/gen/go/sohaapi/client.go +1 -1
  345. package/gen/go/sohaapi/types.go +15610 -6415
  346. package/gen/go/sohaapi/types_test.go +100 -1
  347. package/gen/go/sohaapi/vpn_models_test.go +21 -0
  348. package/gen/ts/sohaapi/index.ts +27859 -17518
  349. package/go.mod +107 -5
  350. package/go.sum +379 -16
  351. package/helmrelease/delivery.go +283 -0
  352. package/helmrelease/delivery_test.go +102 -0
  353. package/helmrelease/frozen_chart.go +91 -0
  354. package/helmrelease/result.go +27 -0
  355. package/helmrelease/runtime/delivery.go +508 -0
  356. package/helmrelease/runtime/delivery_test.go +236 -0
  357. package/helmrelease/runtime/resources_test.go +60 -0
  358. package/helmrelease/values.go +99 -0
  359. package/knowledge/context-envelope.schema.json +1 -0
  360. package/mcp/gateway-manifest.schema.json +387 -72
  361. package/network/README.md +156 -0
  362. package/network/network-ingest-event.schema.json +415 -0
  363. package/network/network-radius-accounting.schema.json +41 -0
  364. package/network/network-runtime-protocol.schema.json +1208 -0
  365. package/network/schema.go +26 -0
  366. package/network/schema_test.go +22 -0
  367. package/openapi/soha-api.json +59260 -35960
  368. package/openapi/soha-api.yaml +36064 -26232
  369. package/openapi/spec_generated.go +3 -3
  370. package/package.json +7 -1
  371. package/resource/manifest_health.go +155 -0
  372. package/resource/manifest_health_test.go +37 -0
  373. package/resource/models.go +14 -0
  374. package/resource/runtime/argocd.go +166 -0
  375. package/resource/runtime/argocd_integration_test.go +142 -0
  376. package/resource/runtime/argocd_observation.go +143 -0
  377. package/resource/runtime/argocd_operation.go +253 -0
  378. package/resource/runtime/argocd_resources.go +115 -0
  379. package/resource/runtime/argocd_resources_test.go +107 -0
  380. package/resource/runtime/argocd_test.go +386 -0
  381. package/resource/runtime/health.go +115 -0
  382. package/resource/runtime/health_test.go +78 -0
  383. package/resource/runtime/operator_integration_test.go +174 -0
  384. package/resource/runtime/ownership.go +178 -0
  385. package/resource/runtime/ownership_test.go +178 -0
  386. package/resource/runtime/rollout_integration_test.go +287 -0
  387. package/resource/runtime/rollout_observation.go +252 -0
  388. package/resource/runtime/rollout_operation.go +398 -0
  389. package/resource/runtime/rollout_resources.go +385 -0
  390. package/resource/runtime/rollout_task.go +165 -0
  391. package/resource/runtime/rollout_test.go +450 -0
@@ -0,0 +1,386 @@
1
+ package runtime
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "errors"
7
+ "strings"
8
+ "testing"
9
+ "time"
10
+
11
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13
+ "k8s.io/apimachinery/pkg/runtime"
14
+ "k8s.io/client-go/dynamic/fake"
15
+ ktesting "k8s.io/client-go/testing"
16
+ )
17
+
18
+ func argoFixture(t *testing.T) (*unstructured.Unstructured, *unstructured.Unstructured) {
19
+ t.Helper()
20
+ decode := func(raw string) *unstructured.Unstructured {
21
+ var object unstructured.Unstructured
22
+ if err := json.Unmarshal([]byte(raw), &object); err != nil {
23
+ t.Fatal(err)
24
+ }
25
+ return &object
26
+ }
27
+ image := "registry.example/app@sha256:" + strings.Repeat("a", 64)
28
+ commit := strings.Repeat("b", 40)
29
+ app := decode(`{"apiVersion":"argoproj.io/v1alpha1","kind":"Application","metadata":{"name":"web","namespace":"demo","uid":"app-uid","resourceVersion":"12"},"spec":{"project":"demo-project","destination":{"server":"https://kubernetes.default.svc","namespace":"demo"},"source":{"repoURL":"https://git.example/team/config.git","targetRevision":"` + commit + `","path":"web","kustomize":{"namespace":"demo","images":["app=` + image + `"]}},"syncPolicy":{"syncOptions":["FailOnSharedResource=true"]}},"status":{"sync":{"status":"Synced","revision":"` + commit + `"},"health":{"status":"Healthy"},"operationState":{"phase":"Succeeded","finishedAt":"2026-09-14T00:00:00Z","syncResult":{"revision":"` + commit + `"},"operation":{"info":[{"name":"delivery.soha.io/argocd-operation","value":"run-1"}]}},"resources":[{"group":"apps","kind":"Deployment","namespace":"demo","name":"web"}]}}`)
30
+ app.SetAnnotations(map[string]string{ArgoOwnerAnnotation: "binding-1", ArgoOperationAnnotation: "run-1"})
31
+ source, _, _ := unstructured.NestedMap(app.Object, "spec", "source")
32
+ _ = unstructured.SetNestedMap(app.Object, source, "status", "sync", "comparedTo", "source")
33
+ deployment := decode(`{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"web","namespace":"demo","uid":"workload-uid","generation":3,"annotations":{"argocd.argoproj.io/tracking-id":"web:apps/Deployment:demo/web"}},"spec":{"replicas":1,"template":{"spec":{"containers":[{"name":"app","image":"` + image + `"}]}}},"status":{"observedGeneration":3,"replicas":1,"updatedReplicas":1,"availableReplicas":1}}`)
34
+ return app, deployment
35
+ }
36
+
37
+ func argoProjectFixture() *unstructured.Unstructured {
38
+ return &unstructured.Unstructured{Object: map[string]any{"apiVersion": "argoproj.io/v1alpha1", "kind": "AppProject", "metadata": map[string]any{"name": "demo-project", "namespace": "demo"}, "spec": map[string]any{"sourceRepos": []any{"https://git.example/team/config.git"}, "destinations": []any{map[string]any{"namespace": "demo", "server": "https://kubernetes.default.svc"}}, "namespaceResourceWhitelist": []any{map[string]any{"group": "apps", "kind": "Deployment"}}}}}
39
+ }
40
+
41
+ func TestArgoImageOverrideForms(t *testing.T) {
42
+ image := "registry.example:5000/app@sha256:" + strings.Repeat("a", 64)
43
+ for _, value := range []string{image, "app=" + image} {
44
+ name, parsed, err := ParseArgoImageOverride(value)
45
+ if err != nil || parsed != image || name != "app" && name != "registry.example:5000/app" {
46
+ t.Fatalf("override %q: %q %q %v", value, name, parsed, err)
47
+ }
48
+ }
49
+ for _, value := range []string{"= " + image, "app:latest", "registry/app:tag@sha256:" + strings.Repeat("a", 64), image + "extra", "app=" + image + "=other"} {
50
+ if _, _, err := ParseArgoImageOverride(value); err == nil {
51
+ t.Fatalf("invalid override accepted: %s", value)
52
+ }
53
+ }
54
+ app, child := argoFixture(t)
55
+ plain := "registry.example/app@sha256:" + strings.Repeat("a", 64)
56
+ _ = unstructured.SetNestedStringSlice(app.Object, []string{plain}, "spec", "source", "kustomize", "images")
57
+ if err := ValidateArgoResources(app, []*unstructured.Unstructured{argoDesiredFixture(child)}); err != nil {
58
+ t.Fatal(err)
59
+ }
60
+ _ = unstructured.SetNestedStringSlice(app.Object, []string{plain, "registry.example/app=" + plain}, "spec", "source", "kustomize", "images")
61
+ if err := ValidateArgoApplication(app); err == nil {
62
+ t.Fatal("duplicate plain and aliased image name accepted")
63
+ }
64
+ }
65
+
66
+ func TestArgoOperationReplayAndOwnershipFences(t *testing.T) {
67
+ for _, mode := range []string{"replay", "dry run", "other owner", "running", "unrestricted project"} {
68
+ t.Run(mode, func(t *testing.T) {
69
+ live, child := argoFixture(t)
70
+ children := []*unstructured.Unstructured{argoDesiredFixture(child)}
71
+ desired := live.DeepCopy()
72
+ delete(desired.Object, "status")
73
+ desired.SetUID("")
74
+ desired.SetResourceVersion("")
75
+ project := argoProjectFixture()
76
+ opID := "run-2"
77
+ options := metav1.PatchOptions{FieldManager: "binding-1"}
78
+ switch mode {
79
+ case "replay":
80
+ opID = "run-1"
81
+ case "dry run":
82
+ options.DryRun = []string{metav1.DryRunAll}
83
+ case "other owner":
84
+ live.SetAnnotations(map[string]string{ArgoOwnerAnnotation: "other"})
85
+ case "running":
86
+ setArgoOperation(live, "binding-1", "other-run")
87
+ case "unrestricted project":
88
+ _ = unstructured.SetNestedSlice(project.Object, []any{map[string]any{"group": "*", "kind": "*"}}, "spec", "clusterResourceWhitelist")
89
+ }
90
+ client := fake.NewSimpleDynamicClient(runtime.NewScheme(), live, project)
91
+ writes := 0
92
+ client.PrependReactor("update", "applications", func(action ktesting.Action) (bool, runtime.Object, error) {
93
+ writes++
94
+ update, ok := action.(ktesting.UpdateAction)
95
+ if !ok {
96
+ t.Fatal("unexpected update action")
97
+ }
98
+ write, ok := update.GetObject().(*unstructured.Unstructured)
99
+ if !ok {
100
+ t.Fatal("unexpected update object")
101
+ }
102
+ if write.GetUID() != live.GetUID() || write.GetResourceVersion() != live.GetResourceVersion() || argoOperationID(write, "operation") != opID || argoString(write, "operation", "sync", "revision") != argoString(desired, "spec", "source", "targetRevision") {
103
+ t.Fatal("write lost operation identity or optimistic lock")
104
+ }
105
+ updateOptions, ok := action.(interface{ GetUpdateOptions() metav1.UpdateOptions })
106
+ if !ok {
107
+ t.Fatal("unexpected update options")
108
+ }
109
+ if len(updateOptions.GetUpdateOptions().DryRun) != 1 {
110
+ t.Fatal("preflight lost dry-run")
111
+ }
112
+ return true, write, nil
113
+ })
114
+ ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
115
+ defer cancel()
116
+ _, err := ApplyArgoApplication(ctx, client, desired, children, options, opID)
117
+ if mode == "replay" || mode == "dry run" {
118
+ if err != nil {
119
+ t.Fatal(err)
120
+ }
121
+ } else if err == nil {
122
+ t.Fatal("unsafe sync accepted")
123
+ }
124
+ if mode == "dry run" && writes != 1 || mode != "dry run" && writes != 0 {
125
+ t.Fatalf("unexpected writes %d", writes)
126
+ }
127
+ })
128
+ }
129
+ }
130
+
131
+ func TestArgoStopWaitsForControllerAndDoesNotStopReplacement(t *testing.T) {
132
+ for _, mode := range []string{"confirmed", "replacement", "controller error"} {
133
+ t.Run(mode, func(t *testing.T) {
134
+ initial, _ := argoFixture(t)
135
+ setArgoOperation(initial, "binding-1", "run-1")
136
+ _ = unstructured.SetNestedField(initial.Object, "Running", "status", "operationState", "phase")
137
+ live := initial.DeepCopy()
138
+ if mode == "replacement" {
139
+ live.SetUID("replacement")
140
+ }
141
+ client := fake.NewSimpleDynamicClient(runtime.NewScheme(), live)
142
+ writes := 0
143
+ client.PrependReactor("update", "applications", func(action ktesting.Action) (bool, runtime.Object, error) {
144
+ writes++
145
+ update, ok := action.(ktesting.UpdateAction)
146
+ if !ok {
147
+ t.Fatal("unexpected update action")
148
+ }
149
+ write, ok := update.GetObject().(*unstructured.Unstructured)
150
+ if !ok {
151
+ t.Fatal("unexpected update object")
152
+ }
153
+ if write.GetUID() != initial.GetUID() || write.GetResourceVersion() != initial.GetResourceVersion() || argoString(write, "status", "operationState", "phase") != "Terminating" || argoOperationID(write, "operation") != "run-1" {
154
+ t.Fatal("termination lost native phase or identity fences")
155
+ }
156
+ delete(write.Object, "operation")
157
+ phase := "Failed"
158
+ if mode == "controller error" {
159
+ phase = "Error"
160
+ }
161
+ _ = unstructured.SetNestedField(write.Object, phase, "status", "operationState", "phase")
162
+ return true, write, client.Tracker().Update(argoApplications, write, "demo")
163
+ })
164
+ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
165
+ defer cancel()
166
+ _, err := terminateArgoOperation(ctx, client.Resource(argoApplications).Namespace("demo"), initial, "run-1")
167
+ if mode == "confirmed" && err != nil || mode != "confirmed" && err == nil {
168
+ t.Fatalf("termination=%v", err)
169
+ }
170
+ if mode == "replacement" && writes != 0 || mode != "replacement" && writes != 1 {
171
+ t.Fatalf("termination writes=%d", writes)
172
+ }
173
+ })
174
+ }
175
+ app, _ := argoFixture(t)
176
+ client := fake.NewSimpleDynamicClient(runtime.NewScheme(), app)
177
+ client.PrependReactor("get", "applications", func(ktesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("API unavailable") })
178
+ ctx, cancel := context.WithCancel(context.Background())
179
+ cancel()
180
+ _, err := waitArgoOperation(ctx, client.Resource(argoApplications).Namespace("demo"), app, "run-1")
181
+ if !errors.Is(err, ErrArgoStopUnconfirmed) || errors.Is(err, context.Canceled) {
182
+ t.Fatalf("unconfirmed stop was acknowledged: %v", err)
183
+ }
184
+ }
185
+
186
+ func TestArgoApplicationRejectsMutableOrUnscopedInputs(t *testing.T) {
187
+ for _, test := range []struct {
188
+ name string
189
+ change func(*unstructured.Unstructured)
190
+ }{
191
+ {"branch", func(app *unstructured.Unstructured) {
192
+ _ = unstructured.SetNestedField(app.Object, "main", "spec", "source", "targetRevision")
193
+ }},
194
+ {"other namespace", func(app *unstructured.Unstructured) {
195
+ _ = unstructured.SetNestedField(app.Object, "production", "spec", "destination", "namespace")
196
+ }},
197
+ {"implicit kustomize namespace", func(app *unstructured.Unstructured) {
198
+ unstructured.RemoveNestedField(app.Object, "spec", "source", "kustomize", "namespace")
199
+ }},
200
+ {"duplicate image mapping", func(app *unstructured.Unstructured) {
201
+ images, _, _ := unstructured.NestedStringSlice(app.Object, "spec", "source", "kustomize", "images")
202
+ _ = unstructured.SetNestedStringSlice(app.Object, append(images, images[0]), "spec", "source", "kustomize", "images")
203
+ }},
204
+ {"external cluster", func(app *unstructured.Unstructured) {
205
+ _ = unstructured.SetNestedField(app.Object, "https://external", "spec", "destination", "server")
206
+ }},
207
+ {"default project", func(app *unstructured.Unstructured) {
208
+ _ = unstructured.SetNestedField(app.Object, "default", "spec", "project")
209
+ }},
210
+ {"automated", func(app *unstructured.Unstructured) {
211
+ _ = unstructured.SetNestedMap(app.Object, map[string]any{}, "spec", "syncPolicy", "automated")
212
+ }},
213
+ {"mutable image", func(app *unstructured.Unstructured) {
214
+ _ = unstructured.SetNestedStringSlice(app.Object, []string{"app=registry.example/app:latest"}, "spec", "source", "kustomize", "images")
215
+ }},
216
+ {"repository credentials", func(app *unstructured.Unstructured) {
217
+ _ = unstructured.SetNestedField(app.Object, "https://token@git.example/config.git", "spec", "source", "repoURL")
218
+ }},
219
+ {"path traversal", func(app *unstructured.Unstructured) {
220
+ _ = unstructured.SetNestedField(app.Object, "../outside", "spec", "source", "path")
221
+ }},
222
+ {"plugin", func(app *unstructured.Unstructured) {
223
+ _ = unstructured.SetNestedMap(app.Object, map[string]any{}, "spec", "source", "plugin")
224
+ }},
225
+ {"ignored drift", func(app *unstructured.Unstructured) {
226
+ _ = unstructured.SetNestedSlice(app.Object, []any{}, "spec", "ignoreDifferences")
227
+ }},
228
+ {"cascade", func(app *unstructured.Unstructured) {
229
+ app.SetFinalizers([]string{"resources-finalizer.argocd.argoproj.io"})
230
+ }},
231
+ } {
232
+ t.Run(test.name, func(t *testing.T) {
233
+ app, _ := argoFixture(t)
234
+ if err := ValidateArgoApplication(app); err != nil {
235
+ t.Fatal(err)
236
+ }
237
+ test.change(app)
238
+ if ValidateArgoApplication(app) == nil {
239
+ t.Fatal("unsafe GitOps definition accepted")
240
+ }
241
+ })
242
+ }
243
+ }
244
+
245
+ func TestArgoHealthRequiresCurrentOperationAndLiveOwnedDigest(t *testing.T) {
246
+ for _, mode := range []string{"healthy", "old operation", "old commit", "old source", "running", "failed", "resource missing", "other owner", "old generation", "wrong image", "deleting", "extra container", "extra init", "extra resource", "wrong root owner", "spec drift", "progress inventory"} {
247
+ t.Run(mode, func(t *testing.T) {
248
+ app, workload := argoFixture(t)
249
+ desired := argoDesiredFixture(app)
250
+ children := []*unstructured.Unstructured{argoDesiredFixture(workload)}
251
+ switch mode {
252
+ case "extra container":
253
+ containers, _, _ := unstructured.NestedSlice(workload.Object, "spec", "template", "spec", "containers")
254
+ _ = unstructured.SetNestedSlice(workload.Object, append(containers, containers[0]), "spec", "template", "spec", "containers")
255
+ case "extra init":
256
+ containers, _, _ := unstructured.NestedSlice(workload.Object, "spec", "template", "spec", "containers")
257
+ _ = unstructured.SetNestedSlice(workload.Object, containers, "spec", "template", "spec", "initContainers")
258
+ case "extra resource":
259
+ resources, _, _ := unstructured.NestedSlice(app.Object, "status", "resources")
260
+ _ = unstructured.SetNestedSlice(app.Object, append(resources, map[string]any{"group": "", "kind": "ConfigMap", "name": "outside", "namespace": "demo"}), "status", "resources")
261
+ case "wrong root owner":
262
+ app.SetAnnotations(map[string]string{ArgoOwnerAnnotation: "other", ArgoOperationAnnotation: "run-1"})
263
+ case "spec drift":
264
+ _ = unstructured.SetNestedField(workload.Object, int64(2), "spec", "replicas")
265
+ case "progress inventory":
266
+ delete(app.Object, "status")
267
+ case "old operation":
268
+ annotations := app.GetAnnotations()
269
+ annotations[ArgoOperationAnnotation] = "new-run"
270
+ app.SetAnnotations(annotations)
271
+ case "old commit":
272
+ _ = unstructured.SetNestedField(app.Object, strings.Repeat("c", 40), "status", "sync", "revision")
273
+ case "old source":
274
+ _ = unstructured.SetNestedField(app.Object, "other", "status", "sync", "comparedTo", "source", "path")
275
+ case "running":
276
+ _ = unstructured.SetNestedField(app.Object, "Running", "status", "operationState", "phase")
277
+ case "failed":
278
+ _ = unstructured.SetNestedField(app.Object, "Failed", "status", "operationState", "phase")
279
+ case "other owner":
280
+ workload.SetAnnotations(map[string]string{"argocd.argoproj.io/tracking-id": "other:apps/Deployment:demo/web"})
281
+ case "old generation":
282
+ workload.SetGeneration(4)
283
+ case "wrong image":
284
+ _ = unstructured.SetNestedSlice(workload.Object, []any{map[string]any{"name": "app", "image": "registry.example/app:latest"}}, "spec", "template", "spec", "containers")
285
+ case "deleting":
286
+ now := metav1.Now()
287
+ workload.SetDeletionTimestamp(&now)
288
+ }
289
+ objects := []runtime.Object{workload}
290
+ if mode == "resource missing" {
291
+ objects = nil
292
+ }
293
+ client := fake.NewSimpleDynamicClient(runtime.NewScheme(), objects...)
294
+ observed, err := ObserveArgoApplication(context.Background(), client, desired, app, children, "binding-1")
295
+ if mode == "healthy" {
296
+ if err != nil || observed.Health != "healthy" || len(observed.Resources) != 1 || observed.Resources[0].GetUID() != "workload-uid" {
297
+ t.Fatalf("observation=%+v err=%v", observed, err)
298
+ }
299
+ } else if observed.Health == "healthy" {
300
+ t.Fatalf("false healthy: %+v", observed)
301
+ }
302
+ if mode == "progress inventory" && len(observed.Resources) != 1 {
303
+ t.Fatal("progress lost child inventory")
304
+ }
305
+ for _, action := range client.Actions() {
306
+ if action.GetVerb() != "get" {
307
+ t.Fatal("observation wrote to cluster")
308
+ }
309
+ }
310
+ })
311
+ }
312
+ }
313
+
314
+ func argoDesiredFixture(live *unstructured.Unstructured) *unstructured.Unstructured {
315
+ desired := live.DeepCopy()
316
+ desired.SetUID("")
317
+ desired.SetResourceVersion("")
318
+ desired.SetAnnotations(nil)
319
+ unstructured.RemoveNestedField(desired.Object, "metadata", "generation")
320
+ delete(desired.Object, "status")
321
+ return desired
322
+ }
323
+
324
+ func TestArgoAmbiguousWriteAndObservationFailureConfirmStop(t *testing.T) {
325
+ for _, mode := range []string{"lost write response", "lost observation"} {
326
+ t.Run(mode, func(t *testing.T) {
327
+ live, workload := argoFixture(t)
328
+ desired := argoDesiredFixture(live)
329
+ children := []*unstructured.Unstructured{argoDesiredFixture(workload)}
330
+ setArgoOperation(live, "binding-1", "run-2")
331
+ _ = unstructured.SetNestedField(live.Object, "Running", "status", "operationState", "phase")
332
+ _ = unstructured.SetNestedSlice(live.Object, []any{map[string]any{"name": ArgoOperationAnnotation, "value": "run-2"}}, "status", "operationState", "operation", "info")
333
+ client := fake.NewSimpleDynamicClient(runtime.NewScheme(), argoProjectFixture())
334
+ stops := 0
335
+ client.PrependReactor("update", "applications", func(action ktesting.Action) (bool, runtime.Object, error) {
336
+ update, ok := action.(ktesting.UpdateAction)
337
+ if !ok {
338
+ t.Fatal("unexpected update action")
339
+ }
340
+ write, ok := update.GetObject().(*unstructured.Unstructured)
341
+ if !ok {
342
+ t.Fatal("unexpected update object")
343
+ }
344
+ if argoString(write, "status", "operationState", "phase") != "Terminating" {
345
+ t.Fatal("missing stop request")
346
+ }
347
+ stops++
348
+ delete(write.Object, "operation")
349
+ _ = unstructured.SetNestedField(write.Object, "Failed", "status", "operationState", "phase")
350
+ return true, write, client.Tracker().Update(argoApplications, write, "demo")
351
+ })
352
+ ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
353
+ defer cancel()
354
+ var err error
355
+ if mode == "lost write response" {
356
+ client.PrependReactor("create", "applications", func(ktesting.Action) (bool, runtime.Object, error) {
357
+ if err := client.Tracker().Create(argoApplications, live, "demo"); err != nil {
358
+ t.Fatal(err)
359
+ }
360
+ return true, nil, errors.New("write response lost")
361
+ })
362
+ _, err = ApplyArgoApplication(ctx, client, desired, children, metav1.PatchOptions{FieldManager: "binding-1"}, "run-2")
363
+ } else {
364
+ if err := client.Tracker().Create(argoApplications, live, "demo"); err != nil {
365
+ t.Fatal(err)
366
+ }
367
+ reads := 0
368
+ client.PrependReactor("get", "applications", func(ktesting.Action) (bool, runtime.Object, error) {
369
+ reads++
370
+ if reads == 1 {
371
+ return true, nil, errors.New("observation unavailable")
372
+ }
373
+ return false, nil, nil
374
+ })
375
+ _, err = waitArgoOperation(ctx, client.Resource(argoApplications).Namespace("demo"), live, "run-2")
376
+ }
377
+ if err == nil || errors.Is(err, ErrArgoStopUnconfirmed) || stops != 1 {
378
+ t.Fatalf("stop=%d error=%v", stops, err)
379
+ }
380
+ actual, err := client.Resource(argoApplications).Namespace("demo").Get(ctx, live.GetName(), metav1.GetOptions{})
381
+ if err != nil || argoOperationActive(actual) {
382
+ t.Fatal("failed task left an active sync")
383
+ }
384
+ })
385
+ }
386
+ }
@@ -0,0 +1,115 @@
1
+ package runtime
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "time"
7
+
8
+ contractresource "github.com/opensoha/soha-contracts/resource"
9
+ corev1 "k8s.io/api/core/v1"
10
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
11
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13
+ "k8s.io/apimachinery/pkg/runtime"
14
+ "k8s.io/apimachinery/pkg/runtime/schema"
15
+ "k8s.io/client-go/dynamic"
16
+ )
17
+
18
+ // ManifestHealth verifies the supported Operator's references with bounded,
19
+ // same-namespace reads. Healthy means its configuration is synchronized, not
20
+ // that a scheduled Job has executed successfully.
21
+ func ManifestHealth(ctx context.Context, client dynamic.Interface, object *unstructured.Unstructured) (string, error) {
22
+ if object == nil {
23
+ return "unknown", nil
24
+ }
25
+ if object.GetDeletionTimestamp() != nil || object.GetAPIVersion() != "workloads.soha.io/v1alpha1" || object.GetKind() != "WorkloadCronJob" {
26
+ return contractresource.ManifestHealth(object.Object), nil
27
+ }
28
+ var item workloadCronJob
29
+ if err := runtime.DefaultUnstructuredConverter.FromUnstructured(object.Object, &item); err != nil {
30
+ return "unknown", fmt.Errorf("decode WorkloadCronJob health: %w", err)
31
+ }
32
+ if health := item.conditionHealth(); health != "healthy" {
33
+ return health, nil
34
+ }
35
+ if client == nil || !item.validReferences() {
36
+ return "unknown", nil
37
+ }
38
+ queryCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
39
+ defer cancel()
40
+ sourceResource := map[string]string{"Deployment": "deployments", "StatefulSet": "statefulsets", "DaemonSet": "daemonsets"}[item.Spec.SourceRef.Kind]
41
+ source, err := client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: sourceResource}).Namespace(item.Namespace).Get(queryCtx, item.Spec.SourceRef.Name, metav1.GetOptions{})
42
+ if apierrors.IsNotFound(err) {
43
+ return "degraded", nil
44
+ }
45
+ if err != nil {
46
+ return "unknown", fmt.Errorf("observe WorkloadCronJob source: %w", err)
47
+ }
48
+ target, err := client.Resource(schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "cronjobs"}).Namespace(item.Namespace).Get(queryCtx, item.Name, metav1.GetOptions{})
49
+ if apierrors.IsNotFound(err) {
50
+ return "progressing", nil
51
+ }
52
+ if err != nil {
53
+ return "unknown", fmt.Errorf("observe WorkloadCronJob target: %w", err)
54
+ }
55
+ return item.relatedHealth(source, target), nil
56
+ }
57
+
58
+ type workloadCronJob struct {
59
+ metav1.ObjectMeta `json:"metadata"`
60
+ Spec struct {
61
+ SourceRef struct {
62
+ Kind string `json:"kind"`
63
+ Name string `json:"name"`
64
+ } `json:"sourceRef"`
65
+ } `json:"spec"`
66
+ Status struct {
67
+ ObservedGeneration int64 `json:"observedGeneration"`
68
+ SourceUID string `json:"sourceUid"`
69
+ SourceResourceVersion string `json:"sourceResourceVersion"`
70
+ CronJobRef corev1.ObjectReference `json:"cronJobRef"`
71
+ Conditions []metav1.Condition `json:"conditions"`
72
+ } `json:"status"`
73
+ }
74
+
75
+ func (item workloadCronJob) conditionHealth() string {
76
+ if item.Generation < 1 || item.Status.ObservedGeneration != item.Generation {
77
+ return "progressing"
78
+ }
79
+ for _, condition := range item.Status.Conditions {
80
+ if condition.Type != "Ready" || condition.ObservedGeneration != item.Generation {
81
+ continue
82
+ }
83
+ if condition.Status == metav1.ConditionFalse {
84
+ return "degraded"
85
+ }
86
+ if condition.Status == metav1.ConditionTrue && condition.Reason == "Reconciled" {
87
+ return "healthy"
88
+ }
89
+ }
90
+ return "unknown"
91
+ }
92
+
93
+ func (item workloadCronJob) validReferences() bool {
94
+ source := item.Spec.SourceRef
95
+ target := item.Status.CronJobRef
96
+ validKind := source.Kind == "Deployment" || source.Kind == "StatefulSet" || source.Kind == "DaemonSet"
97
+ return validKind && item.UID != "" && item.Namespace != "" && item.Name != "" && source.Name != "" &&
98
+ item.Status.SourceUID != "" && item.Status.SourceResourceVersion != "" && target.UID != "" && target.ResourceVersion != "" &&
99
+ target.APIVersion == "batch/v1" && target.Kind == "CronJob" && target.Namespace == item.Namespace && target.Name == item.Name
100
+ }
101
+
102
+ func (item workloadCronJob) relatedHealth(source, target *unstructured.Unstructured) string {
103
+ if source.GetDeletionTimestamp() != nil || target.GetDeletionTimestamp() != nil ||
104
+ string(source.GetUID()) != item.Status.SourceUID || target.GetUID() != item.Status.CronJobRef.UID {
105
+ return "degraded"
106
+ }
107
+ owner := metav1.GetControllerOf(target)
108
+ if owner == nil || owner.UID != item.UID || owner.Name != item.Name || owner.APIVersion != "workloads.soha.io/v1alpha1" || owner.Kind != "WorkloadCronJob" {
109
+ return "degraded"
110
+ }
111
+ if source.GetResourceVersion() != item.Status.SourceResourceVersion || target.GetResourceVersion() != item.Status.CronJobRef.ResourceVersion {
112
+ return "progressing"
113
+ }
114
+ return "healthy"
115
+ }
@@ -0,0 +1,78 @@
1
+ package runtime
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "testing"
7
+
8
+ contractresource "github.com/opensoha/soha-contracts/resource"
9
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11
+ "k8s.io/apimachinery/pkg/runtime"
12
+ "k8s.io/apimachinery/pkg/types"
13
+ "k8s.io/client-go/dynamic/fake"
14
+ )
15
+
16
+ func TestWorkloadCronJobHealthRequiresCurrentOwnedResources(t *testing.T) {
17
+ for _, test := range []struct {
18
+ name string
19
+ change func(root, source, target *unstructured.Unstructured)
20
+ want string
21
+ }{
22
+ {"synchronized configuration", func(_, _, _ *unstructured.Unstructured) {}, "healthy"},
23
+ {"old CR generation", func(root, _, _ *unstructured.Unstructured) { root.SetGeneration(3) }, "progressing"},
24
+ {"old source version", func(_, source, _ *unstructured.Unstructured) { source.SetResourceVersion("12") }, "progressing"},
25
+ {"unobserved target changes", func(_, _, target *unstructured.Unstructured) { target.SetResourceVersion("22") }, "progressing"},
26
+ {"replaced source", func(_, source, _ *unstructured.Unstructured) { source.SetUID(types.UID("new-source")) }, "degraded"},
27
+ {"replaced target", func(_, _, target *unstructured.Unstructured) { target.SetUID(types.UID("new-target")) }, "degraded"},
28
+ {"foreign controller", func(_, _, target *unstructured.Unstructured) { target.SetOwnerReferences(nil) }, "degraded"},
29
+ {"deleting target", func(_, _, target *unstructured.Unstructured) { now := metav1.Now(); target.SetDeletionTimestamp(&now) }, "degraded"},
30
+ {"deleting root", func(root, _, _ *unstructured.Unstructured) { now := metav1.Now(); root.SetDeletionTimestamp(&now) }, "degraded"},
31
+ {"missing controller evidence", func(root, _, _ *unstructured.Unstructured) {
32
+ unstructured.RemoveNestedField(root.Object, "status", "cronJobRef", "resourceVersion")
33
+ }, "unknown"},
34
+ {"cross namespace reference", func(root, _, _ *unstructured.Unstructured) {
35
+ _ = unstructured.SetNestedField(root.Object, "other", "status", "cronJobRef", "namespace")
36
+ }, "unknown"},
37
+ {"unsupported source", func(root, _, _ *unstructured.Unstructured) {
38
+ _ = unstructured.SetNestedField(root.Object, "Secret", "spec", "sourceRef", "kind")
39
+ }, "unknown"},
40
+ {"controller failure", func(root, _, _ *unstructured.Unstructured) {
41
+ _ = unstructured.SetNestedSlice(root.Object, []any{map[string]any{"type": "Ready", "status": "False", "reason": "SourceNotFound", "observedGeneration": int64(2)}}, "status", "conditions")
42
+ }, "degraded"},
43
+ } {
44
+ t.Run(test.name, func(t *testing.T) {
45
+ root, source, target := cronJobHealthFixture(t)
46
+ test.change(root, source, target)
47
+ client := fake.NewSimpleDynamicClient(runtime.NewScheme(), source, target)
48
+ got, err := ManifestHealth(context.Background(), client, root)
49
+ if err != nil || got != test.want {
50
+ t.Fatalf("health = %s, %v; want %s", got, err, test.want)
51
+ }
52
+ for _, action := range client.Actions() {
53
+ if action.GetVerb() != "get" || action.GetNamespace() != "demo" {
54
+ t.Fatalf("unexpected action: %#v", action)
55
+ }
56
+ }
57
+ })
58
+ }
59
+ root, _, _ := cronJobHealthFixture(t)
60
+ if got := contractresource.ManifestHealth(root.Object); got != "unknown" {
61
+ t.Fatalf("root condition alone must not prove health: %s", got)
62
+ }
63
+ }
64
+
65
+ func cronJobHealthFixture(t *testing.T) (*unstructured.Unstructured, *unstructured.Unstructured, *unstructured.Unstructured) {
66
+ t.Helper()
67
+ decode := func(raw string) *unstructured.Unstructured {
68
+ var object map[string]any
69
+ if err := json.Unmarshal([]byte(raw), &object); err != nil {
70
+ t.Fatal(err)
71
+ }
72
+ return &unstructured.Unstructured{Object: object}
73
+ }
74
+ root := decode(`{"apiVersion":"workloads.soha.io/v1alpha1","kind":"WorkloadCronJob","metadata":{"name":"daily","namespace":"demo","uid":"root-uid","generation":2},"spec":{"sourceRef":{"kind":"Deployment","name":"web"}},"status":{"observedGeneration":2,"sourceUid":"source-uid","sourceResourceVersion":"11","cronJobRef":{"apiVersion":"batch/v1","kind":"CronJob","namespace":"demo","name":"daily","uid":"target-uid","resourceVersion":"21"},"conditions":[{"type":"Ready","status":"True","reason":"Reconciled","observedGeneration":2}]}}`)
75
+ source := decode(`{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"web","namespace":"demo","uid":"source-uid","resourceVersion":"11"}}`)
76
+ target := decode(`{"apiVersion":"batch/v1","kind":"CronJob","metadata":{"name":"daily","namespace":"demo","uid":"target-uid","resourceVersion":"21","ownerReferences":[{"apiVersion":"workloads.soha.io/v1alpha1","kind":"WorkloadCronJob","name":"daily","uid":"root-uid","controller":true}]}}`)
77
+ return root, source, target
78
+ }