@opensoha/contracts 0.1.17 → 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 (268) hide show
  1. package/COMPATIBILITY.md +45 -0
  2. package/README.md +37 -0
  3. package/auth/catalog_generated.go +2 -2
  4. package/auth/outpost_headers.go +17 -0
  5. package/auth/outpost_headers_test.go +16 -0
  6. package/auth/permission-catalog.json +383 -1
  7. package/compat/consumer-revisions.json +3 -0
  8. package/compat/json-schema-ecosystem-0.1.0.json +1 -1
  9. package/compat/openapi-loose-boundaries-0.1.0.json +78 -46
  10. package/delivery/canonical.go +136 -0
  11. package/delivery/document.go +203 -0
  12. package/delivery/document.schema.json +1527 -0
  13. package/delivery/document_test.go +191 -0
  14. package/delivery/parser.go +155 -0
  15. package/evaluation/evaluation-run.schema.json +64 -10
  16. package/fixtures/json-schema/gateway-manifest/invalid/bad-execution-mode.json +73 -0
  17. package/fixtures/json-schema/gateway-manifest/invalid/unversioned-check.json +85 -0
  18. package/fixtures/json-schema/gateway-manifest/valid/checks.json +86 -0
  19. package/fixtures/json-schema/gateway-manifest/valid/versioned-task.json +73 -0
  20. package/fixtures/json-schema/network-ingest-event/invalid/vpn-probe-negative-rtt.json +34 -0
  21. package/fixtures/json-schema/network-ingest-event/invalid/vpn-stats-secret.json +27 -0
  22. package/fixtures/json-schema/network-ingest-event/valid/vpn-probe-batch.json +45 -0
  23. package/fixtures/json-schema/network-ingest-event/valid/vpn-tunnel-stats.json +26 -0
  24. package/fixtures/json-schema/network-runtime-protocol/invalid/vpn-managed-bad-token.json +15 -0
  25. package/fixtures/json-schema/network-runtime-protocol/invalid/vpn-managed-mode-override.json +16 -0
  26. package/fixtures/json-schema/network-runtime-protocol/valid/managed-vpn-macos-enrollment.json +27 -0
  27. package/fixtures/json-schema/network-runtime-protocol/valid/vpn-managed-connect.json +15 -0
  28. package/fixtures/openapi/AgentChatInput/invalid/empty-question.json +4 -0
  29. package/fixtures/openapi/AgentChatInput/invalid/system-role.json +9 -0
  30. package/fixtures/openapi/AgentChatInput/valid/authorized-background.json +5 -0
  31. package/fixtures/openapi/AgentChatInput/valid/conversation.json +15 -0
  32. package/fixtures/openapi/AgentRunnerToolCallRequest/invalid/model-identity.json +1 -0
  33. package/fixtures/openapi/AgentRunnerToolCallRequest/valid/search.json +1 -0
  34. package/fixtures/openapi/ApplicationInput/invalid/negative-version.json +6 -0
  35. package/fixtures/openapi/ApplicationInput/invalid/zero-version.json +6 -0
  36. package/fixtures/openapi/ApplicationInput/valid/versioned.json +6 -0
  37. package/fixtures/openapi/ApprovalRequest/invalid/resolved-scopes.json +20 -0
  38. package/fixtures/openapi/ApprovalRequest/valid/resolved-scopes.json +21 -0
  39. package/fixtures/openapi/BuildTemplateInput/invalid/negative-revision.json +6 -0
  40. package/fixtures/openapi/BuildTemplateInput/valid/draft.json +11 -0
  41. package/fixtures/openapi/BuildTriggerRequest/invalid/no-ref.json +1 -0
  42. package/fixtures/openapi/BuildTriggerRequest/valid/no-environment.json +1 -0
  43. package/fixtures/openapi/BuildpacksConfiguration/invalid/mutable-builder.json +5 -0
  44. package/fixtures/openapi/BuildpacksConfiguration/invalid/privilege-override.json +6 -0
  45. package/fixtures/openapi/BuildpacksConfiguration/invalid/unsupported-platform.json +5 -0
  46. package/fixtures/openapi/BuildpacksConfiguration/valid/pinned.json +5 -0
  47. package/fixtures/openapi/CapabilityAssessment/invalid/contract.json +16 -0
  48. package/fixtures/openapi/CapabilityAssessment/valid/contract.json +16 -0
  49. package/fixtures/openapi/CapabilityPlan/invalid/contract.json +16 -0
  50. package/fixtures/openapi/CapabilityPlan/valid/contract.json +19 -0
  51. package/fixtures/openapi/CapabilityPlanValidation/invalid/contract.json +5 -0
  52. package/fixtures/openapi/CapabilityPlanValidation/valid/contract.json +11 -0
  53. package/fixtures/openapi/CapabilityTask/invalid/contract.json +33 -0
  54. package/fixtures/openapi/CapabilityTask/valid/contract.json +34 -0
  55. package/fixtures/openapi/CapabilityTaskInput/invalid/contract.json +22 -0
  56. package/fixtures/openapi/CapabilityTaskInput/valid/contract.json +22 -0
  57. package/fixtures/openapi/CapabilityValueSemantic/invalid/contract.json +4 -0
  58. package/fixtures/openapi/CapabilityValueSemantic/valid/contract.json +7 -0
  59. package/fixtures/openapi/DeliveryAccessResult/invalid/release-verification.json +8 -0
  60. package/fixtures/openapi/DeliveryAccessResult/valid/release-verification.json +8 -0
  61. package/fixtures/openapi/DeliveryBatchAssessment/invalid/release-verification.json +11 -0
  62. package/fixtures/openapi/DeliveryBatchAssessment/valid/release-verification.json +11 -0
  63. package/fixtures/openapi/DeliveryBatchAssessmentInput/invalid/release-verification.json +9 -0
  64. package/fixtures/openapi/DeliveryBatchAssessmentInput/valid/release-verification.json +8 -0
  65. package/fixtures/openapi/DeliveryBatchInput/invalid/ambiguous-source.json +19 -0
  66. package/fixtures/openapi/DeliveryBatchInput/invalid/missing-idempotency.json +16 -0
  67. package/fixtures/openapi/DeliveryBatchInput/valid/quick-deploy.json +17 -0
  68. package/fixtures/openapi/DeliveryBatchInput/valid/saved-workflow.json +5 -0
  69. package/fixtures/openapi/DeliveryBlueprintInput/invalid/unversioned-preset.json +31 -0
  70. package/fixtures/openapi/DeliveryBlueprintInput/valid/service-preset.json +32 -0
  71. package/fixtures/openapi/DeliveryDocument/invalid/unknown-field.json +24 -0
  72. package/fixtures/openapi/DeliveryDocument/invalid/unknown-kind.json +23 -0
  73. package/fixtures/openapi/DeliveryDocument/invalid/unknown-version.json +23 -0
  74. package/fixtures/openapi/DeliveryDocument/valid/build-template.json +26 -0
  75. package/fixtures/openapi/DeliveryDocument/valid/deployment-template.json +26 -0
  76. package/fixtures/openapi/DeliveryDocument/valid/legacy-dag.json +39 -0
  77. package/fixtures/openapi/DeliveryDocument/valid/workflow-template.json +23 -0
  78. package/fixtures/openapi/DeliveryDocument/valid/workflow.json +20 -0
  79. package/fixtures/openapi/DeliveryDraftInput/invalid/short-idempotency-key.json +30 -0
  80. package/fixtures/openapi/DeliveryDraftInput/valid/idempotent-create.json +30 -0
  81. package/fixtures/openapi/DeliveryDraftService/invalid/zero-version.json +7 -0
  82. package/fixtures/openapi/DeliveryDraftService/valid/versioned-template.json +9 -0
  83. package/fixtures/openapi/DeliveryExecutionHistoryPage/invalid-kind.json +9 -0
  84. package/fixtures/openapi/DeliveryExecutionHistoryPage/valid-build.json +16 -0
  85. package/fixtures/openapi/DeliveryExecutionHistoryPage/valid-empty.json +3 -0
  86. package/fixtures/openapi/DeliveryPlanApprovalInput/invalid/confirm.json +1 -0
  87. package/fixtures/openapi/DeliveryPlanApprovalInput/valid/approve.json +1 -0
  88. package/fixtures/openapi/DeliveryPlanInput/invalid/draft-manifest-revision.json +1 -0
  89. package/fixtures/openapi/DeliveryPlanInput/valid/manifest-revision.json +1 -0
  90. package/fixtures/openapi/DeliveryTargetInput/invalid/deploy-without-environment.json +7 -0
  91. package/fixtures/openapi/DeliveryTargetInput/valid/build-without-environment.json +6 -0
  92. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/duplicate-kind.json +13 -0
  93. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/embedded-credentials.json +16 -0
  94. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/empty-kinds.json +10 -0
  95. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/negative-generation.json +15 -0
  96. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/unknown-kind.json +12 -0
  97. package/fixtures/openapi/DeliveryTemplateSourceInput/valid/manual-source.json +15 -0
  98. package/fixtures/openapi/DeliveryWorkflowDefinition/invalid/unversioned-template.json +15 -0
  99. package/fixtures/openapi/DeliveryWorkflowDefinition/valid/cross-app.json +24 -0
  100. package/fixtures/openapi/DeliveryWorkflowInput/invalid/short-key.json +1 -0
  101. package/fixtures/openapi/DeliveryWorkflowInput/valid/idempotent-create.json +1 -0
  102. package/fixtures/openapi/DeploymentTemplatePreviewInput/invalid/invalid-target-override.json +7 -0
  103. package/fixtures/openapi/DeploymentTemplatePreviewInput/invalid/invalid-zero-version.json +6 -0
  104. package/fixtures/openapi/DeploymentTemplatePreviewInput/valid/valid.json +6 -0
  105. package/fixtures/openapi/DockerDeliveryConfiguration/invalid/empty-mappings.json +5 -0
  106. package/fixtures/openapi/DockerDeliveryConfiguration/valid/compose.json +7 -0
  107. package/fixtures/openapi/DockerDeliverySnapshot/invalid/missing-required-field.json +19 -0
  108. package/fixtures/openapi/DockerDeliverySnapshot/valid/frozen-project.json +20 -0
  109. package/fixtures/openapi/DockerOperationCallbackRequest/invalid/cancellation-acknowledged-not-boolean.json +13 -0
  110. package/fixtures/openapi/DockerOperationCallbackRequest/valid/cancellation-acknowledged.json +13 -0
  111. package/fixtures/openapi/DockerProjectInput/invalid/empty-host.json +4 -0
  112. package/fixtures/openapi/DockerProjectInput/valid/inline-idempotent.json +7 -0
  113. package/fixtures/openapi/DockerQuickCreateHostInput/invalid/capacity-type.json +1 -0
  114. package/fixtures/openapi/DockerQuickCreateHostInput/valid/capacity.json +1 -0
  115. package/fixtures/openapi/HelmDeliveryConfiguration/invalid/plain-credential.json +21 -0
  116. package/fixtures/openapi/HelmDeliveryConfiguration/invalid/release-name.json +20 -0
  117. package/fixtures/openapi/HelmDeliveryConfiguration/valid/private-oci.json +20 -0
  118. package/fixtures/openapi/HelmDeliverySnapshot/invalid/secret-plan.json +31 -0
  119. package/fixtures/openapi/HelmDeliverySnapshot/invalid/unknown-operation.json +28 -0
  120. package/fixtures/openapi/HelmDeliverySnapshot/valid/first-install.json +28 -0
  121. package/fixtures/openapi/IdentityApplication/invalid/outpost-status.json +1 -0
  122. package/fixtures/openapi/IdentityApplicationOnboardingInput/invalid/missing-access.json +12 -0
  123. package/fixtures/openapi/IdentityApplicationOnboardingInput/valid/link.json +13 -0
  124. package/fixtures/openapi/IdentityOutpost/invalid/missing-runtime-status.json +9 -0
  125. package/fixtures/openapi/IdentityOutpost/valid/healthy.json +17 -0
  126. package/fixtures/openapi/IdentityOutpost/valid/unregistered.json +12 -0
  127. package/fixtures/openapi/IdentityOutpostDeployment/invalid/missing-protocol.json +3 -0
  128. package/fixtures/openapi/IdentityOutpostDeployment/valid/remote.json +6 -0
  129. package/fixtures/openapi/IdentityOutpostInput/invalid/forged-observation.json +7 -0
  130. package/fixtures/openapi/IdentityOutpostInput/valid/forward-auth.json +6 -0
  131. package/fixtures/openapi/IdentityOutpostInput/valid/legacy.json +8 -0
  132. package/fixtures/openapi/IdentityProviderSetup/invalid/fake-verification.json +1 -0
  133. package/fixtures/openapi/IdentityProviderSetup/valid/incomplete.json +1 -0
  134. package/fixtures/openapi/IdentityProviderSetup/valid/remote.json +1 -0
  135. package/fixtures/openapi/IdentityProviderUserMetadata/invalid/attributes.json +8 -0
  136. package/fixtures/openapi/IdentityProviderUserMetadata/valid/attributes.json +13 -0
  137. package/fixtures/openapi/KubernetesCustomResource/invalid/negative-generation.json +1 -0
  138. package/fixtures/openapi/KubernetesCustomResource/valid/deleting-operator.json +1 -0
  139. package/fixtures/openapi/KubernetesResourceCreationReceipt/invalid/manifest-content.json +20 -0
  140. package/fixtures/openapi/KubernetesResourceCreationReceipt/valid/confirmed.json +20 -0
  141. package/fixtures/openapi/KubernetesResourceCreationReceipt/valid/unknown.json +19 -0
  142. package/fixtures/openapi/ManifestDeploymentEnvelope/invalid/negative-resource-observation.json +57 -0
  143. package/fixtures/openapi/ManifestDeploymentEnvelope/valid/operator-deleting.json +57 -0
  144. package/fixtures/openapi/ManifestExecutionTaskPayload/invalid/gitops-empty.json +7 -0
  145. package/fixtures/openapi/ManifestExecutionTaskPayload/valid/gitops-frozen-children.json +18 -0
  146. package/fixtures/openapi/ManifestKustomizeOptions/invalid/escaping-entry.json +1 -0
  147. package/fixtures/openapi/ManifestKustomizeOptions/invalid/mutable-image.json +1 -0
  148. package/fixtures/openapi/ManifestKustomizeOptions/valid/overlay-images.json +7 -0
  149. package/fixtures/openapi/ManifestRevisionInput/invalid/missing-version.json +1 -0
  150. package/fixtures/openapi/ManifestRevisionInput/valid/current-draft.json +1 -0
  151. package/fixtures/openapi/ManifestServiceTemplateInputs/invalid/mutable-image.json +8 -0
  152. package/fixtures/openapi/ManifestServiceTemplateInputs/valid/frozen-service-image.json +9 -0
  153. package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/auto-with-gateway.json +6 -0
  154. package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/missing-manual-gateway.json +5 -0
  155. package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/mode-override.json +7 -0
  156. package/fixtures/openapi/NetworkVPNConnectionIntentInput/valid/auto-intent.json +5 -0
  157. package/fixtures/openapi/NetworkVPNConnectionIntentInput/valid/manual-intent.json +6 -0
  158. package/fixtures/openapi/NetworkVPNProfileInput/invalid/unknown-private-key.json +25 -0
  159. package/fixtures/openapi/NetworkVPNProfileInput/valid/assigned-profile.json +24 -0
  160. package/fixtures/openapi/NetworkVPNSelectionPolicyInput/invalid/unbounded-attempts.json +21 -0
  161. package/fixtures/openapi/NetworkVPNSelectionPolicyInput/valid/provider-policy.json +21 -0
  162. package/fixtures/openapi/ObservabilityMetricAssessmentInput/invalid/arbitrary-expression.json +10 -0
  163. package/fixtures/openapi/ObservabilityMetricAssessmentInput/valid/metric-window.json +10 -0
  164. package/fixtures/openapi/PluginManifest/valid/hermes-api.json +53 -0
  165. package/fixtures/openapi/ProgressiveRolloutControlInput/invalid/missing-identity.json +5 -0
  166. package/fixtures/openapi/ProgressiveRolloutControlInput/invalid/skip-analysis.json +5 -0
  167. package/fixtures/openapi/ProgressiveRolloutControlInput/valid/pause.json +5 -0
  168. package/fixtures/openapi/ReleaseTargetInput/invalid/missing-required-field.json +15 -0
  169. package/fixtures/openapi/ReleaseTargetInput/valid/docker-compose.json +16 -0
  170. package/fixtures/openapi/RepositoryAnalysis/invalid/moving-ref.json +11 -0
  171. package/fixtures/openapi/RepositoryAnalysis/valid/go.json +29 -0
  172. package/fixtures/openapi/RepositoryAnalysisInput/invalid/arbitrary-source-url.json +6 -0
  173. package/fixtures/openapi/RepositoryAnalysisInput/invalid/missing-ref.json +4 -0
  174. package/fixtures/openapi/RepositoryAnalysisInput/valid/nested-project.json +6 -0
  175. package/fixtures/openapi/ServiceDeploymentTemplateBinding/invalid/invalid-unpinned.json +4 -0
  176. package/fixtures/openapi/ServiceDeploymentTemplateBinding/valid/valid.json +7 -0
  177. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-executable-schema.json +25 -0
  178. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-missing-schema.json +21 -0
  179. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-mixed-renderers.json +40 -0
  180. package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/valid-nested-parameters.json +41 -0
  181. package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/valid-yaml.json +34 -0
  182. package/fixtures/openapi/TemplatePublishInput/invalid/missing-revision.json +1 -0
  183. package/fixtures/openapi/TemplatePublishInput/invalid/unknown-field.json +4 -0
  184. package/fixtures/openapi/TemplatePublishInput/invalid/zero-revision.json +3 -0
  185. package/fixtures/openapi/TemplatePublishInput/valid/revision.json +3 -0
  186. package/fixtures/openapi/ToolCapability/invalid/bad-idempotency.json +35 -0
  187. package/fixtures/openapi/ToolCapability/invalid/unversioned-check.json +47 -0
  188. package/fixtures/openapi/ToolCapability/valid/checks.json +48 -0
  189. package/fixtures/openapi/ToolCapability/valid/versioned-task.json +35 -0
  190. package/fixtures/openapi/ToolInvocationRequest/invalid/empty-version.json +8 -0
  191. package/fixtures/openapi/ToolInvocationRequest/valid/versioned.json +8 -0
  192. package/fixtures/openapi/ToolInvocationResult/invalid/missing-status-call.json +13 -0
  193. package/fixtures/openapi/ToolInvocationResult/valid/task.json +20 -0
  194. package/fixtures/openapi/VirtualMachineCreateInput/invalid/capacity-type.json +1 -0
  195. package/fixtures/openapi/VirtualMachineCreateInput/valid/capacity.json +1 -0
  196. package/fixtures/openapi/VirtualizationCapacityInput/invalid/missing-disk.json +1 -0
  197. package/fixtures/openapi/VirtualizationCapacityInput/valid/running-vm.json +1 -0
  198. package/fixtures/openapi/VirtualizationCapacityResult/invalid/invented-status.json +1 -0
  199. package/fixtures/openapi/VirtualizationCapacityResult/valid/available.json +1 -0
  200. package/fixtures/openapi/VirtualizationWorkerCreateInput/invalid/secret-input.json +5 -0
  201. package/fixtures/openapi/VirtualizationWorkerCreateInput/valid/one-worker.json +4 -0
  202. package/fixtures/openapi/VirtualizationWorkerPoolInput/invalid/external-owner.json +27 -0
  203. package/fixtures/openapi/VirtualizationWorkerPoolInput/invalid/zero-budget.json +27 -0
  204. package/fixtures/openapi/VirtualizationWorkerPoolInput/valid/registered.json +27 -0
  205. package/fixtures/openapi/VirtualizationWorkerPoolListEnvelope/invalid/nested-data.json +5 -0
  206. package/fixtures/openapi/VirtualizationWorkerPoolListEnvelope/valid/empty.json +3 -0
  207. package/fixtures/openapi/WorkbenchContextSelection/invalid/missing-file-content.json +8 -0
  208. package/fixtures/openapi/WorkbenchContextSelection/invalid/unknown-reference-kind.json +8 -0
  209. package/fixtures/openapi/WorkbenchContextSelection/valid/text-and-resource.json +17 -0
  210. package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/trigger-without-plan.json +10 -0
  211. package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/unknown-trigger.json +28 -0
  212. package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/zero-interval.json +28 -0
  213. package/fixtures/openapi/WorkbenchInspectionTaskInput/valid/alert-capability.json +30 -0
  214. package/fixtures/openapi/WorkbenchInspectionTaskInput/valid/scheduled-capability.json +28 -0
  215. package/fixtures/openapi/WorkbenchInspectionTrigger/invalid/missing-rule.json +3 -0
  216. package/fixtures/openapi/WorkbenchInspectionTrigger/invalid/schedule-alert-selector.json +4 -0
  217. package/fixtures/openapi/WorkbenchInspectionTrigger/valid/schedule.json +3 -0
  218. package/fixtures/openapi/WorkbenchModelPreferences/invalid/unknown-effort.json +1 -0
  219. package/fixtures/openapi/WorkbenchModelPreferences/valid/selected.json +1 -0
  220. package/fixtures/openapi/WorkflowCatalogPage/invalid/unsupported-kind.json +6 -0
  221. package/fixtures/openapi/WorkflowCatalogPage/valid/build-without-environment.json +6 -0
  222. package/fixtures/openapi/WorkflowTemplateInput/invalid/zero-revision.json +6 -0
  223. package/fixtures/openapi/WorkflowTemplateInput/valid/draft.json +10 -0
  224. package/gen/go/sohaapi/client.go +1 -1
  225. package/gen/go/sohaapi/types.go +8070 -859
  226. package/gen/go/sohaapi/types_test.go +100 -1
  227. package/gen/go/sohaapi/vpn_models_test.go +21 -0
  228. package/gen/ts/sohaapi/index.ts +20343 -13162
  229. package/go.mod +107 -5
  230. package/go.sum +379 -16
  231. package/helmrelease/delivery.go +283 -0
  232. package/helmrelease/delivery_test.go +102 -0
  233. package/helmrelease/frozen_chart.go +91 -0
  234. package/helmrelease/result.go +27 -0
  235. package/helmrelease/runtime/delivery.go +508 -0
  236. package/helmrelease/runtime/delivery_test.go +236 -0
  237. package/helmrelease/runtime/resources_test.go +60 -0
  238. package/helmrelease/values.go +99 -0
  239. package/knowledge/context-envelope.schema.json +1 -0
  240. package/mcp/gateway-manifest.schema.json +387 -72
  241. package/network/README.md +40 -0
  242. package/network/network-ingest-event.schema.json +228 -0
  243. package/network/network-runtime-protocol.schema.json +457 -1
  244. package/openapi/soha-api.json +54958 -38899
  245. package/openapi/soha-api.yaml +12143 -4984
  246. package/openapi/spec_generated.go +3 -3
  247. package/package.json +3 -1
  248. package/resource/manifest_health.go +155 -0
  249. package/resource/manifest_health_test.go +37 -0
  250. package/resource/models.go +14 -0
  251. package/resource/runtime/argocd.go +166 -0
  252. package/resource/runtime/argocd_integration_test.go +142 -0
  253. package/resource/runtime/argocd_observation.go +143 -0
  254. package/resource/runtime/argocd_operation.go +253 -0
  255. package/resource/runtime/argocd_resources.go +115 -0
  256. package/resource/runtime/argocd_resources_test.go +107 -0
  257. package/resource/runtime/argocd_test.go +386 -0
  258. package/resource/runtime/health.go +115 -0
  259. package/resource/runtime/health_test.go +78 -0
  260. package/resource/runtime/operator_integration_test.go +174 -0
  261. package/resource/runtime/ownership.go +178 -0
  262. package/resource/runtime/ownership_test.go +178 -0
  263. package/resource/runtime/rollout_integration_test.go +287 -0
  264. package/resource/runtime/rollout_observation.go +252 -0
  265. package/resource/runtime/rollout_operation.go +398 -0
  266. package/resource/runtime/rollout_resources.go +385 -0
  267. package/resource/runtime/rollout_task.go +165 -0
  268. package/resource/runtime/rollout_test.go +450 -0
