@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,283 @@
1
+ package helmrelease
2
+
3
+ import (
4
+ "archive/tar"
5
+ "bytes"
6
+ "compress/gzip"
7
+ "crypto/sha256"
8
+ "encoding/hex"
9
+ "encoding/json"
10
+ "errors"
11
+ "io"
12
+ "net/url"
13
+ "path"
14
+ "regexp"
15
+ "strings"
16
+
17
+ "github.com/opensoha/soha-contracts/gen/go/sohaapi"
18
+ )
19
+
20
+ const (
21
+ MaxChartArchiveBytes = 5 << 20
22
+ MaxChartExpandedBytes = 20 << 20
23
+ MaxChartFiles = 1000
24
+ )
25
+
26
+ var (
27
+ helmName = regexp.MustCompile(`^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$`)
28
+ helmVersion = regexp.MustCompile(`^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$`)
29
+ helmSHA256 = regexp.MustCompile(`^sha256:[a-f0-9]{64}$`)
30
+ helmSecretReference = regexp.MustCompile(`^soha://secrets/[A-Za-z0-9._-]+(?:/versions/[1-9][0-9]*)?$`)
31
+ )
32
+
33
+ // ValidateSource rejects executable/local sources and ambiguous, mutable chart references.
34
+ // Credential authorization and resolution belong to the consuming application.
35
+ func ValidateSource(source sohaapi.DeploymentTemplateHelmSource) error {
36
+ u, err := url.Parse(source.RepositoryURL)
37
+ if err != nil || u.Hostname() == "" || (u.Scheme != "https" && u.Scheme != "oci") || u.User != nil || u.RawQuery != "" || u.Fragment != "" || u.Opaque != "" {
38
+ return errors.New("helm repository must be an HTTPS or OCI URL without embedded credentials, query or fragment")
39
+ }
40
+ if strings.Contains(u.Path, "\\") || path.Clean("/"+u.Path) != strings.TrimSuffix("/"+strings.TrimPrefix(u.Path, "/"), "/") && u.Path != "" && u.Path != "/" {
41
+ return errors.New("helm repository path must be canonical")
42
+ }
43
+ if !helmName.MatchString(source.Chart) || len(source.Chart) > 253 || !helmVersion.MatchString(source.Version) {
44
+ return errors.New("helm chart requires a chart name and an exact semantic version")
45
+ }
46
+ if source.Digest != "" && !helmSHA256.MatchString(source.Digest) {
47
+ return errors.New("helm chart digest must be a SHA-256 digest")
48
+ }
49
+ if source.ConnectionID != "" {
50
+ return errors.New("helm chart connections are unavailable; use scoped secret references")
51
+ }
52
+ if source.SecretRefs != nil {
53
+ for alias, reference := range *source.SecretRefs {
54
+ if alias != "CHART_USERNAME" && alias != "CHART_PASSWORD" && alias != "CHART_CA_CERT" {
55
+ return errors.New("unsupported Helm chart credential reference")
56
+ }
57
+ if !helmSecretReference.MatchString(reference) {
58
+ return errors.New("helm chart credentials require Soha secret references")
59
+ }
60
+ }
61
+ }
62
+ return nil
63
+ }
64
+
65
+ func ValidateConfiguration(config sohaapi.HelmDeliveryConfiguration) error {
66
+ if err := ValidateSource(config.Source); err != nil {
67
+ return err
68
+ }
69
+ if !helmName.MatchString(config.ReleaseName) || len(config.ReleaseName) > 53 {
70
+ return errors.New("helm release name must be a DNS label of at most 53 characters")
71
+ }
72
+ if config.TimeoutSeconds < 0 || config.TimeoutSeconds > 3600 {
73
+ return errors.New("helm timeout must be between 1 and 3600 seconds")
74
+ }
75
+ seen := map[string]bool{}
76
+ for _, mapping := range config.ImageMappings {
77
+ if mapping.ContainerName == "" || !mapping.Value.Valid() {
78
+ return errors.New("helm image mapping requires a container and an immutable image field")
79
+ }
80
+ if _, err := pointerParts(mapping.Path); err != nil {
81
+ return err
82
+ }
83
+ if seen[mapping.Path] {
84
+ return errors.New("helm image mapping paths must be unique")
85
+ }
86
+ seen[mapping.Path] = true
87
+ }
88
+ return nil
89
+ }
90
+
91
+ func SHA256(content []byte) string {
92
+ sum := sha256.Sum256(content)
93
+ return "sha256:" + hex.EncodeToString(sum[:])
94
+ }
95
+
96
+ // ValidateChartArchive bounds both compressed and expanded input before an SDK loads it.
97
+ // Symlinks, duplicate entries and dependency downloads are never followed here.
98
+ func ValidateChartArchive(content []byte) error {
99
+ budget, files := int64(MaxChartExpandedBytes), 0
100
+ return validateChartArchive(content, &budget, &files, 0)
101
+ }
102
+
103
+ func validateChartArchive(content []byte, budget *int64, files *int, depth int) error {
104
+ if len(content) == 0 || len(content) > MaxChartArchiveBytes {
105
+ return errors.New("helm chart archive exceeds the 5 MiB limit or is empty")
106
+ }
107
+ if depth > 8 {
108
+ return errors.New("helm chart dependencies exceed the nesting limit")
109
+ }
110
+ zr, err := gzip.NewReader(bytes.NewReader(content))
111
+ if err != nil {
112
+ return errors.New("helm chart must be a gzip archive")
113
+ }
114
+ defer func() { _ = zr.Close() }()
115
+ bounded := chartBudgetReader{reader: zr, remaining: budget}
116
+ reader := tar.NewReader(bounded)
117
+ seen := map[string]bool{}
118
+ root := ""
119
+ for {
120
+ header, err := reader.Next()
121
+ if errors.Is(err, io.EOF) {
122
+ break
123
+ }
124
+ if err != nil || *budget <= 0 || *files >= MaxChartFiles {
125
+ return errors.New("helm chart archive is invalid or exceeds its file/expanded size limit")
126
+ }
127
+ name := strings.TrimSuffix(header.Name, "/")
128
+ if err := validateChartEntry(header, name, seen); err != nil {
129
+ return err
130
+ }
131
+ folder, _, _ := strings.Cut(name, "/")
132
+ if root != "" && root != folder {
133
+ return errors.New("helm chart archive must contain one root directory")
134
+ }
135
+ root, seen[name] = folder, true
136
+ *files++
137
+ if strings.Contains(name, "/crds/") {
138
+ return errors.New("application Helm charts cannot install cluster-scoped CRDs; install them through platform management first")
139
+ }
140
+ if header.Typeflag == tar.TypeReg && path.Base(name) == "values.schema.json" {
141
+ content, err := io.ReadAll(reader)
142
+ if err != nil {
143
+ return errors.New("read Helm values schema")
144
+ }
145
+ if err := validateValuesSchema(content); err != nil {
146
+ return err
147
+ }
148
+ }
149
+ if header.Typeflag == tar.TypeReg && strings.Contains(name, "/charts/") && strings.HasSuffix(name, ".tgz") {
150
+ child, err := io.ReadAll(io.LimitReader(reader, MaxChartArchiveBytes+1))
151
+ if err != nil {
152
+ return errors.New("read Helm chart dependency")
153
+ }
154
+ if err := validateChartArchive(child, budget, files, depth+1); err != nil {
155
+ return err
156
+ }
157
+ }
158
+ }
159
+ if _, err := io.Copy(io.Discard, bounded); err != nil || *budget <= 0 || !seen[root+"/Chart.yaml"] {
160
+ return errors.New("helm chart archive is invalid, oversized or missing Chart.yaml")
161
+ }
162
+ return nil
163
+ }
164
+
165
+ func validateValuesSchema(content []byte) error {
166
+ var schema any
167
+ if err := json.Unmarshal(content, &schema); err != nil {
168
+ return errors.New("helm values schema must be valid JSON")
169
+ }
170
+ return validateSchemaReferences(schema)
171
+ }
172
+
173
+ func validateSchemaReferences(value any) error {
174
+ switch item := value.(type) {
175
+ case map[string]any:
176
+ for key, value := range item {
177
+ if reference, ok := value.(string); ok {
178
+ switch key {
179
+ case "$ref", "$dynamicRef", "$recursiveRef":
180
+ if reference != "" && !strings.HasPrefix(reference, "#") {
181
+ return errors.New("helm values schemas require references within the same document")
182
+ }
183
+ case "$schema":
184
+ if !knownSchemaDialect(reference) {
185
+ return errors.New("helm values schema uses an unsupported schema dialect")
186
+ }
187
+ }
188
+ }
189
+ if err := validateSchemaReferences(value); err != nil {
190
+ return err
191
+ }
192
+ }
193
+ case []any:
194
+ for _, value := range item {
195
+ if err := validateSchemaReferences(value); err != nil {
196
+ return err
197
+ }
198
+ }
199
+ }
200
+ return nil
201
+ }
202
+
203
+ func knownSchemaDialect(value string) bool {
204
+ value = strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(value, "https://"), "http://"), "#")
205
+ switch value {
206
+ case "json-schema.org/draft-04/schema", "json-schema.org/draft-06/schema", "json-schema.org/draft-07/schema", "json-schema.org/draft/2019-09/schema", "json-schema.org/draft/2020-12/schema":
207
+ return true
208
+ default:
209
+ return false
210
+ }
211
+ }
212
+
213
+ func validateChartEntry(header *tar.Header, name string, seen map[string]bool) error {
214
+ if name == "" || strings.Contains(name, "\\") || strings.HasPrefix(name, "/") || path.Clean(name) != name || name == ".." || strings.HasPrefix(name, "../") || seen[name] {
215
+ return errors.New("helm chart archive contains an unsafe or duplicate path")
216
+ }
217
+ if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeDir || header.Size < 0 || header.Size > MaxChartExpandedBytes {
218
+ return errors.New("helm chart archive contains an unsupported file type or size")
219
+ }
220
+ return nil
221
+ }
222
+
223
+ type chartBudgetReader struct {
224
+ reader io.Reader
225
+ remaining *int64
226
+ }
227
+
228
+ func (r chartBudgetReader) Read(p []byte) (int, error) {
229
+ if *r.remaining <= 0 {
230
+ return 0, errors.New("helm chart exceeds the expanded size limit")
231
+ }
232
+ if int64(len(p)) > *r.remaining {
233
+ p = p[:*r.remaining]
234
+ }
235
+ n, err := r.reader.Read(p)
236
+ *r.remaining -= int64(n)
237
+ return n, err
238
+ }
239
+
240
+ // ValidateTaskIdentity bounds the confidential RPC/claim input before native SDK work.
241
+ // Prepare accepts an unfinished render; Execute additionally verifies its digests.
242
+ func ValidateTaskIdentity(input sohaapi.HelmExecutionTaskPayload) error {
243
+ s := input.Snapshot
244
+ for _, value := range []string{s.DeliveryPlanID, s.TargetID, s.ApplicationID, s.ApplicationEnvironmentID, s.ServiceID, s.ClusterID} {
245
+ if strings.TrimSpace(value) == "" || len(value) > 512 {
246
+ return errors.New("helm task identity is incomplete")
247
+ }
248
+ }
249
+ if !input.Action.Valid() || !helmName.MatchString(s.Namespace) || len(s.Namespace) > 63 || !helmName.MatchString(s.ReleaseName) || len(s.ReleaseName) > 53 || s.ExpectedRevision < 0 || s.RollbackRevision < 0 || s.TimeoutSeconds < 0 || s.TimeoutSeconds > 3600 {
250
+ return errors.New("helm task action, namespace, revision or timeout is invalid")
251
+ }
252
+ if len(s.Resources) > 300 {
253
+ return errors.New("helm task exceeds 300 resources")
254
+ }
255
+ if input.Prepared != nil {
256
+ prepared := input.Prepared
257
+ if len(prepared.ChartArchive) > 6990508 || len(prepared.Manifest) > 2<<20 || len(prepared.Hooks) > 50 {
258
+ return errors.New("helm preparation exceeds its content limits")
259
+ }
260
+ total := len(prepared.Manifest)
261
+ for _, hook := range prepared.Hooks {
262
+ total += len(hook)
263
+ }
264
+ encoded, err := json.Marshal(prepared.Values)
265
+ if err != nil || total > 2<<20 || len(encoded) > 1<<20 {
266
+ return errors.New("helm rendered content or values exceeds its limit")
267
+ }
268
+ }
269
+ encoded, err := json.Marshal(input)
270
+ if err != nil || len(encoded) > 8<<20 {
271
+ return errors.New("helm task exceeds the RPC limit")
272
+ }
273
+ return nil
274
+ }
275
+
276
+ // LegacyAllowedActions restricts delivery-owned releases to inspection through
277
+ // the legacy resource API. Nil leaves unmanaged releases to normal authorization.
278
+ func LegacyAllowedActions(labels map[string]string) []string {
279
+ if labels["soha-delivery-owner"] != "" {
280
+ return []string{"view"}
281
+ }
282
+ return nil
283
+ }
@@ -0,0 +1,102 @@
1
+ package helmrelease
2
+
3
+ import (
4
+ "archive/tar"
5
+ "bytes"
6
+ "compress/gzip"
7
+ "reflect"
8
+ "strings"
9
+ "testing"
10
+
11
+ "github.com/opensoha/soha-contracts/gen/go/sohaapi"
12
+ )
13
+
14
+ func TestHelmDeliveryBoundaries(t *testing.T) {
15
+ for _, repository := range []string{"https://charts.example.test", "https://charts.example.test/private/", "oci://registry.example.test/team"} {
16
+ if err := ValidateSource(sohaapi.DeploymentTemplateHelmSource{RepositoryURL: repository, Chart: "app", Version: "1.2.3-beta.1+build"}); err != nil {
17
+ t.Fatal(err)
18
+ }
19
+ }
20
+ for _, repository := range []string{"file:///tmp/chart", "http://example.test", "https://user:pass@example.test", "https://example.test?token=secret", "https://example.test/a/../b", "https://example.test/%2e%2e/b"} {
21
+ if err := ValidateSource(sohaapi.DeploymentTemplateHelmSource{RepositoryURL: repository, Chart: "app", Version: "1.2.3"}); err == nil {
22
+ t.Fatalf("accepted unsafe source %s", repository)
23
+ }
24
+ }
25
+ image := "registry.example.test/app@sha256:" + strings.Repeat("a", 64)
26
+ values := map[string]any{"workers": []any{map[string]any{"image": "old"}}}
27
+ mappings := []sohaapi.HelmImageMapping{{ContainerName: "main", Path: "/workers/0/image", Value: sohaapi.HelmImageMappingValueImage}}
28
+ result, err := MapImages(values, mappings, map[string]string{"main": image})
29
+ if err != nil || !reflect.DeepEqual(result, map[string]any{"workers": []any{map[string]any{"image": image}}}) || !reflect.DeepEqual(values, map[string]any{"workers": []any{map[string]any{"image": "old"}}}) {
30
+ t.Fatalf("image mapping did not preserve input: %v", err)
31
+ }
32
+ for _, invalid := range []string{"/workers/2/image", "/workers/01/image", "/workers/-/image", "/workers/0/image/child", "/bad~2/path"} {
33
+ mappings[0].Path = invalid
34
+ if _, err := MapImages(values, mappings, map[string]string{"main": image}); err == nil {
35
+ t.Fatalf("accepted invalid image path %s", invalid)
36
+ }
37
+ }
38
+ mappings[0].Path = "/image"
39
+ if _, err := MapImages(nil, mappings, map[string]string{"main": image}); err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ if _, err := MapImages(nil, mappings, map[string]string{"main": "registry/app:latest"}); err == nil {
43
+ t.Fatal("accepted mutable image")
44
+ }
45
+ valid := testChartArchive(t, []tar.Header{{Name: "app/Chart.yaml", Mode: 0600, Size: 1}})
46
+ if err := ValidateChartArchive(valid); err != nil {
47
+ t.Fatal(err)
48
+ }
49
+ for _, headers := range [][]tar.Header{
50
+ {{Name: "../Chart.yaml", Size: 1}},
51
+ {{Name: "app/Chart.yaml", Size: 1}, {Name: "app/Chart.yaml", Size: 1}},
52
+ {{Name: "app/Chart.yaml", Typeflag: tar.TypeSymlink, Linkname: "/etc/passwd"}},
53
+ {{Name: "app/Chart.yaml", Size: 1}, {Name: "app/large", Size: MaxChartExpandedBytes}},
54
+ {{Name: "app/Chart.yaml", Size: 1}, {Name: "app/crds/example.yaml", Size: 1}},
55
+ {{Name: "app/Chart.yaml", Size: 1}, {Name: "app/charts/child/crds/example.yaml", Size: 1}},
56
+ } {
57
+ if err := ValidateChartArchive(testChartArchive(t, headers)); err == nil {
58
+ t.Fatal("accepted unsafe archive")
59
+ }
60
+ }
61
+ budget, files := int64(100), 0
62
+ if err := validateChartArchive(valid, &budget, &files, 0); err == nil {
63
+ t.Fatal("expanded archive budget was not enforced")
64
+ }
65
+ for _, schema := range []string{
66
+ `{"$ref":"file:///etc/config.json"}`,
67
+ `{"properties":{"value":{"$ref":"https://example.test/schema"}}}`,
68
+ `{"allOf":[{"$dynamicRef":"../other.json#/$defs/value"}]}`,
69
+ `{"$schema":"https://example.test/dialect"}`,
70
+ } {
71
+ if err := validateValuesSchema([]byte(schema)); err == nil {
72
+ t.Fatalf("accepted external schema reference: %s", schema)
73
+ }
74
+ }
75
+ if err := validateValuesSchema([]byte(`{"$schema":"http://json-schema.org/draft-07/schema#","$defs":{"value":{"type":"string"}},"properties":{"value":{"$ref":"#/$defs/value"}}}`)); err != nil {
76
+ t.Fatal(err)
77
+ }
78
+ }
79
+
80
+ func testChartArchive(t *testing.T, headers []tar.Header) []byte {
81
+ t.Helper()
82
+ var buffer bytes.Buffer
83
+ zip := gzip.NewWriter(&buffer)
84
+ writer := tar.NewWriter(zip)
85
+ for _, header := range headers {
86
+ if err := writer.WriteHeader(&header); err != nil {
87
+ t.Fatal(err)
88
+ }
89
+ if header.Size > 0 {
90
+ if _, err := writer.Write(bytes.Repeat([]byte("x"), int(header.Size))); err != nil {
91
+ t.Fatal(err)
92
+ }
93
+ }
94
+ }
95
+ if err := writer.Close(); err != nil {
96
+ t.Fatal(err)
97
+ }
98
+ if err := zip.Close(); err != nil {
99
+ t.Fatal(err)
100
+ }
101
+ return buffer.Bytes()
102
+ }
@@ -0,0 +1,91 @@
1
+ package helmrelease
2
+
3
+ import (
4
+ "archive/tar"
5
+ "bytes"
6
+ "compress/gzip"
7
+ "errors"
8
+ "fmt"
9
+ "io"
10
+ "strconv"
11
+ "strings"
12
+
13
+ "sigs.k8s.io/yaml"
14
+ )
15
+
16
+ // FrozenChartArchive transports already rendered resources through native Helm
17
+ // install/upgrade/rollback, including hooks. It never evaluates source templates
18
+ // again. The original source digest remains part of the delivery snapshot.
19
+ func FrozenChartArchive(original []byte, manifest string, hooks []string) ([]byte, error) {
20
+ if len(manifest) > 2<<20 || len(hooks) > 50 {
21
+ return nil, errors.New("helm rendered resources exceed the snapshot limit")
22
+ }
23
+ total := len(manifest)
24
+ for _, hook := range hooks {
25
+ total += len(hook)
26
+ }
27
+ if total > 4<<20 {
28
+ return nil, errors.New("helm rendered hooks exceed the snapshot limit")
29
+ }
30
+ if err := ValidateChartArchive(original); err != nil {
31
+ return nil, err
32
+ }
33
+ metadata, err := chartMetadata(original)
34
+ if err != nil {
35
+ return nil, err
36
+ }
37
+ delete(metadata, "dependencies")
38
+ metadataBytes, err := yaml.Marshal(metadata)
39
+ if err != nil {
40
+ return nil, err
41
+ }
42
+ files := []struct{ name, content string }{{"Chart.yaml", string(metadataBytes)}, {"values.yaml", "{}\n"}, {"templates/resources.yaml", "{{ print " + strconv.Quote(manifest) + " }}"}}
43
+ for index, hook := range hooks {
44
+ files = append(files, struct{ name, content string }{fmt.Sprintf("templates/hook-%d.yaml", index), "{{ print " + strconv.Quote(hook) + " }}"})
45
+ }
46
+ var buffer bytes.Buffer
47
+ zip := gzip.NewWriter(&buffer)
48
+ writer := tar.NewWriter(zip)
49
+ for _, file := range files {
50
+ if err := writer.WriteHeader(&tar.Header{Name: "frozen/" + file.name, Mode: 0600, Size: int64(len(file.content))}); err != nil {
51
+ return nil, err
52
+ }
53
+ if _, err := io.WriteString(writer, file.content); err != nil {
54
+ return nil, err
55
+ }
56
+ }
57
+ if err := writer.Close(); err != nil {
58
+ return nil, err
59
+ }
60
+ if err := zip.Close(); err != nil {
61
+ return nil, err
62
+ }
63
+ return buffer.Bytes(), nil
64
+ }
65
+
66
+ func chartMetadata(content []byte) (map[string]any, error) {
67
+ zip, err := gzip.NewReader(bytes.NewReader(content))
68
+ if err != nil {
69
+ return nil, err
70
+ }
71
+ defer func() { _ = zip.Close() }()
72
+ reader := tar.NewReader(zip)
73
+ for {
74
+ header, err := reader.Next()
75
+ if err != nil {
76
+ return nil, errors.New("helm chart metadata is missing")
77
+ }
78
+ if strings.Count(header.Name, "/") != 1 || !strings.HasSuffix(header.Name, "/Chart.yaml") {
79
+ continue
80
+ }
81
+ content, err := io.ReadAll(reader)
82
+ if err != nil {
83
+ return nil, err
84
+ }
85
+ var metadata map[string]any
86
+ if err := yaml.Unmarshal(content, &metadata); err != nil {
87
+ return nil, errors.New("helm chart metadata is invalid")
88
+ }
89
+ return metadata, nil
90
+ }
91
+ }
@@ -0,0 +1,27 @@
1
+ package helmrelease
2
+
3
+ import (
4
+ "errors"
5
+
6
+ "github.com/opensoha/soha-contracts/gen/go/sohaapi"
7
+ )
8
+
9
+ // ValidateCompletedResult binds completion to the frozen plan and native revision.
10
+ func ValidateCompletedResult(payload sohaapi.HelmExecutionTaskPayload, result sohaapi.HelmExecutionTaskResult) error {
11
+ if !result.Stopped || result.RenderedDigest != payload.Snapshot.RenderedDigest || !helmSHA256.MatchString(result.RenderedDigest) {
12
+ return errors.New("helm completion must confirm executor stop and the approved render")
13
+ }
14
+ if payload.Action == sohaapi.Preflight {
15
+ if !result.Ready || result.Status != "preflighted" || result.Revision != payload.Snapshot.ExpectedRevision {
16
+ return errors.New("helm preflight must match the expected native revision")
17
+ }
18
+ return nil
19
+ }
20
+ if payload.Action != sohaapi.Apply && payload.Action != sohaapi.Observe || result.Status != "deployed" || result.Revision != payload.Snapshot.ExpectedRevision+1 {
21
+ return errors.New("helm completion must match the deployed native revision")
22
+ }
23
+ if payload.Action == sohaapi.Observe && !result.Ready {
24
+ return errors.New("helm health observation is not ready")
25
+ }
26
+ return nil
27
+ }