@opensoha/contracts 0.1.23 → 0.1.25

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 (415) hide show
  1. package/COMPATIBILITY.md +95 -0
  2. package/README.md +24 -6
  3. package/agent/environment/README.md +55 -0
  4. package/agent/environment/export.go +139 -0
  5. package/agent/environment/export_test.go +62 -0
  6. package/agent/environment/preflight.go +62 -0
  7. package/agent/environment/preflight_test.go +41 -0
  8. package/agent/environment/protocol.go +221 -0
  9. package/agent/environment/protocol_test.go +81 -0
  10. package/agent/environment-lease.schema.json +2 -2
  11. package/agent/environment-template.schema.json +1 -1
  12. package/agent/guest/browser_network.go +77 -0
  13. package/agent/guest/data.go +62 -0
  14. package/agent/guest/protocol.go +235 -0
  15. package/agent/guest/protocol_test.go +142 -0
  16. package/agent/guest/renewal.go +56 -0
  17. package/agent/guest/renewal_test.go +53 -0
  18. package/agent/guest/runtime.go +315 -0
  19. package/agent/guest/runtime_json.go +75 -0
  20. package/agent/guest/runtime_test.go +110 -0
  21. package/agent/personal-browser-network-request.schema.json +40 -0
  22. package/agent/personal-browser-network-response.schema.json +44 -0
  23. package/agent/personal-environment-export.schema.json +55 -0
  24. package/agent/personal-environment-preflight-request.schema.json +52 -0
  25. package/agent/personal-environment-preflight-result.schema.json +69 -0
  26. package/agent/personal-environment-request.schema.json +137 -0
  27. package/agent/personal-environment-result.schema.json +101 -0
  28. package/agent/personal-guest-binding.schema.json +92 -0
  29. package/agent/personal-guest-data-plan.schema.json +32 -0
  30. package/agent/personal-guest-enrollment.schema.json +21 -0
  31. package/agent/personal-guest-heartbeat.schema.json +44 -0
  32. package/agent/personal-guest-provisioning.schema.json +71 -0
  33. package/agent/personal-guest-renewal.schema.json +33 -0
  34. package/agent/personal-guest-runtime-result.schema.json +164 -0
  35. package/agent/personal-guest-runtime.schema.json +304 -0
  36. package/agent/personal-guest-tools.schema.json +75 -0
  37. package/auth/catalog_generated.go +2 -2
  38. package/auth/gateway_permissions_test.go +48 -0
  39. package/auth/permission-catalog.json +331 -1
  40. package/auth/token-claims.schema.json +5 -0
  41. package/compat/consumer-revisions.json +3 -2
  42. package/compat/json-schema-ecosystem-0.1.0.json +1 -1
  43. package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
  44. package/connectors/connector-action.schema.json +138 -0
  45. package/fixtures/json-schema/connector-action/invalid/empty-values.json +11 -0
  46. package/fixtures/json-schema/connector-action/invalid/invalid-expiry.json +16 -0
  47. package/fixtures/json-schema/connector-action/invalid/lowercase-alias.json +16 -0
  48. package/fixtures/json-schema/connector-action/invalid/unknown-entry-field.json +17 -0
  49. package/fixtures/json-schema/connector-action/invalid/unknown-frame-field.json +17 -0
  50. package/fixtures/json-schema/connector-action/invalid/unknown-version.json +16 -0
  51. package/fixtures/json-schema/connector-action/invalid/unversioned-reference.json +16 -0
  52. package/fixtures/json-schema/connector-action/invalid/zero-version.json +16 -0
  53. package/fixtures/json-schema/connector-action/valid/pinned.json +16 -0
  54. package/fixtures/json-schema/connector-definition/invalid/credential-userinfo.json +8 -0
  55. package/fixtures/json-schema/connector-definition/invalid/reference-traversal.json +8 -0
  56. package/fixtures/json-schema/connector-definition/invalid/zero-version.json +8 -0
  57. package/fixtures/json-schema/connector-definition/valid/canonical.json +8 -0
  58. package/fixtures/json-schema/connector-definition/valid/pinned.json +8 -0
  59. package/fixtures/json-schema/personal-browser-network-request/invalid/bounded.json +4 -0
  60. package/fixtures/json-schema/personal-browser-network-request/valid/bounded.json +4 -0
  61. package/fixtures/json-schema/personal-browser-network-response/invalid/bounded.json +5 -0
  62. package/fixtures/json-schema/personal-browser-network-response/valid/bounded.json +5 -0
  63. package/fixtures/json-schema/personal-environment-export/invalid/extra.json +11 -0
  64. package/fixtures/json-schema/personal-environment-export/invalid/ownerId.json +10 -0
  65. package/fixtures/json-schema/personal-environment-export/invalid/sha256.json +10 -0
  66. package/fixtures/json-schema/personal-environment-export/invalid/sizeBytes.json +10 -0
  67. package/fixtures/json-schema/personal-environment-export/valid/complete.json +10 -0
  68. package/fixtures/json-schema/personal-environment-preflight-request/invalid/disk.json +11 -0
  69. package/fixtures/json-schema/personal-environment-preflight-request/invalid/network.json +11 -0
  70. package/fixtures/json-schema/personal-environment-preflight-request/invalid/node.json +12 -0
  71. package/fixtures/json-schema/personal-environment-preflight-request/invalid/owner.json +12 -0
  72. package/fixtures/json-schema/personal-environment-preflight-request/valid/bounded.json +11 -0
  73. package/fixtures/json-schema/personal-environment-preflight-result/invalid/disk.json +15 -0
  74. package/fixtures/json-schema/personal-environment-preflight-result/invalid/network.json +15 -0
  75. package/fixtures/json-schema/personal-environment-preflight-result/invalid/no-hard-quota.json +15 -0
  76. package/fixtures/json-schema/personal-environment-preflight-result/invalid/node.json +16 -0
  77. package/fixtures/json-schema/personal-environment-preflight-result/invalid/not-ready.json +15 -0
  78. package/fixtures/json-schema/personal-environment-preflight-result/invalid/owner.json +16 -0
  79. package/fixtures/json-schema/personal-environment-preflight-result/valid/bounded.json +15 -0
  80. package/fixtures/json-schema/personal-environment-request/invalid/delete-data-argv.json +24 -0
  81. package/fixtures/json-schema/personal-environment-request/invalid/delete-data-createData.json +22 -0
  82. package/fixtures/json-schema/personal-environment-request/invalid/delete-data-taskId.json +22 -0
  83. package/fixtures/json-schema/personal-environment-request/invalid/export-argv.json +22 -0
  84. package/fixtures/json-schema/personal-environment-request/invalid/export-createData.json +22 -0
  85. package/fixtures/json-schema/personal-environment-request/invalid/export-taskId.json +22 -0
  86. package/fixtures/json-schema/personal-environment-request/invalid/extra-command.json +24 -0
  87. package/fixtures/json-schema/personal-environment-request/invalid/host-workspace.json +22 -0
  88. package/fixtures/json-schema/personal-environment-request/invalid/image-injection.json +22 -0
  89. package/fixtures/json-schema/personal-environment-request/invalid/network-egress.json +21 -0
  90. package/fixtures/json-schema/personal-environment-request/invalid/no-command.json +21 -0
  91. package/fixtures/json-schema/personal-environment-request/invalid/recreate-on-release.json +22 -0
  92. package/fixtures/json-schema/personal-environment-request/invalid/relative-command.json +24 -0
  93. package/fixtures/json-schema/personal-environment-request/invalid/released-identity.json +22 -0
  94. package/fixtures/json-schema/personal-environment-request/invalid/task-empty.json +27 -0
  95. package/fixtures/json-schema/personal-environment-request/invalid/task-host-path.json +27 -0
  96. package/fixtures/json-schema/personal-environment-request/invalid/task-noncanonical.json +27 -0
  97. package/fixtures/json-schema/personal-environment-request/invalid/task-null.json +27 -0
  98. package/fixtures/json-schema/personal-environment-request/invalid/task-on-inspection.json +22 -0
  99. package/fixtures/json-schema/personal-environment-request/invalid/unbounded-quota.json +21 -0
  100. package/fixtures/json-schema/personal-environment-request/valid/acquire.json +21 -0
  101. package/fixtures/json-schema/personal-environment-request/valid/delete-data.json +21 -0
  102. package/fixtures/json-schema/personal-environment-request/valid/execute.json +26 -0
  103. package/fixtures/json-schema/personal-environment-request/valid/export-data.json +21 -0
  104. package/fixtures/json-schema/personal-environment-request/valid/inspect.json +21 -0
  105. package/fixtures/json-schema/personal-environment-request/valid/provision.json +22 -0
  106. package/fixtures/json-schema/personal-environment-request/valid/release.json +21 -0
  107. package/fixtures/json-schema/personal-environment-request/valid/task-workspace.json +27 -0
  108. package/fixtures/json-schema/personal-environment-result/invalid/active-without-observation.json +7 -0
  109. package/fixtures/json-schema/personal-environment-result/invalid/deleted-output.json +8 -0
  110. package/fixtures/json-schema/personal-environment-result/invalid/deleted-snapshot.json +8 -0
  111. package/fixtures/json-schema/personal-environment-result/invalid/deleted-taskId.json +8 -0
  112. package/fixtures/json-schema/personal-environment-result/invalid/private-handle.json +8 -0
  113. package/fixtures/json-schema/personal-environment-result/invalid/stopped-fake-observation.json +20 -0
  114. package/fixtures/json-schema/personal-environment-result/invalid/task-empty.json +22 -0
  115. package/fixtures/json-schema/personal-environment-result/invalid/task-host-path.json +22 -0
  116. package/fixtures/json-schema/personal-environment-result/invalid/task-null.json +22 -0
  117. package/fixtures/json-schema/personal-environment-result/invalid/task-on-stopped.json +8 -0
  118. package/fixtures/json-schema/personal-environment-result/invalid/task-without-command-output.json +21 -0
  119. package/fixtures/json-schema/personal-environment-result/valid/active.json +21 -0
  120. package/fixtures/json-schema/personal-environment-result/valid/deleted.json +7 -0
  121. package/fixtures/json-schema/personal-environment-result/valid/expired.json +7 -0
  122. package/fixtures/json-schema/personal-environment-result/valid/missing.json +7 -0
  123. package/fixtures/json-schema/personal-environment-result/valid/released.json +7 -0
  124. package/fixtures/json-schema/personal-environment-result/valid/task-workspace.json +22 -0
  125. package/fixtures/json-schema/personal-guest-binding/invalid/host-path.json +17 -0
  126. package/fixtures/json-schema/personal-guest-binding/invalid/invalid-policy.json +17 -0
  127. package/fixtures/json-schema/personal-guest-binding/invalid/invalid-serial.json +17 -0
  128. package/fixtures/json-schema/personal-guest-binding/invalid/legacy-version.json +17 -0
  129. package/fixtures/json-schema/personal-guest-binding/invalid/missing-generation.json +17 -0
  130. package/fixtures/json-schema/personal-guest-binding/invalid/missing-identity.json +16 -0
  131. package/fixtures/json-schema/personal-guest-binding/invalid/missing-provider-identity.json +16 -0
  132. package/fixtures/json-schema/personal-guest-binding/invalid/raw-provider-identity.json +17 -0
  133. package/fixtures/json-schema/personal-guest-binding/valid/minimal.json +17 -0
  134. package/fixtures/json-schema/personal-guest-data-plan/invalid/quota.json +1 -0
  135. package/fixtures/json-schema/personal-guest-data-plan/valid/basic.json +1 -0
  136. package/fixtures/json-schema/personal-guest-enrollment/invalid/capabilities.json +7 -0
  137. package/fixtures/json-schema/personal-guest-enrollment/invalid/legacy-version.json +4 -0
  138. package/fixtures/json-schema/personal-guest-enrollment/invalid/missing-identity.json +3 -0
  139. package/fixtures/json-schema/personal-guest-enrollment/invalid/ownerId.json +5 -0
  140. package/fixtures/json-schema/personal-guest-enrollment/invalid/token.json +5 -0
  141. package/fixtures/json-schema/personal-guest-enrollment/valid/minimal.json +4 -0
  142. package/fixtures/json-schema/personal-guest-heartbeat/invalid/argv.json +11 -0
  143. package/fixtures/json-schema/personal-guest-heartbeat/invalid/legacy-version.json +8 -0
  144. package/fixtures/json-schema/personal-guest-heartbeat/invalid/missing-identity.json +7 -0
  145. package/fixtures/json-schema/personal-guest-heartbeat/invalid/owner.json +9 -0
  146. package/fixtures/json-schema/personal-guest-heartbeat/invalid/self-ready.json +9 -0
  147. package/fixtures/json-schema/personal-guest-heartbeat/invalid/zero-sequence.json +8 -0
  148. package/fixtures/json-schema/personal-guest-heartbeat/valid/minimal.json +8 -0
  149. package/fixtures/json-schema/personal-guest-provisioning/invalid/network.json +1 -0
  150. package/fixtures/json-schema/personal-guest-provisioning/valid/basic.json +1 -0
  151. package/fixtures/json-schema/personal-guest-renewal/invalid/unknown.json +1 -0
  152. package/fixtures/json-schema/personal-guest-renewal/valid/basic.json +1 -0
  153. package/fixtures/json-schema/personal-guest-runtime/invalid/minimal.json +45 -0
  154. package/fixtures/json-schema/personal-guest-runtime/valid/minimal.json +45 -0
  155. package/fixtures/json-schema/personal-guest-runtime-result/invalid/minimal.json +28 -0
  156. package/fixtures/json-schema/personal-guest-runtime-result/valid/minimal.json +27 -0
  157. package/fixtures/json-schema/personal-guest-tools/invalid/minimal.json +31 -0
  158. package/fixtures/json-schema/personal-guest-tools/valid/minimal.json +30 -0
  159. package/fixtures/json-schema/source-import-policy/invalid/case-variant-scope.json +4 -0
  160. package/fixtures/json-schema/source-import-policy/invalid/case-variant-version.json +4 -0
  161. package/fixtures/json-schema/source-import-policy/invalid/duplicate-grant.json +4 -0
  162. package/fixtures/json-schema/source-import-policy/invalid/empty-id.json +4 -0
  163. package/fixtures/json-schema/source-import-policy/invalid/future-version.json +4 -0
  164. package/fixtures/json-schema/source-import-policy/invalid/unknown-field.json +4 -0
  165. package/fixtures/json-schema/source-import-policy/valid/deny-all.json +4 -0
  166. package/fixtures/json-schema/source-import-policy/valid/shared.json +4 -0
  167. package/fixtures/json-schema/token-claims/invalid/authz-negative.json +23 -0
  168. package/fixtures/json-schema/token-claims/invalid/authz-string.json +23 -0
  169. package/fixtures/json-schema/token-claims/invalid/authz-zero.json +23 -0
  170. package/fixtures/json-schema/token-claims/valid/versioned-session.json +23 -0
  171. package/fixtures/openapi/AgentRunEventListEnvelope/invalid/basic.json +8 -0
  172. package/fixtures/openapi/AgentRunEventListEnvelope/valid/basic.json +8 -0
  173. package/fixtures/openapi/AgentRunEventListEnvelope/valid/native.json +1 -0
  174. package/fixtures/openapi/AgentRunResumeRequest/invalid/basic.json +3 -0
  175. package/fixtures/openapi/AgentRunResumeRequest/valid/basic.json +3 -0
  176. package/fixtures/openapi/AgentSSHCommandRequest/invalid/task51.json +24 -0
  177. package/fixtures/openapi/AgentSSHCommandRequest/valid/task51.json +23 -0
  178. package/fixtures/openapi/AgentSSHCommandResult/invalid/task51.json +8 -0
  179. package/fixtures/openapi/AgentSSHCommandResult/valid/task51.json +7 -0
  180. package/fixtures/openapi/AgentSSHSessionRequest/invalid/task51.json +20 -0
  181. package/fixtures/openapi/AgentSSHSessionRequest/valid/task51.json +19 -0
  182. package/fixtures/openapi/CapabilityTaskInput/invalid/duplicate-scope.json +36 -0
  183. package/fixtures/openapi/CapabilityTaskInput/valid/scoped.json +32 -0
  184. package/fixtures/openapi/DockerQuickCreateHostInput/invalid/guest-access.json +7 -0
  185. package/fixtures/openapi/DockerQuickCreateHostInput/valid/guest-access.json +9 -0
  186. package/fixtures/openapi/KnowledgeSourceDocument/invalid/tombstone-with-content.json +1 -0
  187. package/fixtures/openapi/KnowledgeSourceDocument/valid/tombstone.json +1 -0
  188. package/fixtures/openapi/KubernetesAgentActionCheck/invalid/unproven-state.json +6 -0
  189. package/fixtures/openapi/KubernetesAgentActionCheck/valid/configured-denied.json +9 -0
  190. package/fixtures/openapi/KubernetesAgentActionProfile/invalid/wildcard.json +1 -0
  191. package/fixtures/openapi/KubernetesAgentActionProfile/valid/workbench.json +1 -0
  192. package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/action-profile.json +3 -0
  193. package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/action-profile.json +5 -0
  194. package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/condition-status-boolean.json +17 -0
  195. package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/taint-effect-missing.json +16 -0
  196. package/fixtures/openapi/KubernetesNodeListEnvelope/valid/legacy-fields-absent.json +11 -0
  197. package/fixtures/openapi/KubernetesNodeListEnvelope/valid/no-taints.json +13 -0
  198. package/fixtures/openapi/KubernetesNodeListEnvelope/valid/ready-cordoned-tainted.json +28 -0
  199. package/fixtures/openapi/KubernetesNodeListEnvelope/valid/unknown-fields-null.json +13 -0
  200. package/fixtures/openapi/KubernetesResourceUpdatePlanRequest/invalid/missing-required.json +1 -0
  201. package/fixtures/openapi/KubernetesResourceYaml/invalid/missing-required.json +1 -0
  202. package/fixtures/openapi/KubernetesTopologyCoverage/invalid/empty-as-success.json +5 -0
  203. package/fixtures/openapi/KubernetesTopologyCoverage/valid/not-installed.json +6 -0
  204. package/fixtures/openapi/KubernetesTopologyGraphEdge/invalid/negative-weight.json +8 -0
  205. package/fixtures/openapi/KubernetesTopologyGraphEdge/valid/zero-weight.json +20 -0
  206. package/fixtures/openapi/KubernetesTopologyGraphNode/invalid/fake-flow-kind.json +42 -0
  207. package/fixtures/openapi/KubernetesTopologyGraphNode/valid/rule.json +42 -0
  208. package/fixtures/openapi/KubernetesTopologySnapshot/invalid/raw-source-field.json +33 -0
  209. package/fixtures/openapi/KubernetesTopologySnapshot/valid/projected-source.json +32 -0
  210. package/fixtures/openapi/KubernetesTopologySnapshotRequest/invalid/arbitrary-gvr.json +8 -0
  211. package/fixtures/openapi/KubernetesTopologySnapshotRequest/valid/bounded-sources.json +11 -0
  212. package/fixtures/openapi/KubernetesTopologySourceQuery/invalid/cluster-namespace.json +4 -0
  213. package/fixtures/openapi/KubernetesTopologySourceQuery/valid/all-namespaces.json +4 -0
  214. package/fixtures/openapi/ObservabilityDataSource/invalid/missing-required.json +1 -0
  215. package/fixtures/openapi/PersonalAgentAccess/invalid/invalid.json +1 -0
  216. package/fixtures/openapi/PersonalAgentAccess/valid/valid.json +1 -0
  217. package/fixtures/openapi/PersonalAgentAccessDecision/invalid/invalid.json +1 -0
  218. package/fixtures/openapi/PersonalAgentAccessDecision/valid/valid.json +1 -0
  219. package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/invalid/owner.json +8 -0
  220. package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/valid/side-chat.json +9 -0
  221. package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/motion.json +1 -0
  222. package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/owner.json +1 -0
  223. package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/remote.json +1 -0
  224. package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/svg.json +1 -0
  225. package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/raster.json +1 -0
  226. package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/reset.json +1 -0
  227. package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/empty.json +1 -0
  228. package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/owner.json +1 -0
  229. package/fixtures/openapi/PersonalAssistantUpdateRequest/valid/name.json +1 -0
  230. package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/host-url.json +12 -0
  231. package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/lifetime.json +11 -0
  232. package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/unknown-agent.json +11 -0
  233. package/fixtures/openapi/PersonalDockerEnvironmentPolicy/valid/basic.json +11 -0
  234. package/fixtures/openapi/PersonalEnvironment/invalid/invented-state.json +6 -0
  235. package/fixtures/openapi/PersonalEnvironment/invalid/missing-state.json +5 -0
  236. package/fixtures/openapi/PersonalEnvironment/invalid/negative-generation.json +7 -0
  237. package/fixtures/openapi/PersonalEnvironment/invalid/network.json +23 -0
  238. package/fixtures/openapi/PersonalEnvironment/invalid/private-node.json +24 -0
  239. package/fixtures/openapi/PersonalEnvironment/invalid/unsupported-backend.json +7 -0
  240. package/fixtures/openapi/PersonalEnvironment/valid/deleted.json +23 -0
  241. package/fixtures/openapi/PersonalEnvironment/valid/deleting.json +23 -0
  242. package/fixtures/openapi/PersonalEnvironment/valid/deletion_unknown.json +23 -0
  243. package/fixtures/openapi/PersonalEnvironment/valid/frozen-binding.json +13 -0
  244. package/fixtures/openapi/PersonalEnvironment/valid/ready.json +23 -0
  245. package/fixtures/openapi/PersonalEnvironment/valid/unknown.json +23 -0
  246. package/fixtures/openapi/PersonalEnvironment/valid/unprovisioned.json +6 -0
  247. package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/command.json +3 -0
  248. package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/node.json +4 -0
  249. package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/owner.json +4 -0
  250. package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/quota.json +10 -0
  251. package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/reconcile.json +3 -0
  252. package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/sleep.json +3 -0
  253. package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/wake.json +4 -0
  254. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/empty-name.json +5 -0
  255. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/long-name.json +5 -0
  256. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/missing-name.json +4 -0
  257. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/owner.json +6 -0
  258. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/stale-version.json +5 -0
  259. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/valid/confirmed.json +5 -0
  260. package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/assistantId.json +4 -0
  261. package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/environmentVersion.json +4 -0
  262. package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/ownerId.json +5 -0
  263. package/fixtures/openapi/PersonalEnvironmentDataExportRequest/valid/current.json +4 -0
  264. package/fixtures/openapi/PersonalEnvironmentEnvelope/invalid/missing-data.json +1 -0
  265. package/fixtures/openapi/PersonalEnvironmentEnvelope/valid/state.json +8 -0
  266. package/fixtures/openapi/PersonalEnvironmentQuota/invalid/cpu.json +7 -0
  267. package/fixtures/openapi/PersonalEnvironmentQuota/invalid/disk.json +7 -0
  268. package/fixtures/openapi/PersonalEnvironmentQuota/invalid/network.json +7 -0
  269. package/fixtures/openapi/PersonalEnvironmentQuota/invalid/processes.json +7 -0
  270. package/fixtures/openapi/PersonalEnvironmentQuota/valid/basic.json +7 -0
  271. package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/mode.json +13 -0
  272. package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/network.json +13 -0
  273. package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/node.json +14 -0
  274. package/fixtures/openapi/PersonalEnvironmentTemplate/valid/template.json +13 -0
  275. package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/missing.json +1 -0
  276. package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/unbounded.json +1317 -0
  277. package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/choices.json +17 -0
  278. package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/empty.json +3 -0
  279. package/fixtures/openapi/PersonalEnvironmentUsage/invalid/negative.json +6 -0
  280. package/fixtures/openapi/PersonalEnvironmentUsage/valid/usage.json +6 -0
  281. package/fixtures/openapi/PersonalFeedFeedback/invalid/owner.json +4 -0
  282. package/fixtures/openapi/PersonalFeedFeedback/valid/liked.json +3 -0
  283. package/fixtures/openapi/PersonalFeedItem/invalid/no-source.json +9 -0
  284. package/fixtures/openapi/PersonalFeedItem/valid/post.json +15 -0
  285. package/fixtures/openapi/PersonalFeedSettings/invalid/owner.json +6 -0
  286. package/fixtures/openapi/PersonalFeedSettings/valid/default.json +5 -0
  287. package/fixtures/openapi/PersonalGuestPreparationInput/invalid/foreign-owner.json +5 -0
  288. package/fixtures/openapi/PersonalGuestPreparationInput/invalid/unsafe-counter.json +4 -0
  289. package/fixtures/openapi/PersonalGuestPreparationInput/valid/ed25519-csr.json +4 -0
  290. package/fixtures/openapi/PersonalGuestRuntimeEnvelope/invalid/minimal.json +10 -0
  291. package/fixtures/openapi/PersonalGuestRuntimeEnvelope/valid/minimal.json +9 -0
  292. package/fixtures/openapi/PersonalGuestRuntimeInput/invalid/minimal.json +6 -0
  293. package/fixtures/openapi/PersonalGuestRuntimeInput/valid/minimal.json +8 -0
  294. package/fixtures/openapi/PersonalGuestToolsInput/invalid/minimal.json +5 -0
  295. package/fixtures/openapi/PersonalGuestToolsInput/valid/minimal.json +4 -0
  296. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/device.json +15 -0
  297. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/fractional-size.json +15 -0
  298. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/network.json +15 -0
  299. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/owner.json +15 -0
  300. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/provider.json +15 -0
  301. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/unknown.json +16 -0
  302. package/fixtures/openapi/PersonalVMEnvironmentPolicy/valid/pve.json +15 -0
  303. package/fixtures/openapi/PersonalWorkspaceRuntime/invalid/shared.json +3 -0
  304. package/fixtures/openapi/PersonalWorkspaceRuntime/valid/shared.json +4 -0
  305. package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/invalid/shared.json +5 -0
  306. package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/valid/shared.json +6 -0
  307. package/fixtures/openapi/ResourceMonitoringPanel/invalid/unknown-quality.json +7 -0
  308. package/fixtures/openapi/ResourceMonitoringPanel/valid/not-collected.json +7 -0
  309. package/fixtures/openapi/ResourceMonitoringPoint/invalid/string-zero.json +4 -0
  310. package/fixtures/openapi/ResourceMonitoringPoint/valid/missing.json +4 -0
  311. package/fixtures/openapi/ResourceMonitoringPoint/valid/zero.json +4 -0
  312. package/fixtures/openapi/ResourceMonitoringView/invalid/raw-query.json +12 -0
  313. package/fixtures/openapi/ResourceMonitoringView/valid/unconfigured.json +12 -0
  314. package/fixtures/openapi/SecretGenerateSSHKeyRequest/invalid/task51.json +6 -0
  315. package/fixtures/openapi/SecretGenerateSSHKeyRequest/valid/task51.json +12 -0
  316. package/fixtures/openapi/SecretReferenceUsage/invalid/version.json +6 -0
  317. package/fixtures/openapi/SecretReferenceUsage/valid/version.json +8 -0
  318. package/fixtures/openapi/VMSSHCredentialRedemptionInput/invalid/task51.json +7 -0
  319. package/fixtures/openapi/VMSSHCredentialRedemptionInput/valid/task51.json +6 -0
  320. package/fixtures/openapi/VirtualMachineAccessBindingInput/invalid/task51.json +10 -0
  321. package/fixtures/openapi/VirtualMachineAccessBindingInput/valid/task51.json +10 -0
  322. package/fixtures/openapi/VirtualMachineActionInput/invalid/capacity-not-boolean.json +1 -0
  323. package/fixtures/openapi/VirtualMachineActionInput/valid/new-data-with-admission.json +1 -0
  324. package/fixtures/openapi/VirtualMachineCreateInput/valid/cloud-image.json +12 -0
  325. package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-config-injection.json +1 -0
  326. package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-existing-disk.json +1 -0
  327. package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-too-long.json +1 -0
  328. package/fixtures/openapi/VirtualMachineDiskChange/valid/personal-data.json +1 -0
  329. package/fixtures/openapi/VirtualMachineGuestAccessInput/invalid/task51.json +4 -0
  330. package/fixtures/openapi/VirtualMachineGuestAccessInput/valid/task51.json +6 -0
  331. package/fixtures/openapi/VirtualMachineSSHCommand/invalid/task51.json +9 -0
  332. package/fixtures/openapi/VirtualMachineSSHCommand/valid/task51.json +8 -0
  333. package/fixtures/openapi/VirtualMachineSSHCommandInput/invalid/task51.json +5 -0
  334. package/fixtures/openapi/VirtualMachineSSHCommandInput/valid/task51.json +5 -0
  335. package/fixtures/openapi/VirtualMachineSSHFrame/invalid/encoding.json +5 -0
  336. package/fixtures/openapi/VirtualMachineSSHFrame/invalid/task51.json +4 -0
  337. package/fixtures/openapi/VirtualMachineSSHFrame/valid/binary.json +5 -0
  338. package/fixtures/openapi/VirtualMachineSSHFrame/valid/task51.json +5 -0
  339. package/fixtures/openapi/VirtualMachineSSHSessionInput/invalid/task51.json +4 -0
  340. package/fixtures/openapi/VirtualMachineSSHSessionInput/valid/task51.json +5 -0
  341. package/fixtures/openapi/VirtualizationCapacityPolicyInput/invalid/task51.json +12 -0
  342. package/fixtures/openapi/VirtualizationCapacityPolicyInput/valid/task51.json +13 -0
  343. package/fixtures/openapi/VirtualizationImageInput/invalid/cloud-image-config-type.json +6 -0
  344. package/fixtures/openapi/VirtualizationImageInput/valid/cloud-image.json +11 -0
  345. package/fixtures/openapi/WorkbenchAssistantTask/invalid/kind.json +1 -0
  346. package/fixtures/openapi/WorkbenchAssistantTask/invalid/owner.json +1 -0
  347. package/fixtures/openapi/WorkbenchAssistantTask/valid/generate.json +1 -0
  348. package/fixtures/openapi/WorkbenchAssistantTask/valid/undo.json +1 -0
  349. package/fixtures/openapi/WorkbenchContextSelection/invalid/remote-image.json +1 -0
  350. package/fixtures/openapi/WorkbenchContextSelection/invalid/too-many-images.json +1 -0
  351. package/fixtures/openapi/WorkbenchContextSelection/valid/chat-image.json +9 -0
  352. package/fixtures/openapi/WorkbenchContextUsage/invalid/fractional-capacity.json +1 -0
  353. package/fixtures/openapi/WorkbenchContextUsage/invalid/negative.json +1 -0
  354. package/fixtures/openapi/WorkbenchContextUsage/valid/reported.json +1 -0
  355. package/fixtures/openapi/WorkbenchContextUsage/valid/unknown.json +1 -0
  356. package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/owner.json +1 -0
  357. package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/provider.json +1 -0
  358. package/fixtures/openapi/WorkbenchMainSessionRequest/valid/empty.json +1 -0
  359. package/fixtures/openapi/WorkbenchMainSessionRequest/valid/provider.json +1 -0
  360. package/fixtures/openapi/WorkbenchMessageSearchPage/invalid/role.json +12 -0
  361. package/fixtures/openapi/WorkbenchMessageSearchPage/valid/page.json +12 -0
  362. package/fixtures/openapi/WorkbenchMessageWindow/invalid/missing.json +1 -0
  363. package/fixtures/openapi/WorkbenchMessageWindow/valid/empty.json +3 -0
  364. package/fixtures/openapi/WorkbenchModelOption/invalid/zero-capacity.json +1 -0
  365. package/fixtures/openapi/WorkbenchModelOption/valid/capacity.json +1 -0
  366. package/fixtures/openapi/WorkbenchModelOption/valid/unknown.json +1 -0
  367. package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/invalid/basic.json +7 -0
  368. package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/basic.json +11 -0
  369. package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/selected-profile.json +23 -0
  370. package/fixtures/openapi/WorkbenchSendMessageStreamRequest/invalid/missing-required.json +1 -0
  371. package/fixtures/openapi/WorkbenchSendMessageStreamRequest/valid/mixed-professional-task.json +22 -0
  372. package/fixtures/openapi/WorkbenchTaskIntent/invalid/basic.json +5 -0
  373. package/fixtures/openapi/WorkbenchTaskIntent/invalid/oversized-capability-source.json +3 -0
  374. package/fixtures/openapi/WorkbenchTaskIntent/valid/basic.json +9 -0
  375. package/fixtures/openapi/WorkbenchTaskIntent/valid/independent-sources.json +17 -0
  376. package/gen/go/sohaapi/client.go +1 -1
  377. package/gen/go/sohaapi/kubernetes_monitoring_test.go +67 -0
  378. package/gen/go/sohaapi/personal_environment_test.go +25 -0
  379. package/gen/go/sohaapi/types.go +3460 -433
  380. package/gen/go/sohaapi/vm_disk_test.go +21 -0
  381. package/gen/ts/sohaapi/index.ts +5454 -1397
  382. package/go.mod +61 -49
  383. package/go.sum +202 -176
  384. package/knowledge/connector-definition.schema.json +6 -1
  385. package/knowledge/source-import-policy.schema.json +32 -0
  386. package/memory/memory-record.schema.json +1 -1
  387. package/openapi/soha-api.json +24199 -16810
  388. package/openapi/soha-api.yaml +2537 -14
  389. package/openapi/spec_generated.go +3 -3
  390. package/openapi/spec_test.go +34 -0
  391. package/package.json +19 -3
  392. package/resource/agent_access.go +12 -0
  393. package/resource/agent_profiles.go +62 -0
  394. package/resource/agent_profiles_test.go +36 -0
  395. package/resource/kubernetes_kinds.go +73 -0
  396. package/resource/kubernetes_kinds_test.go +38 -0
  397. package/resource/models.go +23 -14
  398. package/resource/network_topology.go +166 -0
  399. package/resource/network_topology_projection.go +208 -0
  400. package/resource/network_topology_projection_test.go +53 -0
  401. package/resource/network_topology_test.go +51 -0
  402. package/resource/network_topology_validation.go +29 -0
  403. package/resource/node_drain.go +34 -0
  404. package/resource/resource_monitoring.go +99 -0
  405. package/resource/runtime/network_topology.go +232 -0
  406. package/resource/runtime/network_topology_test.go +62 -0
  407. package/resource/runtime/node_drain.go +124 -0
  408. package/resource/runtime/node_drain_test.go +95 -0
  409. package/resource/runtime/resource_edit.go +128 -0
  410. package/resource/runtime/resource_edit_test.go +183 -0
  411. package/resource/runtime/resource_monitoring.go +243 -0
  412. package/resource/runtime/resource_monitoring_test.go +88 -0
  413. /package/fixtures/openapi/DeliveryExecutionHistoryPage/{invalid-kind.json → invalid/invalid-kind.json} +0 -0
  414. /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-build.json → valid/valid-build.json} +0 -0
  415. /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-empty.json → valid/valid-empty.json} +0 -0