@@ -0,0 +1,236 @@
1
+ package runtime
2
+
3
+ import (
4
+ "archive/tar"
5
+ "bytes"
6
+ "compress/gzip"
7
+ "context"
8
+ "encoding/base64"
9
+ "encoding/json"
10
+ "io"
11
+ "net/http"
12
+ "strings"
13
+ "testing"
14
+ "time"
15
+
16
+ "github.com/opensoha/soha-contracts/gen/go/sohaapi"
17
+ "github.com/opensoha/soha-contracts/helmrelease"
18
+ "helm.sh/helm/v4/pkg/action"
19
+ "helm.sh/helm/v4/pkg/chart/common"
20
+ "helm.sh/helm/v4/pkg/chart/loader"
21
+ chartv2 "helm.sh/helm/v4/pkg/chart/v2"
22
+ "helm.sh/helm/v4/pkg/engine"
23
+ "helm.sh/helm/v4/pkg/kube"
24
+ kubefake "helm.sh/helm/v4/pkg/kube/fake"
25
+ releasecommon "helm.sh/helm/v4/pkg/release/common"
26
+ releasev1 "helm.sh/helm/v4/pkg/release/v1"
27
+ "helm.sh/helm/v4/pkg/storage"
28
+ "helm.sh/helm/v4/pkg/storage/driver"
29
+ "k8s.io/apimachinery/pkg/api/meta"
30
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31
+ "k8s.io/apimachinery/pkg/runtime/schema"
32
+ kuberesource "k8s.io/cli-runtime/pkg/resource"
33
+ restfake "k8s.io/client-go/rest/fake"
34
+ )
35
+
36
+ type observedHelmKubeClient struct {
37
+ kubefake.PrintingKubeClient
38
+ deleting bool
39
+ missing bool
40
+ namespace string
41
+ reads int
42
+ }
43
+
44
+ func (c *observedHelmKubeClient) Build(io.Reader, bool) (kube.ResourceList, error) {
45
+ object := `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"app","namespace":"dev"}}`
46
+ client := &restfake.RESTClient{GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: kuberesource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, Client: restfake.CreateHTTPClient(func(*http.Request) (*http.Response, error) {
47
+ c.reads++
48
+ if c.missing {
49
+ return &http.Response{StatusCode: http.StatusNotFound, Header: http.Header{"Content-Type": {"application/json"}}, Body: io.NopCloser(strings.NewReader(`{"apiVersion":"v1","kind":"Status","status":"Failure","reason":"NotFound","code":404}`))}, nil
50
+ }
51
+ body := object
52
+ if c.deleting {
53
+ body = strings.Replace(body, `"name":"app"`, `"name":"app","deletionTimestamp":"2026-09-12T00:00:00Z"`, 1)
54
+ }
55
+ return &http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": {"application/json"}}, Body: io.NopCloser(strings.NewReader(body))}, nil
56
+ })}
57
+ var item unstructured.Unstructured
58
+ if err := json.Unmarshal([]byte(object), &item); err != nil {
59
+ return nil, err
60
+ }
61
+ namespace := "dev"
62
+ if c.namespace != "" {
63
+ namespace = c.namespace
64
+ }
65
+ return kube.ResourceList{&kuberesource.Info{Client: client, Namespace: namespace, Name: "app", Object: &item, Mapping: &meta.RESTMapping{Resource: schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, GroupVersionKind: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Scope: meta.RESTScopeNamespace}}}, nil
66
+ }
67
+
68
+ func TestHelmObservationUsesLiveResourceHealth(t *testing.T) {
69
+ client := &observedHelmKubeClient{}
70
+ cfg := &action.Configuration{KubeClient: client}
71
+ snapshot := sohaapi.HelmDeliverySnapshot{ApplicationID: "app", ServiceID: "svc", ApplicationEnvironmentID: "dev", TargetID: "target", DeliveryPlanID: "plan", ReleaseName: "app", Namespace: "dev"}
72
+ _, digest, err := helmDeliveryResources(cfg, "dev", "manifest", nil)
73
+ if err != nil {
74
+ t.Fatal(err)
75
+ }
76
+ snapshot.RenderedDigest = digest
77
+ release := &releasev1.Release{Name: "app", Namespace: "dev", Version: 1, Manifest: "manifest", Labels: helmDeliveryLabels(snapshot), Info: &releasev1.Info{Status: releasecommon.StatusDeployed}}
78
+ for _, deleting := range []bool{false, true} {
79
+ client.deleting = deleting
80
+ result, err := observeHelmDelivery(context.Background(), cfg, snapshot, release)
81
+ if err != nil || result.Ready == deleting || len(result.Resources) != 1 {
82
+ t.Fatalf("live health deleting=%v: %+v %v", deleting, result, err)
83
+ }
84
+ }
85
+ }
86
+
87
+ func TestHelmDeliveryFrozenRenderAndNativeRollbackRecovery(t *testing.T) {
88
+ manifest := "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: app\ndata:\n literal: '{{ this is data }}'\n"
89
+ hook := "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: hook\n annotations:\n helm.sh/hook: pre-install,pre-upgrade\n"
90
+ original := helmSourceTestArchive(t)
91
+ frozen, err := helmrelease.FrozenChartArchive(original, manifest, []string{hook})
92
+ if err != nil {
93
+ t.Fatal(err)
94
+ }
95
+ ch, err := loader.LoadArchive(bytes.NewReader(frozen))
96
+ if err != nil {
97
+ t.Fatal(err)
98
+ }
99
+ files, err := engine.Render(ch, map[string]any{})
100
+ if err != nil || files["app/templates/resources.yaml"] != manifest || files["app/templates/hook-0.yaml"] != hook {
101
+ t.Fatalf("frozen render changed approved resources: %v %#v", err, files)
102
+ }
103
+ cfg := &action.Configuration{Releases: storage.Init(driver.NewMemory()), KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, Capabilities: common.DefaultCapabilities}
104
+ snapshot := sohaapi.HelmDeliverySnapshot{ClusterID: "cluster", ApplicationID: "app", ServiceID: "svc", ApplicationEnvironmentID: "dev", TargetID: "target", DeliveryPlanID: "old-plan", ReleaseName: "app", Namespace: "dev", ChartDigest: helmrelease.SHA256(original)}
105
+ nativeChart, ok := ch.(*chartv2.Chart)
106
+ if !ok {
107
+ t.Fatal("frozen chart has an unexpected type")
108
+ }
109
+ for version := 1; version <= 2; version++ {
110
+ item := &releasev1.Release{Name: "app", Namespace: "dev", Version: version, Info: &releasev1.Info{Status: releasecommon.StatusDeployed}, Labels: helmDeliveryLabels(snapshot), Chart: nativeChart, Config: map[string]any{"version": version}, Manifest: manifest}
111
+ if err := cfg.Releases.Create(item); err != nil {
112
+ t.Fatal(err)
113
+ }
114
+ }
115
+ snapshot.DeliveryPlanID, snapshot.ExpectedRevision, snapshot.RollbackRevision = "rollback-plan", 2, 1
116
+ if err := RequireUnmanaged(cfg, snapshot.ReleaseName); err == nil {
117
+ t.Fatal("legacy mutation accepted a governed release")
118
+ }
119
+ if err := RequireUnmanaged(cfg, "missing-release"); err != nil {
120
+ t.Fatal("ownership guard replaced normal missing-release handling", err)
121
+ }
122
+ snapshot.Operation = sohaapi.HelmDeliverySnapshotOperationRollback
123
+ _, snapshot.RenderedDigest, err = helmDeliveryResources(cfg, "dev", manifest, nil)
124
+ if err != nil {
125
+ t.Fatal(err)
126
+ }
127
+ rolledBack, err := runHelmDeliveryRollback(cfg, snapshot, time.Second)
128
+ if err != nil || rolledBack.Version != 3 || rolledBack.Config["version"] != 1 || !helmDeliveryAlreadyApplied(rolledBack, snapshot) {
129
+ t.Fatalf("native rollback did not produce the selected revision with new plan ownership: %+v %v", rolledBack, err)
130
+ }
131
+ old, err := currentHelmDelivery(cfg, "app", 1)
132
+ if err != nil || old.Labels["soha-delivery-plan"] == rolledBack.Labels["soha-delivery-plan"] {
133
+ t.Fatalf("rollback rewrote previous history: %v", err)
134
+ }
135
+ prepared := &sohaapi.HelmPreparedRelease{Manifest: manifest}
136
+ if err := json.Unmarshal([]byte(`{"version":1}`), &prepared.Values); err != nil {
137
+ t.Fatal(err)
138
+ }
139
+ snapshot.ValuesDigest = helmrelease.SHA256([]byte(`{"version":1}`))
140
+ result, err := Execute(context.Background(), cfg, sohaapi.HelmExecutionTaskPayload{Action: sohaapi.Apply, Snapshot: snapshot, Prepared: prepared})
141
+ if err != nil || !result.Ready || result.Revision != 3 {
142
+ t.Fatalf("recovered callback repeated or lost native rollback: %+v %v", result, err)
143
+ }
144
+ history, err := cfg.Releases.History("app")
145
+ if err != nil || len(history) != 3 {
146
+ t.Fatalf("retry created another native revision: %d %v", len(history), err)
147
+ }
148
+ old.Config["version"] = 99
149
+ if err := cfg.Releases.Update(old); err != nil {
150
+ t.Fatal(err)
151
+ }
152
+ if _, err := Execute(context.Background(), cfg, sohaapi.HelmExecutionTaskPayload{Action: sohaapi.Apply, Snapshot: snapshot, Prepared: prepared}); err == nil {
153
+ t.Fatal("rollback accepted changed historical values")
154
+ }
155
+ rolledBack.Info.Status = releasecommon.StatusFailed
156
+ result, err = observeHelmDelivery(context.Background(), cfg, snapshot, rolledBack)
157
+ if err == nil || result.Ready || !result.Stopped || result.Revision != 3 || result.Status != "failed" {
158
+ t.Fatalf("failed side effect disappeared: %+v %v", result, err)
159
+ }
160
+ }
161
+
162
+ func helmSourceTestArchive(t *testing.T) []byte {
163
+ t.Helper()
164
+ var buffer bytes.Buffer
165
+ zip := gzip.NewWriter(&buffer)
166
+ writer := tar.NewWriter(zip)
167
+ for name, content := range map[string]string{
168
+ "app/Chart.yaml": "apiVersion: v2\nname: app\nversion: 1.2.3\n",
169
+ "app/values.yaml": "replicaCount: 1\n",
170
+ "app/values.schema.json": `{"type":"object","properties":{"replicaCount":{"type":"integer","minimum":1}}}`,
171
+ } {
172
+ if err := writer.WriteHeader(&tar.Header{Name: name, Mode: 0600, Size: int64(len(content))}); err != nil {
173
+ t.Fatal(err)
174
+ }
175
+ if _, err := writer.Write([]byte(content)); err != nil {
176
+ t.Fatal(err)
177
+ }
178
+ }
179
+ if err := writer.Close(); err != nil {
180
+ t.Fatal(err)
181
+ }
182
+ if err := zip.Close(); err != nil {
183
+ t.Fatal(err)
184
+ }
185
+ return buffer.Bytes()
186
+ }
187
+
188
+ func TestHelmPrepareKeepsLiveClientsForResourceValidation(t *testing.T) {
189
+ client := &observedHelmKubeClient{missing: true}
190
+ releases := storage.Init(driver.NewMemory())
191
+ cfg := &action.Configuration{KubeClient: client, Releases: releases, Capabilities: common.DefaultCapabilities}
192
+ archive := helmSourceTestArchive(t)
193
+ input := sohaapi.HelmExecutionTaskPayload{Action: sohaapi.Preflight, Snapshot: sohaapi.HelmDeliverySnapshot{DeliveryPlanID: "plan", ApplicationID: "app", ServiceID: "svc", ServiceVersion: 1, ApplicationEnvironmentID: "dev", TargetID: "target", ClusterID: "cluster", Namespace: "dev", ReleaseName: "app", ChartDigest: helmrelease.SHA256(archive)}, Prepared: &sohaapi.HelmPreparedRelease{ChartArchive: base64.StdEncoding.EncodeToString(archive)}}
194
+ result, err := Prepare(context.Background(), cfg, input)
195
+ if err != nil || len(result.Snapshot.Resources) != 1 || cfg.KubeClient != client || cfg.Releases != releases {
196
+ t.Fatalf("prepare lost live validation: resources=%d client=%T err=%v", len(result.Snapshot.Resources), cfg.KubeClient, err)
197
+ }
198
+ if _, err := releases.Last("app"); err == nil {
199
+ t.Fatal("preparation persisted a release")
200
+ }
201
+ }
202
+
203
+ func TestHelmPreflightUsesClusterCapabilitiesWithoutTemplateLookup(t *testing.T) {
204
+ client := &observedHelmKubeClient{missing: true}
205
+ caps := common.DefaultCapabilities.Copy()
206
+ caps.KubeVersion = common.KubeVersion{Version: "v1.36.0", Major: "1", Minor: "36"}
207
+ caps.APIVersions = common.VersionSet{"v1"}
208
+ lookups := 0
209
+ cfg := &action.Configuration{KubeClient: client, Releases: storage.Init(driver.NewMemory()), Capabilities: caps, CustomTemplateFuncs: map[string]any{"lookup": func(string, string, string, string) (map[string]any, error) {
210
+ lookups++
211
+ return map[string]any{"secret": "private"}, nil
212
+ }}}
213
+ ch := &chartv2.Chart{Metadata: &chartv2.Metadata{APIVersion: "v2", Name: "app", Version: "1.0.0", KubeVersion: ">=1.35.0"}, Templates: []*common.File{{Name: "templates/config.yaml", Data: []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: app\ndata:\n version: {{ .Capabilities.KubeVersion.Version | quote }}\n absentAPI: {{ .Capabilities.APIVersions.Has \"apps/v1\" | quote }}\n lookup: {{ lookup \"v1\" \"Secret\" \"other-namespace\" \"private\" | toJson | quote }}\n")}}}
214
+ input := sohaapi.HelmExecutionTaskPayload{Snapshot: sohaapi.HelmDeliverySnapshot{Operation: sohaapi.HelmDeliverySnapshotOperationInstall, ReleaseName: "app", Namespace: "dev"}}
215
+ result, err := runHelmDeliveryAction(context.Background(), cfg, input, ch, true)
216
+ if err != nil {
217
+ t.Fatal(err)
218
+ }
219
+ if lookups != 0 || !strings.Contains(result.Manifest, `version: "v1.36.0"`) || !strings.Contains(result.Manifest, `absentAPI: "false"`) || !strings.Contains(result.Manifest, `lookup: "{}"`) {
220
+ t.Fatal("preflight ignored actual capabilities or read template data", result.Manifest)
221
+ }
222
+ lookup, ok := cfg.CustomTemplateFuncs["lookup"].(func(string, string, string, string) (map[string]any, error))
223
+ if !ok {
224
+ t.Fatal("preflight replaced the caller's template functions")
225
+ }
226
+ if original, err := lookup("", "", "", ""); err != nil || original["secret"] != "private" {
227
+ t.Fatal("preflight changed the caller's lookup function")
228
+ }
229
+ if _, err := cfg.Releases.Last("app"); err == nil {
230
+ t.Fatal("preflight persisted a release")
231
+ }
232
+ client.namespace, client.reads = "other-namespace", 0
233
+ if _, err := runHelmDeliveryAction(context.Background(), cfg, input, ch, true); err == nil || client.reads != 0 {
234
+ t.Fatal("preflight read a resource outside the authorized namespace", err, client.reads)
235
+ }
236
+ }
@@ -0,0 +1,60 @@
1
+ package runtime
2
+
3
+ import (
4
+ "io"
5
+ "testing"
6
+
7
+ "helm.sh/helm/v4/pkg/action"
8
+ "helm.sh/helm/v4/pkg/kube"
9
+ kubefake "helm.sh/helm/v4/pkg/kube/fake"
10
+ "k8s.io/apimachinery/pkg/api/meta"
11
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12
+ "k8s.io/apimachinery/pkg/runtime/schema"
13
+ cliresource "k8s.io/cli-runtime/pkg/resource"
14
+ )
15
+
16
+ type deliveryResourceClient struct {
17
+ *kubefake.PrintingKubeClient
18
+ items kube.ResourceList
19
+ }
20
+
21
+ func (c deliveryResourceClient) Build(io.Reader, bool) (kube.ResourceList, error) {
22
+ return c.items, nil
23
+ }
24
+
25
+ func TestHelmResourceAdmissionRejectsUntrackedOrRetainedObjects(t *testing.T) {
26
+ for _, test := range []struct {
27
+ name, namespace, objectName string
28
+ scope meta.RESTScope
29
+ keep, duplicate, invalid bool
30
+ }{
31
+ {name: "namespaced", namespace: "dev", objectName: "app", scope: meta.RESTScopeNamespace},
32
+ {name: "other namespace", namespace: "prod", objectName: "app", scope: meta.RESTScopeNamespace, invalid: true},
33
+ {name: "cluster scope", objectName: "app", scope: meta.RESTScopeRoot, invalid: true},
34
+ {name: "generated name", namespace: "dev", scope: meta.RESTScopeNamespace, invalid: true},
35
+ {name: "retained resource", namespace: "dev", objectName: "app", scope: meta.RESTScopeNamespace, keep: true, invalid: true},
36
+ {name: "duplicate resource", namespace: "dev", objectName: "app", scope: meta.RESTScopeNamespace, duplicate: true, invalid: true},
37
+ } {
38
+ t.Run(test.name, func(t *testing.T) {
39
+ object := &unstructured.Unstructured{Object: map[string]any{"apiVersion": "v1", "kind": "ConfigMap"}}
40
+ object.SetName(test.objectName)
41
+ object.SetNamespace(test.namespace)
42
+ if test.keep {
43
+ object.SetAnnotations(map[string]string{"helm.sh/resource-policy": "keep"})
44
+ }
45
+ item := &cliresource.Info{Name: test.objectName, Namespace: test.namespace, Object: object, Mapping: &meta.RESTMapping{Scope: test.scope, GroupVersionKind: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}}}
46
+ items := kube.ResourceList{item}
47
+ if test.duplicate {
48
+ items = append(items, item)
49
+ }
50
+ cfg := &action.Configuration{KubeClient: deliveryResourceClient{&kubefake.PrintingKubeClient{Out: io.Discard}, items}}
51
+ resources, digest, err := helmDeliveryResources(cfg, "dev", "manifest", nil)
52
+ if (err != nil) != test.invalid {
53
+ t.Fatalf("err=%v", err)
54
+ }
55
+ if err == nil && (len(resources) != 1 || len(digest) != 71) {
56
+ t.Fatal("resource identity or digest missing")
57
+ }
58
+ })
59
+ }
60
+ }
@@ -0,0 +1,99 @@
1
+ package helmrelease
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "fmt"
7
+ "strconv"
8
+ "strings"
9
+
10
+ "github.com/opensoha/soha-contracts/gen/go/sohaapi"
11
+ )
12
+
13
+ // MapImages returns a copy; mapping never changes a saved values configuration.
14
+ func MapImages(values map[string]any, mappings []sohaapi.HelmImageMapping, images map[string]string) (map[string]any, error) {
15
+ encoded, err := json.Marshal(values)
16
+ if err != nil {
17
+ return nil, err
18
+ }
19
+ result := map[string]any{}
20
+ if err := json.Unmarshal(encoded, &result); err != nil {
21
+ return nil, err
22
+ }
23
+ if result == nil {
24
+ result = map[string]any{}
25
+ }
26
+ for _, mapping := range mappings {
27
+ image := images[mapping.ContainerName]
28
+ repository, digest, ok := strings.Cut(image, "@")
29
+ if !ok || repository == "" || !helmSHA256.MatchString(digest) {
30
+ return nil, fmt.Errorf("helm image mapping requires a verified digest for container %s", mapping.ContainerName)
31
+ }
32
+ value := image
33
+ switch mapping.Value {
34
+ case sohaapi.HelmImageMappingValueRepository:
35
+ value = repository
36
+ case sohaapi.HelmImageMappingValueDigest:
37
+ value = digest
38
+ case sohaapi.HelmImageMappingValueImage:
39
+ default:
40
+ return nil, errors.New("unsupported Helm image mapping value")
41
+ }
42
+ parts, err := pointerParts(mapping.Path)
43
+ if err != nil {
44
+ return nil, err
45
+ }
46
+ if err := setPointer(result, parts, value); err != nil {
47
+ return nil, fmt.Errorf("helm image path %s: %w", mapping.Path, err)
48
+ }
49
+ }
50
+ return result, nil
51
+ }
52
+
53
+ func pointerParts(pointer string) ([]string, error) {
54
+ if !strings.HasPrefix(pointer, "/") || len(pointer) > 512 {
55
+ return nil, errors.New("helm image path must be an RFC 6901 pointer of at most 512 characters")
56
+ }
57
+ parts := strings.Split(pointer[1:], "/")
58
+ for index, part := range parts {
59
+ for offset := 0; offset < len(part); offset++ {
60
+ if part[offset] == '~' {
61
+ if offset+1 >= len(part) || part[offset+1] != '0' && part[offset+1] != '1' {
62
+ return nil, errors.New("invalid escape in Helm image path")
63
+ }
64
+ offset++
65
+ }
66
+ }
67
+ parts[index] = strings.ReplaceAll(strings.ReplaceAll(part, "~1", "/"), "~0", "~")
68
+ }
69
+ return parts, nil
70
+ }
71
+
72
+ func setPointer(node any, parts []string, value string) error {
73
+ key := parts[0]
74
+ switch typed := node.(type) {
75
+ case map[string]any:
76
+ if len(parts) == 1 {
77
+ typed[key] = value
78
+ return nil
79
+ }
80
+ child, ok := typed[key]
81
+ if !ok {
82
+ child = map[string]any{}
83
+ typed[key] = child
84
+ }
85
+ return setPointer(child, parts[1:], value)
86
+ case []any:
87
+ index, err := strconv.Atoi(key)
88
+ if err != nil || index < 0 || index >= len(typed) || strconv.Itoa(index) != key {
89
+ return errors.New("array index is missing or out of bounds")
90
+ }
91
+ if len(parts) == 1 {
92
+ typed[index] = value
93
+ return nil
94
+ }
95
+ return setPointer(typed[index], parts[1:], value)
96
+ default:
97
+ return errors.New("path traverses a scalar value")
98
+ }
99
+ }
@@ -22,6 +22,7 @@
22
22
  "environment": { "type": "object" },
23
23
  "budgets": { "type": "object" },
24
24
  "budgetUsage": { "type": "object" },
25
+ "truncations": { "type": "array", "items": { "type": "string" } },
25
26
  "policySnapshot": { "$ref": "#/$defs/versionRef" },
26
27
  "contentHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
27
28
  "createdAt": { "type": "string", "format": "date-time" }