@@ -0,0 +1,183 @@
1
+ package runtime
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "errors"
7
+ "fmt"
8
+ "testing"
9
+
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
+ k8sruntime "k8s.io/apimachinery/pkg/runtime"
14
+ "k8s.io/apimachinery/pkg/runtime/schema"
15
+ "k8s.io/apimachinery/pkg/types"
16
+ "k8s.io/apimachinery/pkg/util/validation/field"
17
+ dynamicfake "k8s.io/client-go/dynamic/fake"
18
+ clienttesting "k8s.io/client-go/testing"
19
+ )
20
+
21
+ const testEditManager = "opensoha-resource-edit/v1"
22
+
23
+ func editFixture() *unstructured.Unstructured {
24
+ item := &unstructured.Unstructured{Object: map[string]any{"apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]any{"name": "sample", "namespace": "demo", "uid": "original", "resourceVersion": "1"}, "data": map[string]any{"phase": "created"}}}
25
+ item.SetManagedFields([]metav1.ManagedFieldsEntry{{Manager: testEditManager, Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "v1", FieldsType: "FieldsV1", FieldsV1: metav1.NewFieldsV1(`{"f:data":{"f:phase":{}}}`)}})
26
+ return item
27
+ }
28
+
29
+ func editConflict(manager string) error {
30
+ return apierrors.NewApplyConflict([]metav1.StatusCause{{Type: metav1.CauseTypeFieldManagerConflict, Field: ".data.phase", Message: fmt.Sprintf("conflict with %q using v1", manager)}}, "field conflict")
31
+ }
32
+
33
+ func TestApplyResourceEditMigratesOnlyItsOwnCreationFields(t *testing.T) {
34
+ for _, test := range []struct {
35
+ name string
36
+ dryRun, foreign, race, retryFail, probePass, portInvalid bool
37
+ }{
38
+ {name: "write"}, {name: "dry-run", dryRun: true}, {name: "successful-ssa-preview", dryRun: true, probePass: true}, {name: "port-list-preview", dryRun: true, portInvalid: true},
39
+ {name: "external", foreign: true}, {name: "external-dry-run", foreign: true, dryRun: true},
40
+ {name: "concurrent-migration", race: true}, {name: "concurrent-retry", retryFail: true},
41
+ } {
42
+ t.Run(test.name, func(t *testing.T) {
43
+ live := editFixture()
44
+ if test.portInvalid {
45
+ live.SetKind("Service")
46
+ }
47
+ if test.foreign {
48
+ owners := live.GetManagedFields()
49
+ owners = append(owners, metav1.ManagedFieldsEntry{Manager: "helm", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "v1", FieldsType: "FieldsV1", FieldsV1: metav1.NewFieldsV1(`{"f:data":{"f:external":{}}}`)})
50
+ live.SetManagedFields(owners)
51
+ }
52
+ desired := live.DeepCopy()
53
+ desired.SetManagedFields(nil)
54
+ desired.Object["data"] = map[string]any{"phase": "updated"}
55
+ client := dynamicfake.NewSimpleDynamicClient(k8sruntime.NewScheme(), live)
56
+ applyCalls, migrations, updates := 0, 0, 0
57
+ client.PrependReactor("patch", "configmaps", func(action clienttesting.Action) (bool, k8sruntime.Object, error) {
58
+ patch, ok := action.(clienttesting.PatchActionImpl)
59
+ if !ok {
60
+ t.Fatalf("unexpected action %T", action)
61
+ }
62
+ if patch.GetPatchType() == types.JSONPatchType {
63
+ migrations++
64
+ assertEditMigration(t, patch.GetPatch(), test.foreign)
65
+ if test.race {
66
+ return true, nil, apierrors.NewConflict(schema.GroupResource{Resource: "configmaps"}, "sample", errors.New("concurrent update"))
67
+ }
68
+ moved := live.DeepCopy()
69
+ moved.SetResourceVersion("2")
70
+ return true, moved, nil
71
+ }
72
+ applyCalls++
73
+ options := patch.GetPatchOptions()
74
+ if options.Force == nil || *options.Force || options.FieldManager != testEditManager || (len(options.DryRun) > 0) != test.dryRun {
75
+ t.Fatalf("unsafe apply options: %+v", options)
76
+ }
77
+ if test.probePass {
78
+ return true, desired, nil
79
+ }
80
+ if test.portInvalid {
81
+ return true, nil, apierrors.NewInvalid(schema.GroupKind{Kind: "Service"}, "sample", field.ErrorList{field.Required(field.NewPath("spec", "ports").Index(0).Child("name"), "")})
82
+ }
83
+ if test.dryRun || test.foreign {
84
+ manager := testEditManager
85
+ if test.foreign {
86
+ manager = "helm"
87
+ }
88
+ return true, nil, editConflict(manager)
89
+ }
90
+ if test.retryFail {
91
+ return true, nil, editConflict("external-controller")
92
+ }
93
+ var body unstructured.Unstructured
94
+ if err := json.Unmarshal(patch.GetPatch(), &body); err != nil || body.GetResourceVersion() != "2" || body.GetUID() != live.GetUID() {
95
+ t.Fatalf("retry lost observed identity: %s %v", patch.GetPatch(), err)
96
+ }
97
+ return true, &body, nil
98
+ })
99
+ client.PrependReactor("update", "configmaps", func(action clienttesting.Action) (bool, k8sruntime.Object, error) {
100
+ update, ok := action.(clienttesting.UpdateActionImpl)
101
+ if !ok {
102
+ t.Fatalf("unexpected update %T", action)
103
+ }
104
+ options := update.GetUpdateOptions()
105
+ if !test.dryRun || len(options.DryRun) != 1 || options.DryRun[0] != metav1.DryRunAll || options.FieldManager != testEditManager {
106
+ t.Fatalf("migration validation wrote live data: %+v", options)
107
+ }
108
+ updates++
109
+ return true, update.GetObject(), nil
110
+ })
111
+ resource := client.Resource(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}).Namespace("demo")
112
+ _, err := ApplyResourceEdit(context.Background(), resource, live, desired, testEditManager, test.dryRun)
113
+ if (err != nil) != (test.foreign || test.race || test.retryFail) {
114
+ t.Fatalf("result: %v", err)
115
+ }
116
+ wantMigrations, wantUpdates, wantApply := 1, 0, 1
117
+ if test.foreign {
118
+ wantMigrations, wantApply = 1, 1
119
+ }
120
+ if test.dryRun {
121
+ wantMigrations, wantApply = 0, 1
122
+ if !test.foreign && !test.probePass {
123
+ wantUpdates = 1
124
+ }
125
+ }
126
+ if test.race {
127
+ wantApply = 0
128
+ }
129
+ if migrations != wantMigrations || updates != wantUpdates || applyCalls != wantApply || desired.GetResourceVersion() != "1" {
130
+ t.Fatalf("apply=%d migrate=%d validate=%d; caller RV=%s", applyCalls, migrations, updates, desired.GetResourceVersion())
131
+ }
132
+ })
133
+ }
134
+ }
135
+
136
+ func assertEditMigration(t *testing.T, payload []byte, foreign bool) {
137
+ t.Helper()
138
+ var ops []struct {
139
+ Op, Path string
140
+ Value json.RawMessage
141
+ }
142
+ if err := json.Unmarshal(payload, &ops); err != nil || len(ops) != 3 {
143
+ t.Fatalf("invalid migration: %s %v", payload, err)
144
+ }
145
+ if ops[0].Op != "test" || ops[0].Path != "/metadata/uid" || string(ops[0].Value) != `"original"` || ops[2].Path != "/metadata/resourceVersion" || string(ops[2].Value) != `"1"` {
146
+ t.Fatalf("migration lacks UID/RV protection: %s", payload)
147
+ }
148
+ var owners []metav1.ManagedFieldsEntry
149
+ if err := json.Unmarshal(ops[1].Value, &owners); err != nil || len(owners) < 1 || owners[0].Manager != testEditManager || owners[0].Operation != metav1.ManagedFieldsOperationApply {
150
+ t.Fatalf("unexpected ownership migration: %s %v", payload, err)
151
+ }
152
+ if foreign && (len(owners) != 2 || owners[1].Manager != "helm" || owners[1].Operation != metav1.ManagedFieldsOperationApply || owners[1].FieldsV1.GetRawString() != `{"f:data":{"f:external":{}}}`) {
153
+ t.Fatal("migration changed external ownership")
154
+ }
155
+ }
156
+
157
+ func TestResourceEditMigrationFailsClosed(t *testing.T) {
158
+ for _, missing := range []string{"uid", "resourceVersion"} {
159
+ live := editFixture()
160
+ unstructured.RemoveNestedField(live.Object, "metadata", missing)
161
+ if patch, err := resourceEditMigrationPatch(live, testEditManager); err != nil || patch != nil {
162
+ t.Fatalf("migrated without %s: %s %v", missing, patch, err)
163
+ }
164
+ }
165
+ for _, manager := range []string{"helm", testEditManager + "-other", testEditManager + `" using v1`} {
166
+ if onlyResourceEditConflicts(editFixture(), editConflict(manager), testEditManager) {
167
+ t.Fatalf("misidentified foreign manager %q", manager)
168
+ }
169
+ }
170
+ for _, entry := range []metav1.ManagedFieldsEntry{
171
+ {Manager: testEditManager, Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "v1beta1"},
172
+ {Manager: testEditManager, Operation: metav1.ManagedFieldsOperationApply, APIVersion: "v1beta1"},
173
+ } {
174
+ live := editFixture()
175
+ live.SetManagedFields(append(live.GetManagedFields(), entry))
176
+ if patch, err := resourceEditMigrationPatch(live, testEditManager); err != nil || patch != nil {
177
+ t.Fatalf("discarded another version's ownership: %s %v", patch, err)
178
+ }
179
+ }
180
+ if onlyResourceEditConflicts(editFixture(), apierrors.NewApplyConflict(nil, "unknown conflict"), testEditManager) {
181
+ t.Fatal("accepted incomplete conflict evidence")
182
+ }
183
+ }
@@ -0,0 +1,243 @@
1
+ package runtime
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "math"
9
+ "sort"
10
+ "time"
11
+
12
+ cr "github.com/opensoha/soha-contracts/resource"
13
+ corev1 "k8s.io/api/core/v1"
14
+ "k8s.io/client-go/rest"
15
+ )
16
+
17
+ // ReadResourceMonitoringInventory returns bounded current aggregate facts, not Pod manifests.
18
+ func ReadResourceMonitoringInventory(ctx context.Context, client rest.Interface, clusterID string) (cr.ResourceMonitoringInventory, error) {
19
+ ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
20
+ defer cancel()
21
+ now := time.Now().UTC().Format(time.RFC3339)
22
+ output := cr.ResourceMonitoringInventory{Nodes: []cr.ResourceMonitoringNode{}, Quality: "valid", ObservedAt: now}
23
+ var nodes corev1.NodeList
24
+ if err := readMonitoringList(ctx, client, "nodes", cr.ResourceMonitoringMaxNodes, 2<<20, &nodes); err != nil {
25
+ return output, err
26
+ }
27
+ var pods corev1.PodList
28
+ podErr := readMonitoringList(ctx, client, "pods", 5000, 16<<20, &pods)
29
+ complete := podErr == nil && pods.Continue == "" && len(pods.Items) <= 5000
30
+ if !complete {
31
+ output.Quality = "partial"
32
+ output.ReasonCode = "pod_snapshot_incomplete"
33
+ output.Truncated = pods.Continue != "" || len(pods.Items) > 5000
34
+ }
35
+ if nodes.Continue != "" || len(nodes.Items) > cr.ResourceMonitoringMaxNodes {
36
+ output.Quality = "partial"
37
+ output.ReasonCode = "node_budget"
38
+ output.Truncated = true
39
+ }
40
+ aggregates := monitoringPodAggregates(pods.Items)
41
+ if complete {
42
+ output.UnscheduledPendingPods = number(aggregates[""].pending)
43
+ }
44
+ for index, node := range nodes.Items {
45
+ if index >= cr.ResourceMonitoringMaxNodes {
46
+ break
47
+ }
48
+ fact := monitoringNodeFact(clusterID, node, aggregates[node.Name], complete, now)
49
+ output.Nodes = append(output.Nodes, fact)
50
+ }
51
+
52
+ sort.Slice(output.Nodes, func(i, j int) bool { return output.Nodes[i].NodeRef.Name < output.Nodes[j].NodeRef.Name })
53
+ return output, nil
54
+ }
55
+
56
+ func readMonitoringList(ctx context.Context, client rest.Interface, resource string, limit, maxBytes int, target any) error {
57
+ if client == nil {
58
+ return fmt.Errorf("kubernetes REST client unavailable")
59
+ }
60
+ stream, err := client.Get().AbsPath("/api/v1/"+resource).Param("limit", fmt.Sprint(limit)).SetHeader("Accept", "application/json").Stream(ctx)
61
+ if err != nil {
62
+ return err
63
+ }
64
+ defer func() { _ = stream.Close() }()
65
+ raw, err := io.ReadAll(io.LimitReader(stream, int64(maxBytes)+1))
66
+ if err != nil {
67
+ return err
68
+ }
69
+ if len(raw) > maxBytes {
70
+ return fmt.Errorf("monitoring inventory byte budget")
71
+ }
72
+ return json.Unmarshal(raw, target)
73
+ }
74
+
75
+ type monitoringPodAggregate struct {
76
+ cpuRequest, memoryRequest, cpuLimit, memoryLimit, count, pending float64
77
+ cpuComplete, memoryComplete bool
78
+ initialized bool
79
+ }
80
+
81
+ func monitoringPodAggregates(pods []corev1.Pod) map[string]monitoringPodAggregate {
82
+ result := map[string]monitoringPodAggregate{}
83
+ for _, pod := range pods {
84
+ if monitoringTerminalPod(pod) {
85
+ continue
86
+ }
87
+ aggregate := result[pod.Spec.NodeName]
88
+ if !aggregate.initialized {
89
+ aggregate.cpuComplete = true
90
+ aggregate.memoryComplete = true
91
+ aggregate.initialized = true
92
+ }
93
+ aggregate.count++
94
+ if pod.Status.Phase == corev1.PodPending {
95
+ aggregate.pending++
96
+ }
97
+ requests, limits, cpuComplete, memoryComplete := effectiveMonitoringPodResources(pod)
98
+ aggregate.cpuRequest += requests.cpu
99
+ aggregate.memoryRequest += requests.memory
100
+ aggregate.cpuLimit += limits.cpu
101
+ aggregate.memoryLimit += limits.memory
102
+ aggregate.cpuComplete = aggregate.cpuComplete && cpuComplete
103
+ aggregate.memoryComplete = aggregate.memoryComplete && memoryComplete
104
+ result[pod.Spec.NodeName] = aggregate
105
+ }
106
+ return result
107
+ }
108
+ func monitoringTerminalPod(pod corev1.Pod) bool {
109
+ return pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed
110
+ }
111
+
112
+ type monitoringResources struct{ cpu, memory float64 }
113
+
114
+ func monitoringResourcesFromList(values corev1.ResourceList) monitoringResources {
115
+ cpu := values[corev1.ResourceCPU]
116
+ memory := values[corev1.ResourceMemory]
117
+ return monitoringResources{cpu: float64(cpu.MilliValue()) / 1000, memory: float64(memory.Value())}
118
+ }
119
+ func (r monitoringResources) add(other monitoringResources) monitoringResources {
120
+ return monitoringResources{cpu: r.cpu + other.cpu, memory: r.memory + other.memory}
121
+ }
122
+ func (r monitoringResources) max(other monitoringResources) monitoringResources {
123
+ return monitoringResources{cpu: math.Max(r.cpu, other.cpu), memory: math.Max(r.memory, other.memory)}
124
+ }
125
+
126
+ func effectiveMonitoringPodResources(pod corev1.Pod) (monitoringResources, monitoringResources, bool, bool) {
127
+ var regularRequest, regularLimit, sidecarRequest, sidecarLimit, initRequest, initLimit monitoringResources
128
+ cpuComplete, memoryComplete := true, true
129
+ addCoverage := func(container corev1.Container) {
130
+ cpuValue, cpu := container.Resources.Limits[corev1.ResourceCPU]
131
+ memoryValue, memory := container.Resources.Limits[corev1.ResourceMemory]
132
+ cpu = cpu && !cpuValue.IsZero()
133
+ memory = memory && !memoryValue.IsZero()
134
+ cpuComplete = cpuComplete && cpu
135
+ memoryComplete = memoryComplete && memory
136
+ }
137
+ for _, container := range pod.Spec.Containers {
138
+ regularRequest = regularRequest.add(monitoringResourcesFromList(container.Resources.Requests))
139
+ regularLimit = regularLimit.add(monitoringResourcesFromList(container.Resources.Limits))
140
+ addCoverage(container)
141
+ }
142
+ for _, container := range pod.Spec.InitContainers {
143
+ requests := monitoringResourcesFromList(container.Resources.Requests)
144
+ limits := monitoringResourcesFromList(container.Resources.Limits)
145
+ addCoverage(container)
146
+ if container.RestartPolicy != nil && *container.RestartPolicy == corev1.ContainerRestartPolicyAlways {
147
+ sidecarRequest = sidecarRequest.add(requests)
148
+ sidecarLimit = sidecarLimit.add(limits)
149
+ requests = sidecarRequest
150
+ limits = sidecarLimit
151
+ } else {
152
+ requests = requests.add(sidecarRequest)
153
+ limits = limits.add(sidecarLimit)
154
+ }
155
+ initRequest = initRequest.max(requests)
156
+ initLimit = initLimit.max(limits)
157
+ }
158
+ requests := regularRequest.add(sidecarRequest).max(initRequest)
159
+ limits := regularLimit.add(sidecarLimit).max(initLimit)
160
+ if pod.Spec.Resources != nil {
161
+ requests = monitoringPodLevelOverride(requests, pod.Spec.Resources.Requests)
162
+ limits = monitoringPodLevelOverride(limits, pod.Spec.Resources.Limits)
163
+ if value, ok := pod.Spec.Resources.Limits[corev1.ResourceCPU]; ok {
164
+ cpuComplete = !value.IsZero()
165
+ }
166
+ if value, ok := pod.Spec.Resources.Limits[corev1.ResourceMemory]; ok {
167
+ memoryComplete = !value.IsZero()
168
+ }
169
+ }
170
+ overhead := monitoringResourcesFromList(pod.Spec.Overhead)
171
+ requests = requests.add(overhead)
172
+ if limits.cpu > 0 {
173
+ limits.cpu += overhead.cpu
174
+ }
175
+ if limits.memory > 0 {
176
+ limits.memory += overhead.memory
177
+ }
178
+ return requests, limits, cpuComplete, memoryComplete
179
+ }
180
+ func monitoringPodLevelOverride(current monitoringResources, values corev1.ResourceList) monitoringResources {
181
+ if value, ok := values[corev1.ResourceCPU]; ok {
182
+ current.cpu = float64(value.MilliValue()) / 1000
183
+ }
184
+ if value, ok := values[corev1.ResourceMemory]; ok {
185
+ current.memory = float64(value.Value())
186
+ }
187
+ return current
188
+ }
189
+ func monitoringQuantity(values corev1.ResourceList) cr.ResourceMonitoringQuantity {
190
+ result := cr.ResourceMonitoringQuantity{}
191
+ if cpu, ok := values[corev1.ResourceCPU]; ok {
192
+ result.CPUCores = number(float64(cpu.MilliValue()) / 1000)
193
+ }
194
+ if memory, ok := values[corev1.ResourceMemory]; ok {
195
+ result.MemoryBytes = number(float64(memory.Value()))
196
+ }
197
+ if pods, ok := values[corev1.ResourcePods]; ok {
198
+ result.PodSlots = number(float64(pods.Value()))
199
+ }
200
+ return result
201
+ }
202
+ func monitoringNodeFact(clusterID string, node corev1.Node, aggregate monitoringPodAggregate, complete bool, now string) cr.ResourceMonitoringNode {
203
+ fact := cr.ResourceMonitoringNode{NodeRef: cr.ResourceRef{ClusterID: clusterID, APIVersion: "v1", Kind: "Node", Name: node.Name, ScopeMode: cr.ResourceScopeModeCluster, UID: string(node.UID), ResourceVersion: node.ResourceVersion}, OS: node.Status.NodeInfo.OperatingSystem, CreatedAt: node.CreationTimestamp.UTC().Format(time.RFC3339), Ready: "Unknown", MemoryPressure: "Unknown", DiskPressure: "Unknown", PIDPressure: "Unknown", Cordoned: node.Spec.Unschedulable, Capacity: monitoringQuantity(node.Status.Capacity), Allocatable: monitoringQuantity(node.Status.Allocatable), Quality: "valid", ObservedAt: now}
204
+ for _, address := range node.Status.Addresses {
205
+ if address.Type == corev1.NodeInternalIP {
206
+ fact.InternalIP = address.Address
207
+ break
208
+ }
209
+ }
210
+ for _, condition := range node.Status.Conditions {
211
+ switch condition.Type {
212
+ case corev1.NodeReady:
213
+ fact.Ready = string(condition.Status)
214
+ case corev1.NodeMemoryPressure:
215
+ fact.MemoryPressure = string(condition.Status)
216
+ case corev1.NodeDiskPressure:
217
+ fact.DiskPressure = string(condition.Status)
218
+ case corev1.NodePIDPressure:
219
+ fact.PIDPressure = string(condition.Status)
220
+ }
221
+ }
222
+ if !complete {
223
+ fact.Quality = "partial"
224
+ return fact
225
+ }
226
+ fact.Requests = cr.ResourceMonitoringQuantity{CPUCores: number(aggregate.cpuRequest), MemoryBytes: number(aggregate.memoryRequest)}
227
+ fact.CPULimitsComplete = !aggregate.initialized || aggregate.cpuComplete
228
+ fact.MemoryLimitsComplete = !aggregate.initialized || aggregate.memoryComplete
229
+ if fact.CPULimitsComplete {
230
+ fact.Limits.CPUCores = number(aggregate.cpuLimit)
231
+ }
232
+ if fact.MemoryLimitsComplete {
233
+ fact.Limits.MemoryBytes = number(aggregate.memoryLimit)
234
+ }
235
+ fact.BoundPods = number(aggregate.count)
236
+ fact.PendingPods = number(aggregate.pending)
237
+ if fact.Allocatable.PodSlots != nil {
238
+ fact.RemainingPodSlots = number(math.Max(0, *fact.Allocatable.PodSlots-aggregate.count))
239
+ fact.OverflowPodSlots = number(math.Max(0, aggregate.count-*fact.Allocatable.PodSlots))
240
+ }
241
+ return fact
242
+ }
243
+ func number(value float64) *float64 { return &value }
@@ -0,0 +1,88 @@
1
+ package runtime
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "net/http"
7
+ "net/http/httptest"
8
+ "testing"
9
+
10
+ corev1 "k8s.io/api/core/v1"
11
+ "k8s.io/apimachinery/pkg/api/resource"
12
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13
+ "k8s.io/apimachinery/pkg/types"
14
+ "k8s.io/client-go/kubernetes"
15
+ "k8s.io/client-go/rest"
16
+ )
17
+
18
+ func monitoringResourceList(cpu, memory string) corev1.ResourceList {
19
+ return corev1.ResourceList{corev1.ResourceCPU: resource.MustParse(cpu), corev1.ResourceMemory: resource.MustParse(memory)}
20
+ }
21
+ func TestMonitoringFootprintHandlesSidecarsInitAndOverhead(t *testing.T) {
22
+ always := corev1.ContainerRestartPolicyAlways
23
+ container := func(name, cpu string) corev1.Container {
24
+ return corev1.Container{Name: name, Resources: corev1.ResourceRequirements{Requests: monitoringResourceList(cpu, "1Mi"), Limits: monitoringResourceList(cpu, "1Mi")}}
25
+ }
26
+ sidecar := container("sidecar", "1")
27
+ sidecar.RestartPolicy = &always
28
+ pod := corev1.Pod{Spec: corev1.PodSpec{Containers: []corev1.Container{container("app", "2")}, InitContainers: []corev1.Container{sidecar, container("init", "4")}, Overhead: monitoringResourceList("100m", "1Mi")}}
29
+ requests, limits, cpuComplete, memoryComplete := effectiveMonitoringPodResources(pod)
30
+ if requests.cpu != 5.1 || limits.cpu != 5.1 || !cpuComplete || !memoryComplete {
31
+ t.Fatalf("effective footprint requests=%+v limits=%+v complete=%v/%v", requests, limits, cpuComplete, memoryComplete)
32
+ }
33
+ pod.Spec.Containers[0].Resources.Limits = monitoringResourceList("0", "0")
34
+ _, _, cpuComplete, memoryComplete = effectiveMonitoringPodResources(pod)
35
+ if cpuComplete || memoryComplete {
36
+ t.Fatal("zero limits treated as an explicit finite limit")
37
+ }
38
+ pod.Spec.Resources = &corev1.ResourceRequirements{Limits: monitoringResourceList("0", "0")}
39
+ _, _, cpuComplete, memoryComplete = effectiveMonitoringPodResources(pod)
40
+ if cpuComplete || memoryComplete {
41
+ t.Fatal("Pod-level zero limits treated as complete finite limits")
42
+ }
43
+ }
44
+ func TestMonitoringInventoryCountsOnlyBoundNonterminalPods(t *testing.T) {
45
+ nodes := corev1.NodeList{Items: []corev1.Node{{ObjectMeta: metav1.ObjectMeta{Name: "node", UID: types.UID("node-uid")}, Status: corev1.NodeStatus{Capacity: monitoringResourceList("4", "8Gi"), Allocatable: corev1.ResourceList{corev1.ResourcePods: resource.MustParse("1")}}}}}
46
+ deleting := metav1.Now()
47
+ pods := corev1.PodList{Items: []corev1.Pod{
48
+ {ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &deleting}, Spec: corev1.PodSpec{NodeName: "node"}, Status: corev1.PodStatus{Phase: corev1.PodRunning}},
49
+ {Spec: corev1.PodSpec{NodeName: "node"}, Status: corev1.PodStatus{Phase: corev1.PodPending}},
50
+ {Spec: corev1.PodSpec{NodeName: "node"}, Status: corev1.PodStatus{Phase: corev1.PodSucceeded}},
51
+ {Spec: corev1.PodSpec{NodeName: "node"}, Status: corev1.PodStatus{Phase: corev1.PodFailed}},
52
+ {Status: corev1.PodStatus{Phase: corev1.PodPending}},
53
+ }}
54
+ forbidden := false
55
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56
+ switch r.URL.Path {
57
+ case "/api/v1/nodes":
58
+ _ = json.NewEncoder(w).Encode(nodes)
59
+ case "/api/v1/pods":
60
+ if forbidden {
61
+ http.Error(w, "Forbidden", http.StatusForbidden)
62
+ return
63
+ }
64
+ _ = json.NewEncoder(w).Encode(pods)
65
+ default:
66
+ t.Errorf("unexpected path %s", r.URL.Path)
67
+ http.NotFound(w, r)
68
+ }
69
+ }))
70
+ defer server.Close()
71
+ client, _ := kubernetes.NewForConfig(&rest.Config{Host: server.URL})
72
+ inventory, err := ReadResourceMonitoringInventory(context.Background(), client.CoreV1().RESTClient(), "c")
73
+ if err != nil {
74
+ t.Fatal(err)
75
+ }
76
+ fact := inventory.Nodes[0]
77
+ if fact.BoundPods == nil || *fact.BoundPods != 2 || *fact.PendingPods != 1 || *fact.RemainingPodSlots != 0 || *fact.OverflowPodSlots != 1 || *inventory.UnscheduledPendingPods != 1 {
78
+ t.Fatalf("wrong capacity: %+v", fact)
79
+ }
80
+ forbidden = true
81
+ inventory, err = ReadResourceMonitoringInventory(context.Background(), client.CoreV1().RESTClient(), "c")
82
+ if err != nil {
83
+ t.Fatal(err)
84
+ }
85
+ if inventory.Quality != "partial" || inventory.Nodes[0].BoundPods != nil || inventory.Nodes[0].Requests.CPUCores != nil {
86
+ t.Fatalf("partial Pod read invented zero capacity facts: %+v", inventory)
87
+ }
88
+ }