@opensoha/contracts 0.1.22 → 0.1.24

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 (361) 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/permission-catalog.json +32 -1
  39. package/auth/token-claims.schema.json +5 -0
  40. package/compat/consumer-revisions.json +4 -3
  41. package/compat/json-schema-ecosystem-0.1.0.json +1 -1
  42. package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
  43. package/connectors/connector-action.schema.json +138 -0
  44. package/fixtures/json-schema/connector-action/invalid/empty-values.json +11 -0
  45. package/fixtures/json-schema/connector-action/invalid/invalid-expiry.json +16 -0
  46. package/fixtures/json-schema/connector-action/invalid/lowercase-alias.json +16 -0
  47. package/fixtures/json-schema/connector-action/invalid/unknown-entry-field.json +17 -0
  48. package/fixtures/json-schema/connector-action/invalid/unknown-frame-field.json +17 -0
  49. package/fixtures/json-schema/connector-action/invalid/unknown-version.json +16 -0
  50. package/fixtures/json-schema/connector-action/invalid/unversioned-reference.json +16 -0
  51. package/fixtures/json-schema/connector-action/invalid/zero-version.json +16 -0
  52. package/fixtures/json-schema/connector-action/valid/pinned.json +16 -0
  53. package/fixtures/json-schema/connector-definition/invalid/credential-userinfo.json +8 -0
  54. package/fixtures/json-schema/connector-definition/invalid/reference-traversal.json +8 -0
  55. package/fixtures/json-schema/connector-definition/invalid/zero-version.json +8 -0
  56. package/fixtures/json-schema/connector-definition/valid/canonical.json +8 -0
  57. package/fixtures/json-schema/connector-definition/valid/pinned.json +8 -0
  58. package/fixtures/json-schema/personal-browser-network-request/invalid/bounded.json +4 -0
  59. package/fixtures/json-schema/personal-browser-network-request/valid/bounded.json +4 -0
  60. package/fixtures/json-schema/personal-browser-network-response/invalid/bounded.json +5 -0
  61. package/fixtures/json-schema/personal-browser-network-response/valid/bounded.json +5 -0
  62. package/fixtures/json-schema/personal-environment-export/invalid/extra.json +11 -0
  63. package/fixtures/json-schema/personal-environment-export/invalid/ownerId.json +10 -0
  64. package/fixtures/json-schema/personal-environment-export/invalid/sha256.json +10 -0
  65. package/fixtures/json-schema/personal-environment-export/invalid/sizeBytes.json +10 -0
  66. package/fixtures/json-schema/personal-environment-export/valid/complete.json +10 -0
  67. package/fixtures/json-schema/personal-environment-preflight-request/invalid/disk.json +11 -0
  68. package/fixtures/json-schema/personal-environment-preflight-request/invalid/network.json +11 -0
  69. package/fixtures/json-schema/personal-environment-preflight-request/invalid/node.json +12 -0
  70. package/fixtures/json-schema/personal-environment-preflight-request/invalid/owner.json +12 -0
  71. package/fixtures/json-schema/personal-environment-preflight-request/valid/bounded.json +11 -0
  72. package/fixtures/json-schema/personal-environment-preflight-result/invalid/disk.json +15 -0
  73. package/fixtures/json-schema/personal-environment-preflight-result/invalid/network.json +15 -0
  74. package/fixtures/json-schema/personal-environment-preflight-result/invalid/no-hard-quota.json +15 -0
  75. package/fixtures/json-schema/personal-environment-preflight-result/invalid/node.json +16 -0
  76. package/fixtures/json-schema/personal-environment-preflight-result/invalid/not-ready.json +15 -0
  77. package/fixtures/json-schema/personal-environment-preflight-result/invalid/owner.json +16 -0
  78. package/fixtures/json-schema/personal-environment-preflight-result/valid/bounded.json +15 -0
  79. package/fixtures/json-schema/personal-environment-request/invalid/delete-data-argv.json +24 -0
  80. package/fixtures/json-schema/personal-environment-request/invalid/delete-data-createData.json +22 -0
  81. package/fixtures/json-schema/personal-environment-request/invalid/delete-data-taskId.json +22 -0
  82. package/fixtures/json-schema/personal-environment-request/invalid/export-argv.json +22 -0
  83. package/fixtures/json-schema/personal-environment-request/invalid/export-createData.json +22 -0
  84. package/fixtures/json-schema/personal-environment-request/invalid/export-taskId.json +22 -0
  85. package/fixtures/json-schema/personal-environment-request/invalid/extra-command.json +24 -0
  86. package/fixtures/json-schema/personal-environment-request/invalid/host-workspace.json +22 -0
  87. package/fixtures/json-schema/personal-environment-request/invalid/image-injection.json +22 -0
  88. package/fixtures/json-schema/personal-environment-request/invalid/network-egress.json +21 -0
  89. package/fixtures/json-schema/personal-environment-request/invalid/no-command.json +21 -0
  90. package/fixtures/json-schema/personal-environment-request/invalid/recreate-on-release.json +22 -0
  91. package/fixtures/json-schema/personal-environment-request/invalid/relative-command.json +24 -0
  92. package/fixtures/json-schema/personal-environment-request/invalid/released-identity.json +22 -0
  93. package/fixtures/json-schema/personal-environment-request/invalid/task-empty.json +27 -0
  94. package/fixtures/json-schema/personal-environment-request/invalid/task-host-path.json +27 -0
  95. package/fixtures/json-schema/personal-environment-request/invalid/task-noncanonical.json +27 -0
  96. package/fixtures/json-schema/personal-environment-request/invalid/task-null.json +27 -0
  97. package/fixtures/json-schema/personal-environment-request/invalid/task-on-inspection.json +22 -0
  98. package/fixtures/json-schema/personal-environment-request/invalid/unbounded-quota.json +21 -0
  99. package/fixtures/json-schema/personal-environment-request/valid/acquire.json +21 -0
  100. package/fixtures/json-schema/personal-environment-request/valid/delete-data.json +21 -0
  101. package/fixtures/json-schema/personal-environment-request/valid/execute.json +26 -0
  102. package/fixtures/json-schema/personal-environment-request/valid/export-data.json +21 -0
  103. package/fixtures/json-schema/personal-environment-request/valid/inspect.json +21 -0
  104. package/fixtures/json-schema/personal-environment-request/valid/provision.json +22 -0
  105. package/fixtures/json-schema/personal-environment-request/valid/release.json +21 -0
  106. package/fixtures/json-schema/personal-environment-request/valid/task-workspace.json +27 -0
  107. package/fixtures/json-schema/personal-environment-result/invalid/active-without-observation.json +7 -0
  108. package/fixtures/json-schema/personal-environment-result/invalid/deleted-output.json +8 -0
  109. package/fixtures/json-schema/personal-environment-result/invalid/deleted-snapshot.json +8 -0
  110. package/fixtures/json-schema/personal-environment-result/invalid/deleted-taskId.json +8 -0
  111. package/fixtures/json-schema/personal-environment-result/invalid/private-handle.json +8 -0
  112. package/fixtures/json-schema/personal-environment-result/invalid/stopped-fake-observation.json +20 -0
  113. package/fixtures/json-schema/personal-environment-result/invalid/task-empty.json +22 -0
  114. package/fixtures/json-schema/personal-environment-result/invalid/task-host-path.json +22 -0
  115. package/fixtures/json-schema/personal-environment-result/invalid/task-null.json +22 -0
  116. package/fixtures/json-schema/personal-environment-result/invalid/task-on-stopped.json +8 -0
  117. package/fixtures/json-schema/personal-environment-result/invalid/task-without-command-output.json +21 -0
  118. package/fixtures/json-schema/personal-environment-result/valid/active.json +21 -0
  119. package/fixtures/json-schema/personal-environment-result/valid/deleted.json +7 -0
  120. package/fixtures/json-schema/personal-environment-result/valid/expired.json +7 -0
  121. package/fixtures/json-schema/personal-environment-result/valid/missing.json +7 -0
  122. package/fixtures/json-schema/personal-environment-result/valid/released.json +7 -0
  123. package/fixtures/json-schema/personal-environment-result/valid/task-workspace.json +22 -0
  124. package/fixtures/json-schema/personal-guest-binding/invalid/host-path.json +17 -0
  125. package/fixtures/json-schema/personal-guest-binding/invalid/invalid-policy.json +17 -0
  126. package/fixtures/json-schema/personal-guest-binding/invalid/invalid-serial.json +17 -0
  127. package/fixtures/json-schema/personal-guest-binding/invalid/legacy-version.json +17 -0
  128. package/fixtures/json-schema/personal-guest-binding/invalid/missing-generation.json +17 -0
  129. package/fixtures/json-schema/personal-guest-binding/invalid/missing-identity.json +16 -0
  130. package/fixtures/json-schema/personal-guest-binding/invalid/missing-provider-identity.json +16 -0
  131. package/fixtures/json-schema/personal-guest-binding/invalid/raw-provider-identity.json +17 -0
  132. package/fixtures/json-schema/personal-guest-binding/valid/minimal.json +17 -0
  133. package/fixtures/json-schema/personal-guest-data-plan/invalid/quota.json +1 -0
  134. package/fixtures/json-schema/personal-guest-data-plan/valid/basic.json +1 -0
  135. package/fixtures/json-schema/personal-guest-enrollment/invalid/capabilities.json +7 -0
  136. package/fixtures/json-schema/personal-guest-enrollment/invalid/legacy-version.json +4 -0
  137. package/fixtures/json-schema/personal-guest-enrollment/invalid/missing-identity.json +3 -0
  138. package/fixtures/json-schema/personal-guest-enrollment/invalid/ownerId.json +5 -0
  139. package/fixtures/json-schema/personal-guest-enrollment/invalid/token.json +5 -0
  140. package/fixtures/json-schema/personal-guest-enrollment/valid/minimal.json +4 -0
  141. package/fixtures/json-schema/personal-guest-heartbeat/invalid/argv.json +11 -0
  142. package/fixtures/json-schema/personal-guest-heartbeat/invalid/legacy-version.json +8 -0
  143. package/fixtures/json-schema/personal-guest-heartbeat/invalid/missing-identity.json +7 -0
  144. package/fixtures/json-schema/personal-guest-heartbeat/invalid/owner.json +9 -0
  145. package/fixtures/json-schema/personal-guest-heartbeat/invalid/self-ready.json +9 -0
  146. package/fixtures/json-schema/personal-guest-heartbeat/invalid/zero-sequence.json +8 -0
  147. package/fixtures/json-schema/personal-guest-heartbeat/valid/minimal.json +8 -0
  148. package/fixtures/json-schema/personal-guest-provisioning/invalid/network.json +1 -0
  149. package/fixtures/json-schema/personal-guest-provisioning/valid/basic.json +1 -0
  150. package/fixtures/json-schema/personal-guest-renewal/invalid/unknown.json +1 -0
  151. package/fixtures/json-schema/personal-guest-renewal/valid/basic.json +1 -0
  152. package/fixtures/json-schema/personal-guest-runtime/invalid/minimal.json +45 -0
  153. package/fixtures/json-schema/personal-guest-runtime/valid/minimal.json +45 -0
  154. package/fixtures/json-schema/personal-guest-runtime-result/invalid/minimal.json +28 -0
  155. package/fixtures/json-schema/personal-guest-runtime-result/valid/minimal.json +27 -0
  156. package/fixtures/json-schema/personal-guest-tools/invalid/minimal.json +31 -0
  157. package/fixtures/json-schema/personal-guest-tools/valid/minimal.json +30 -0
  158. package/fixtures/json-schema/source-import-policy/invalid/case-variant-scope.json +4 -0
  159. package/fixtures/json-schema/source-import-policy/invalid/case-variant-version.json +4 -0
  160. package/fixtures/json-schema/source-import-policy/invalid/duplicate-grant.json +4 -0
  161. package/fixtures/json-schema/source-import-policy/invalid/empty-id.json +4 -0
  162. package/fixtures/json-schema/source-import-policy/invalid/future-version.json +4 -0
  163. package/fixtures/json-schema/source-import-policy/invalid/unknown-field.json +4 -0
  164. package/fixtures/json-schema/source-import-policy/valid/deny-all.json +4 -0
  165. package/fixtures/json-schema/source-import-policy/valid/shared.json +4 -0
  166. package/fixtures/json-schema/token-claims/invalid/authz-negative.json +23 -0
  167. package/fixtures/json-schema/token-claims/invalid/authz-string.json +23 -0
  168. package/fixtures/json-schema/token-claims/invalid/authz-zero.json +23 -0
  169. package/fixtures/json-schema/token-claims/valid/versioned-session.json +23 -0
  170. package/fixtures/openapi/AgentRunEventListEnvelope/invalid/basic.json +8 -0
  171. package/fixtures/openapi/AgentRunEventListEnvelope/valid/basic.json +8 -0
  172. package/fixtures/openapi/AgentRunEventListEnvelope/valid/native.json +1 -0
  173. package/fixtures/openapi/AgentRunResumeRequest/invalid/basic.json +3 -0
  174. package/fixtures/openapi/AgentRunResumeRequest/valid/basic.json +3 -0
  175. package/fixtures/openapi/AgentSSHCommandRequest/invalid/task51.json +24 -0
  176. package/fixtures/openapi/AgentSSHCommandRequest/valid/task51.json +23 -0
  177. package/fixtures/openapi/AgentSSHCommandResult/invalid/task51.json +8 -0
  178. package/fixtures/openapi/AgentSSHCommandResult/valid/task51.json +7 -0
  179. package/fixtures/openapi/AgentSSHSessionRequest/invalid/task51.json +20 -0
  180. package/fixtures/openapi/AgentSSHSessionRequest/valid/task51.json +19 -0
  181. package/fixtures/openapi/CapabilityTaskInput/invalid/duplicate-scope.json +36 -0
  182. package/fixtures/openapi/CapabilityTaskInput/valid/scoped.json +32 -0
  183. package/fixtures/openapi/DockerQuickCreateHostInput/invalid/guest-access.json +7 -0
  184. package/fixtures/openapi/DockerQuickCreateHostInput/valid/guest-access.json +9 -0
  185. package/fixtures/openapi/KnowledgeSourceDocument/invalid/tombstone-with-content.json +1 -0
  186. package/fixtures/openapi/KnowledgeSourceDocument/valid/tombstone.json +1 -0
  187. package/fixtures/openapi/KubernetesResourceUpdatePlanRequest/invalid/missing-required.json +1 -0
  188. package/fixtures/openapi/KubernetesResourceYaml/invalid/missing-required.json +1 -0
  189. package/fixtures/openapi/ObservabilityDataSource/invalid/missing-required.json +1 -0
  190. package/fixtures/openapi/PersonalAgentAccess/invalid/invalid.json +1 -0
  191. package/fixtures/openapi/PersonalAgentAccess/valid/valid.json +1 -0
  192. package/fixtures/openapi/PersonalAgentAccessDecision/invalid/invalid.json +1 -0
  193. package/fixtures/openapi/PersonalAgentAccessDecision/valid/valid.json +1 -0
  194. package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/invalid/owner.json +8 -0
  195. package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/valid/side-chat.json +9 -0
  196. package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/motion.json +1 -0
  197. package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/owner.json +1 -0
  198. package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/remote.json +1 -0
  199. package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/svg.json +1 -0
  200. package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/raster.json +1 -0
  201. package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/reset.json +1 -0
  202. package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/empty.json +1 -0
  203. package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/owner.json +1 -0
  204. package/fixtures/openapi/PersonalAssistantUpdateRequest/valid/name.json +1 -0
  205. package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/host-url.json +12 -0
  206. package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/lifetime.json +11 -0
  207. package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/unknown-agent.json +11 -0
  208. package/fixtures/openapi/PersonalDockerEnvironmentPolicy/valid/basic.json +11 -0
  209. package/fixtures/openapi/PersonalEnvironment/invalid/invented-state.json +6 -0
  210. package/fixtures/openapi/PersonalEnvironment/invalid/missing-state.json +5 -0
  211. package/fixtures/openapi/PersonalEnvironment/invalid/negative-generation.json +7 -0
  212. package/fixtures/openapi/PersonalEnvironment/invalid/network.json +23 -0
  213. package/fixtures/openapi/PersonalEnvironment/invalid/private-node.json +24 -0
  214. package/fixtures/openapi/PersonalEnvironment/invalid/unsupported-backend.json +7 -0
  215. package/fixtures/openapi/PersonalEnvironment/valid/deleted.json +23 -0
  216. package/fixtures/openapi/PersonalEnvironment/valid/deleting.json +23 -0
  217. package/fixtures/openapi/PersonalEnvironment/valid/deletion_unknown.json +23 -0
  218. package/fixtures/openapi/PersonalEnvironment/valid/frozen-binding.json +13 -0
  219. package/fixtures/openapi/PersonalEnvironment/valid/ready.json +23 -0
  220. package/fixtures/openapi/PersonalEnvironment/valid/unknown.json +23 -0
  221. package/fixtures/openapi/PersonalEnvironment/valid/unprovisioned.json +6 -0
  222. package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/command.json +3 -0
  223. package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/node.json +4 -0
  224. package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/owner.json +4 -0
  225. package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/quota.json +10 -0
  226. package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/reconcile.json +3 -0
  227. package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/sleep.json +3 -0
  228. package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/wake.json +4 -0
  229. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/empty-name.json +5 -0
  230. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/long-name.json +5 -0
  231. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/missing-name.json +4 -0
  232. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/owner.json +6 -0
  233. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/stale-version.json +5 -0
  234. package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/valid/confirmed.json +5 -0
  235. package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/assistantId.json +4 -0
  236. package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/environmentVersion.json +4 -0
  237. package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/ownerId.json +5 -0
  238. package/fixtures/openapi/PersonalEnvironmentDataExportRequest/valid/current.json +4 -0
  239. package/fixtures/openapi/PersonalEnvironmentEnvelope/invalid/missing-data.json +1 -0
  240. package/fixtures/openapi/PersonalEnvironmentEnvelope/valid/state.json +8 -0
  241. package/fixtures/openapi/PersonalEnvironmentQuota/invalid/cpu.json +7 -0
  242. package/fixtures/openapi/PersonalEnvironmentQuota/invalid/disk.json +7 -0
  243. package/fixtures/openapi/PersonalEnvironmentQuota/invalid/network.json +7 -0
  244. package/fixtures/openapi/PersonalEnvironmentQuota/invalid/processes.json +7 -0
  245. package/fixtures/openapi/PersonalEnvironmentQuota/valid/basic.json +7 -0
  246. package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/mode.json +13 -0
  247. package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/network.json +13 -0
  248. package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/node.json +14 -0
  249. package/fixtures/openapi/PersonalEnvironmentTemplate/valid/template.json +13 -0
  250. package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/missing.json +1 -0
  251. package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/unbounded.json +1317 -0
  252. package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/choices.json +17 -0
  253. package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/empty.json +3 -0
  254. package/fixtures/openapi/PersonalEnvironmentUsage/invalid/negative.json +6 -0
  255. package/fixtures/openapi/PersonalEnvironmentUsage/valid/usage.json +6 -0
  256. package/fixtures/openapi/PersonalFeedFeedback/invalid/owner.json +4 -0
  257. package/fixtures/openapi/PersonalFeedFeedback/valid/liked.json +3 -0
  258. package/fixtures/openapi/PersonalFeedItem/invalid/no-source.json +9 -0
  259. package/fixtures/openapi/PersonalFeedItem/valid/post.json +15 -0
  260. package/fixtures/openapi/PersonalFeedSettings/invalid/owner.json +6 -0
  261. package/fixtures/openapi/PersonalFeedSettings/valid/default.json +5 -0
  262. package/fixtures/openapi/PersonalGuestPreparationInput/invalid/foreign-owner.json +5 -0
  263. package/fixtures/openapi/PersonalGuestPreparationInput/invalid/unsafe-counter.json +4 -0
  264. package/fixtures/openapi/PersonalGuestPreparationInput/valid/ed25519-csr.json +4 -0
  265. package/fixtures/openapi/PersonalGuestRuntimeEnvelope/invalid/minimal.json +10 -0
  266. package/fixtures/openapi/PersonalGuestRuntimeEnvelope/valid/minimal.json +9 -0
  267. package/fixtures/openapi/PersonalGuestRuntimeInput/invalid/minimal.json +6 -0
  268. package/fixtures/openapi/PersonalGuestRuntimeInput/valid/minimal.json +8 -0
  269. package/fixtures/openapi/PersonalGuestToolsInput/invalid/minimal.json +5 -0
  270. package/fixtures/openapi/PersonalGuestToolsInput/valid/minimal.json +4 -0
  271. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/device.json +15 -0
  272. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/fractional-size.json +15 -0
  273. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/network.json +15 -0
  274. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/owner.json +15 -0
  275. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/provider.json +15 -0
  276. package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/unknown.json +16 -0
  277. package/fixtures/openapi/PersonalVMEnvironmentPolicy/valid/pve.json +15 -0
  278. package/fixtures/openapi/PersonalWorkspaceRuntime/invalid/shared.json +3 -0
  279. package/fixtures/openapi/PersonalWorkspaceRuntime/valid/shared.json +4 -0
  280. package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/invalid/shared.json +5 -0
  281. package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/valid/shared.json +6 -0
  282. package/fixtures/openapi/SecretGenerateSSHKeyRequest/invalid/task51.json +6 -0
  283. package/fixtures/openapi/SecretGenerateSSHKeyRequest/valid/task51.json +12 -0
  284. package/fixtures/openapi/SecretReferenceUsage/invalid/version.json +6 -0
  285. package/fixtures/openapi/SecretReferenceUsage/valid/version.json +8 -0
  286. package/fixtures/openapi/VMSSHCredentialRedemptionInput/invalid/task51.json +7 -0
  287. package/fixtures/openapi/VMSSHCredentialRedemptionInput/valid/task51.json +6 -0
  288. package/fixtures/openapi/VirtualMachineAccessBindingInput/invalid/task51.json +10 -0
  289. package/fixtures/openapi/VirtualMachineAccessBindingInput/valid/task51.json +10 -0
  290. package/fixtures/openapi/VirtualMachineActionInput/invalid/capacity-not-boolean.json +1 -0
  291. package/fixtures/openapi/VirtualMachineActionInput/valid/new-data-with-admission.json +1 -0
  292. package/fixtures/openapi/VirtualMachineCreateInput/valid/cloud-image.json +12 -0
  293. package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-config-injection.json +1 -0
  294. package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-existing-disk.json +1 -0
  295. package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-too-long.json +1 -0
  296. package/fixtures/openapi/VirtualMachineDiskChange/valid/personal-data.json +1 -0
  297. package/fixtures/openapi/VirtualMachineGuestAccessInput/invalid/task51.json +4 -0
  298. package/fixtures/openapi/VirtualMachineGuestAccessInput/valid/task51.json +6 -0
  299. package/fixtures/openapi/VirtualMachineSSHCommand/invalid/task51.json +9 -0
  300. package/fixtures/openapi/VirtualMachineSSHCommand/valid/task51.json +8 -0
  301. package/fixtures/openapi/VirtualMachineSSHCommandInput/invalid/task51.json +5 -0
  302. package/fixtures/openapi/VirtualMachineSSHCommandInput/valid/task51.json +5 -0
  303. package/fixtures/openapi/VirtualMachineSSHFrame/invalid/encoding.json +5 -0
  304. package/fixtures/openapi/VirtualMachineSSHFrame/invalid/task51.json +4 -0
  305. package/fixtures/openapi/VirtualMachineSSHFrame/valid/binary.json +5 -0
  306. package/fixtures/openapi/VirtualMachineSSHFrame/valid/task51.json +5 -0
  307. package/fixtures/openapi/VirtualMachineSSHSessionInput/invalid/task51.json +4 -0
  308. package/fixtures/openapi/VirtualMachineSSHSessionInput/valid/task51.json +5 -0
  309. package/fixtures/openapi/VirtualizationCapacityPolicyInput/invalid/task51.json +12 -0
  310. package/fixtures/openapi/VirtualizationCapacityPolicyInput/valid/task51.json +13 -0
  311. package/fixtures/openapi/VirtualizationImageInput/invalid/cloud-image-config-type.json +6 -0
  312. package/fixtures/openapi/VirtualizationImageInput/valid/cloud-image.json +11 -0
  313. package/fixtures/openapi/WorkbenchAssistantTask/invalid/kind.json +1 -0
  314. package/fixtures/openapi/WorkbenchAssistantTask/invalid/owner.json +1 -0
  315. package/fixtures/openapi/WorkbenchAssistantTask/valid/generate.json +1 -0
  316. package/fixtures/openapi/WorkbenchAssistantTask/valid/undo.json +1 -0
  317. package/fixtures/openapi/WorkbenchContextSelection/invalid/remote-image.json +1 -0
  318. package/fixtures/openapi/WorkbenchContextSelection/invalid/too-many-images.json +1 -0
  319. package/fixtures/openapi/WorkbenchContextSelection/valid/chat-image.json +9 -0
  320. package/fixtures/openapi/WorkbenchContextUsage/invalid/fractional-capacity.json +1 -0
  321. package/fixtures/openapi/WorkbenchContextUsage/invalid/negative.json +1 -0
  322. package/fixtures/openapi/WorkbenchContextUsage/valid/reported.json +1 -0
  323. package/fixtures/openapi/WorkbenchContextUsage/valid/unknown.json +1 -0
  324. package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/owner.json +1 -0
  325. package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/provider.json +1 -0
  326. package/fixtures/openapi/WorkbenchMainSessionRequest/valid/empty.json +1 -0
  327. package/fixtures/openapi/WorkbenchMainSessionRequest/valid/provider.json +1 -0
  328. package/fixtures/openapi/WorkbenchMessageSearchPage/invalid/role.json +12 -0
  329. package/fixtures/openapi/WorkbenchMessageSearchPage/valid/page.json +12 -0
  330. package/fixtures/openapi/WorkbenchMessageWindow/invalid/missing.json +1 -0
  331. package/fixtures/openapi/WorkbenchMessageWindow/valid/empty.json +3 -0
  332. package/fixtures/openapi/WorkbenchModelOption/invalid/zero-capacity.json +1 -0
  333. package/fixtures/openapi/WorkbenchModelOption/valid/capacity.json +1 -0
  334. package/fixtures/openapi/WorkbenchModelOption/valid/unknown.json +1 -0
  335. package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/invalid/basic.json +7 -0
  336. package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/basic.json +11 -0
  337. package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/selected-profile.json +23 -0
  338. package/fixtures/openapi/WorkbenchSendMessageStreamRequest/invalid/missing-required.json +1 -0
  339. package/fixtures/openapi/WorkbenchSendMessageStreamRequest/valid/mixed-professional-task.json +22 -0
  340. package/fixtures/openapi/WorkbenchTaskIntent/invalid/basic.json +5 -0
  341. package/fixtures/openapi/WorkbenchTaskIntent/invalid/oversized-capability-source.json +3 -0
  342. package/fixtures/openapi/WorkbenchTaskIntent/valid/basic.json +9 -0
  343. package/fixtures/openapi/WorkbenchTaskIntent/valid/independent-sources.json +17 -0
  344. package/gen/go/sohaapi/client.go +1 -1
  345. package/gen/go/sohaapi/personal_environment_test.go +25 -0
  346. package/gen/go/sohaapi/types.go +2679 -240
  347. package/gen/go/sohaapi/types_test.go +30 -30
  348. package/gen/go/sohaapi/vm_disk_test.go +21 -0
  349. package/gen/ts/sohaapi/index.ts +4150 -169
  350. package/go.mod +61 -49
  351. package/go.sum +202 -176
  352. package/knowledge/connector-definition.schema.json +6 -1
  353. package/knowledge/source-import-policy.schema.json +32 -0
  354. package/memory/memory-record.schema.json +1 -1
  355. package/openapi/soha-api.json +22230 -15693
  356. package/openapi/soha-api.yaml +2495 -12
  357. package/openapi/spec_generated.go +3 -3
  358. package/package.json +19 -3
  359. /package/fixtures/openapi/DeliveryExecutionHistoryPage/{invalid-kind.json → invalid/invalid-kind.json} +0 -0
  360. /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-build.json → valid/valid-build.json} +0 -0
  361. /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-empty.json → valid/valid-empty.json} +0 -0
@@ -0,0 +1,315 @@
1
+ package guest
2
+
3
+ import (
4
+ "bytes"
5
+ "crypto/sha256"
6
+ "encoding/base64"
7
+ "encoding/json"
8
+ "errors"
9
+ "fmt"
10
+ "image/jpeg"
11
+ "net/url"
12
+ "regexp"
13
+ "strings"
14
+ "time"
15
+ "unicode/utf8"
16
+
17
+ env "github.com/opensoha/soha-contracts/agent/environment"
18
+ )
19
+
20
+ const RuntimeOperationVersion = "opensoha.dev/personal-guest-runtime/v1"
21
+ const RuntimeResultVersion = "opensoha.dev/personal-guest-runtime-result/v1"
22
+ const ToolsPlanVersion = "opensoha.dev/personal-guest-tools/v1"
23
+ const RuntimeFileLimit = 44 << 10
24
+
25
+ var artifactDigestPattern = regexp.MustCompile(`^[a-f0-9]{64}$`)
26
+ var runtimeImagePattern = regexp.MustCompile(`^docker\.io/library/debian@sha256:[a-f0-9]{64}$`)
27
+ var debianVersionPattern = regexp.MustCompile(`^[0-9][a-zA-Z0-9.+:~_-]{0,127}$`)
28
+
29
+ // Runtime operations are frozen by Core in an encrypted durable task. The SSH
30
+ // launcher carries only the operation identity; mTLS retrieves the exact grant.
31
+ type RuntimeOperation struct {
32
+ SchemaVersion string `json:"schemaVersion"`
33
+ OperationID string `json:"operationId"`
34
+ Binding Binding `json:"binding"`
35
+ Kind string `json:"kind"`
36
+ Request env.Request `json:"request"`
37
+ Deadline time.Time `json:"deadline"`
38
+ File *RuntimeFileInput `json:"file,omitempty"`
39
+ Browser *BrowserAction `json:"browser,omitempty"`
40
+ DockerImage string `json:"dockerImage,omitempty"`
41
+ Source string `json:"source,omitempty"`
42
+ RequestHash string `json:"requestHash,omitempty"`
43
+ ArtifactSHA256 string `json:"artifactSHA256"`
44
+ }
45
+
46
+ type RuntimeFileInput struct {
47
+ Path string `json:"path"`
48
+ ContentBase64 string `json:"contentBase64,omitempty"`
49
+ ExpectedSHA256 string `json:"expectedSHA256,omitempty"`
50
+ }
51
+
52
+ type RuntimeFileResult struct {
53
+ Path string `json:"path"`
54
+ ContentBase64 string `json:"contentBase64,omitempty"`
55
+ SHA256 string `json:"sha256"`
56
+ Size int64 `json:"size"`
57
+ }
58
+
59
+ type BrowserAction struct {
60
+ Action string `json:"action"`
61
+ ControlID string `json:"controlId"`
62
+ Generation int64 `json:"generation"`
63
+ Owner string `json:"owner"`
64
+ TaskID string `json:"taskId,omitempty"`
65
+ URL string `json:"url,omitempty"`
66
+ Selector string `json:"selector,omitempty"`
67
+ Text string `json:"text,omitempty"`
68
+ X *int `json:"x,omitempty"`
69
+ Y *int `json:"y,omitempty"`
70
+ }
71
+
72
+ type BrowserFrame struct {
73
+ ControlID string `json:"controlId"`
74
+ Generation int64 `json:"generation"`
75
+ InstanceID string `json:"instanceId"`
76
+ URL string `json:"url,omitempty"`
77
+ Title string `json:"title,omitempty"`
78
+ Text string `json:"text,omitempty"`
79
+ JPEGBase64 string `json:"jpegBase64,omitempty"`
80
+ Width int `json:"width"`
81
+ Height int `json:"height"`
82
+ }
83
+
84
+ type RuntimeProof struct {
85
+ InstanceID string `json:"instanceId"`
86
+ ArtifactSHA256 string `json:"artifactSHA256"`
87
+ FilesystemID string `json:"filesystemId"`
88
+ Command bool `json:"command"`
89
+ FileRead bool `json:"fileRead"`
90
+ FileWrite bool `json:"fileWrite"`
91
+ Browser bool `json:"browser"`
92
+ Docker bool `json:"docker"`
93
+ CredentialsHidden bool `json:"credentialsHidden"`
94
+ NamespacesIsolated bool `json:"namespacesIsolated"`
95
+ DockerImage string `json:"dockerImage"`
96
+ }
97
+
98
+ type RuntimeResult struct {
99
+ SchemaVersion string `json:"schemaVersion"`
100
+ OperationID string `json:"operationId"`
101
+ BindingID string `json:"bindingId"`
102
+ Environment env.Result `json:"environment"`
103
+ File *RuntimeFileResult `json:"file,omitempty"`
104
+ Browser *BrowserFrame `json:"browser,omitempty"`
105
+ Proof *RuntimeProof `json:"proof,omitempty"`
106
+ }
107
+
108
+ // Tools installation cannot initialize data or accept commands/paths. Existing
109
+ // completed data and current owner authority are required before any host write.
110
+ type ToolsPlan struct {
111
+ SchemaVersion string `json:"schemaVersion"`
112
+ OperationID string `json:"operationId"`
113
+ Binding Binding `json:"binding"`
114
+ ArtifactSHA256 string `json:"artifactSHA256"`
115
+ DiskQuotaBytes int64 `json:"diskQuotaBytes"`
116
+ CPUQuotaMilli int64 `json:"cpuQuotaMilli"`
117
+ MemoryBytes int64 `json:"memoryBytes"`
118
+ PidsLimit int64 `json:"pidsLimit"`
119
+ DockerImage string `json:"dockerImage"`
120
+ BubblewrapVersion string `json:"bubblewrapVersion"`
121
+ DockerVersion string `json:"dockerVersion"`
122
+ ChromiumVersion string `json:"chromiumVersion"`
123
+ }
124
+
125
+ type ToolsResult struct {
126
+ OperationID string `json:"operationId"`
127
+ DataRef string `json:"dataRef"`
128
+ ArtifactSHA256 string `json:"artifactSHA256"`
129
+ }
130
+
131
+ func (p ToolsPlan) Validate(now time.Time) error {
132
+ data := DataPlan{SchemaVersion: DataPlanVersion, OperationID: p.OperationID, Binding: p.Binding, DiskQuotaBytes: p.DiskQuotaBytes}
133
+ quota := env.Quota{DiskBytes: p.DiskQuotaBytes, CPUUnits: p.CPUQuotaMilli, MemoryBytes: p.MemoryBytes, MaxProcesses: int(p.PidsLimit)}
134
+ if p.SchemaVersion != ToolsPlanVersion || data.Validate(now) != nil || quota.ValidatePersonalDocker() != nil || !artifactDigestPattern.MatchString(p.ArtifactSHA256) || !runtimeImagePattern.MatchString(p.DockerImage) {
135
+ return errors.New("invalid personal guest tools plan")
136
+ }
137
+ for _, v := range []string{p.BubblewrapVersion, p.DockerVersion, p.ChromiumVersion} {
138
+ if !debianVersionPattern.MatchString(v) {
139
+ return errors.New("invalid frozen Debian tools version")
140
+ }
141
+ }
142
+ return nil
143
+ }
144
+
145
+ func RuntimeFilePath(path string) bool {
146
+ if path == "" || len(path) > 1024 || !utf8.ValidString(path) || strings.ContainsAny(path, "\x00\r\n\\") {
147
+ return false
148
+ }
149
+ for _, part := range strings.Split(path, "/") {
150
+ if part == "" || part == "." || part == ".." {
151
+ return false
152
+ }
153
+ }
154
+ return true
155
+ }
156
+
157
+ func (op RuntimeOperation) Validate(now time.Time) error {
158
+ if !artifactDigestPattern.MatchString(op.ArtifactSHA256) {
159
+ return errors.New("invalid frozen runtime artifact")
160
+ }
161
+ if op.Source != "" && op.Source != "native" && op.Source != "user" || op.Source == "user" && !artifactDigestPattern.MatchString(op.RequestHash) || op.Source != "user" && op.RequestHash != "" {
162
+ return errors.New("invalid frozen runtime origin")
163
+ }
164
+ if op.SchemaVersion != RuntimeOperationVersion || !uuidPattern.MatchString(op.OperationID) || op.Binding.Validate() != nil || now.Before(op.Binding.IssuedAt) || !now.Before(op.Binding.ExpiresAt) || op.Request.Validate() != nil || op.Request.Lease.OwnerID != op.Binding.AssistantID || op.Request.CreateData || !now.Before(op.Deadline) || op.Deadline.After(op.Request.Lease.ExpiresAt) || op.Deadline.After(op.Binding.ExpiresAt) || op.Deadline.Sub(now) > time.Minute {
165
+ return errors.New("invalid or expired personal runtime operation")
166
+ }
167
+ if op.Request.Action == "delete_data" || op.Request.Action == "export_data" {
168
+ return errors.New("runtime operation cannot dispose or export data")
169
+ }
170
+ return op.validateKind()
171
+ }
172
+
173
+ func (op RuntimeOperation) validateKind() error {
174
+ switch op.Kind {
175
+ case "environment":
176
+ if op.File != nil || op.Browser != nil || op.DockerImage != "" || op.Request.Action == "execute" && op.Request.Lease.Mode != "restricted-write" {
177
+ return errors.New("invalid environment runtime payload")
178
+ }
179
+ case "file_read", "file_write":
180
+ return op.validateFile()
181
+ case "docker":
182
+ if op.File != nil || op.Browser != nil || !runtimeImagePattern.MatchString(op.DockerImage) || op.Request.Action != "execute" || op.Request.Lease.Mode != "restricted-write" {
183
+ return errors.New("invalid Docker runtime payload")
184
+ }
185
+ case "browser":
186
+ if op.Browser == nil || op.File != nil || op.DockerImage != "" || op.Request.Action != "inspect" {
187
+ return errors.New("invalid browser runtime payload")
188
+ }
189
+ if (op.Browser.Action == "click" || op.Browser.Action == "type" || op.Browser.Action == "pointer") && op.Request.Lease.Mode != "restricted-write" {
190
+ return errors.New("browser write requires a writable lease")
191
+ }
192
+ return op.Browser.Validate()
193
+ default:
194
+ return errors.New("unsupported personal runtime kind")
195
+ }
196
+ return nil
197
+ }
198
+
199
+ func (op RuntimeOperation) validateFile() error {
200
+ if op.File == nil || op.Browser != nil || op.DockerImage != "" || op.Request.Action != "inspect" || !RuntimeFilePath(op.File.Path) {
201
+ return errors.New("invalid personal runtime file")
202
+ }
203
+ if op.File.ExpectedSHA256 != "" && !artifactDigestPattern.MatchString(op.File.ExpectedSHA256) {
204
+ return errors.New("invalid expected file identity")
205
+ }
206
+ if op.Kind == "file_read" {
207
+ if op.File.ContentBase64 != "" || op.File.ExpectedSHA256 != "" {
208
+ return errors.New("read cannot contain write material")
209
+ }
210
+ return nil
211
+ }
212
+ data, err := base64.StdEncoding.DecodeString(op.File.ContentBase64)
213
+ if err != nil || len(data) > RuntimeFileLimit || !utf8.Valid(data) || op.Request.Lease.Mode != "restricted-write" {
214
+ return errors.New("invalid bounded personal file write")
215
+ }
216
+ return nil
217
+ }
218
+
219
+ func BrowserURL(value string) bool {
220
+ u, err := url.Parse(value)
221
+ if err != nil || len(value) > 4096 || u.User != nil || strings.ContainsAny(value, "\x00\r\n\\") {
222
+ return false
223
+ }
224
+ if u.Scheme == "file" {
225
+ return u.Host == "" && u.RawQuery == "" && strings.HasPrefix(u.Path, "/data/") && RuntimeFilePath(strings.TrimPrefix(u.Path, "/data/"))
226
+ }
227
+ return u.Scheme == "https" && u.Hostname() != "" && (u.Port() == "" || u.Port() == "443")
228
+ }
229
+
230
+ func (b BrowserAction) Validate() error {
231
+ if !uuidPattern.MatchString(b.ControlID) || b.Generation < 1 || b.Generation > MaxCounter || (b.Owner != "user" && b.Owner != "agent") || (b.TaskID != "" && !uuidPattern.MatchString(b.TaskID)) || (b.Owner == "agent" && b.TaskID == "") || len(b.Selector) > 2048 || len(b.Text) > 4096 || !utf8.ValidString(b.Selector+b.Text) || strings.ContainsRune(b.Selector+b.Text, 0) {
232
+ return errors.New("invalid browser control identity or input")
233
+ }
234
+ switch b.Action {
235
+ case "navigate":
236
+ if !BrowserURL(b.URL) || b.Selector != "" || b.Text != "" || b.X != nil || b.Y != nil {
237
+ return errors.New("bounded browser target required")
238
+ }
239
+ case "click", "type":
240
+ if b.URL != "" || b.Selector == "" || b.X != nil || b.Y != nil || b.Action == "click" && b.Text != "" {
241
+ return errors.New("invalid selected browser action")
242
+ }
243
+ case "pointer":
244
+ if b.Owner != "user" || b.URL != "" || b.Selector != "" || b.Text != "" || b.X == nil || b.Y == nil || *b.X < 0 || *b.X >= 800 || *b.Y < 0 || *b.Y >= 600 {
245
+ return errors.New("invalid human browser pointer")
246
+ }
247
+ case "snapshot", "acquire", "takeover", "return", "release":
248
+ if b.URL != "" || b.Selector != "" || b.Text != "" || b.X != nil || b.Y != nil {
249
+ return errors.New("control action cannot carry a site operation")
250
+ }
251
+ default:
252
+ return errors.New("unsupported browser action")
253
+ }
254
+ return nil
255
+ }
256
+
257
+ func (op RuntimeOperation) Digest() (string, error) {
258
+ raw, err := json.Marshal(op)
259
+ if err != nil || len(raw) > 320<<10 {
260
+ return "", errors.New("personal runtime frame exceeds its bound")
261
+ }
262
+ return fmt.Sprintf("sha256:%x", sha256.Sum256(raw)), nil
263
+ }
264
+
265
+ func (r RuntimeResult) ValidateFor(op RuntimeOperation) error {
266
+ if r.SchemaVersion != RuntimeResultVersion || r.OperationID != op.OperationID || r.BindingID != op.Binding.ID || r.Environment.ValidateFor(op.Request) != nil {
267
+ return errors.New("personal runtime receipt identity mismatch")
268
+ }
269
+ if r.Environment.ObservedAt.Before(op.Request.Lease.IssuedAt) || r.Environment.ObservedAt.After(op.Deadline.Add(5*time.Second)) {
270
+ return errors.New("runtime receipt exceeds original authority window")
271
+ }
272
+ if op.Request.Action != "release" && r.Environment.State != "active" && r.Environment.State != "quota_exceeded" {
273
+ return errors.New("runtime operation has no active observation")
274
+ }
275
+ if (op.Kind == "file_read" || op.Kind == "file_write") != (r.File != nil) || (op.Kind == "browser") != (r.Browser != nil) {
276
+ return errors.New("personal runtime receipt kind mismatch")
277
+ }
278
+ if r.File != nil {
279
+ f := r.File
280
+ if f.Path != op.File.Path || !artifactDigestPattern.MatchString(f.SHA256) || f.Size < 0 || f.Size > RuntimeFileLimit {
281
+ return errors.New("invalid personal file receipt")
282
+ }
283
+ if op.Kind == "file_read" {
284
+ data, err := base64.StdEncoding.DecodeString(f.ContentBase64)
285
+ if err != nil || int64(len(data)) != f.Size || !utf8.Valid(data) || fmt.Sprintf("%x", sha256.Sum256(data)) != f.SHA256 {
286
+ return errors.New("personal file content identity mismatch")
287
+ }
288
+ } else if f.ContentBase64 != "" {
289
+ return errors.New("write receipt must not echo content")
290
+ } else {
291
+ data, err := base64.StdEncoding.DecodeString(op.File.ContentBase64)
292
+ if err != nil || f.Size != int64(len(data)) || f.SHA256 != fmt.Sprintf("%x", sha256.Sum256(data)) {
293
+ return errors.New("write receipt differs from frozen content")
294
+ }
295
+ }
296
+ }
297
+ if r.Browser != nil {
298
+ b := r.Browser
299
+ if b.ControlID != op.Browser.ControlID || b.Generation != op.Browser.Generation || !uuidPattern.MatchString(b.InstanceID) || len(b.JPEGBase64) > 48<<10 || len(b.Text) > 4096 || len(b.Title) > 512 || len(b.URL) > 4096 || b.Width != 800 || b.Height != 600 {
300
+ return errors.New("invalid bounded browser receipt")
301
+ }
302
+ image, err := base64.StdEncoding.DecodeString(b.JPEGBase64)
303
+ if err != nil {
304
+ return errors.New("invalid browser pixels")
305
+ }
306
+ size, err := jpeg.DecodeConfig(bytes.NewReader(image))
307
+ if err != nil || size.Width != 800 || size.Height != 600 || !utf8.ValidString(b.URL+b.Title+b.Text) || b.URL != "about:blank" && !BrowserURL(b.URL) {
308
+ return errors.New("invalid browser frame or target")
309
+ }
310
+ }
311
+ if r.Proof != nil && (op.Kind != "environment" || op.Request.Action != "inspect" || !uuidPattern.MatchString(r.Proof.InstanceID) || r.Proof.ArtifactSHA256 != op.ArtifactSHA256 || r.Proof.FilesystemID != op.Binding.DataRef || !runtimeImagePattern.MatchString(r.Proof.DockerImage)) {
312
+ return errors.New("invalid personal runtime capability proof")
313
+ }
314
+ return nil
315
+ }
@@ -0,0 +1,75 @@
1
+ package guest
2
+
3
+ func (v *RuntimeFileResult) UnmarshalJSON(raw []byte) error {
4
+ type wire RuntimeFileResult
5
+ var d wire
6
+ if err := decodeStrictBounded(raw, &d, 64<<10, "path", "contentBase64", "sha256", "size"); err != nil {
7
+ return err
8
+ }
9
+ *v = RuntimeFileResult(d)
10
+ return nil
11
+ }
12
+ func (v *BrowserFrame) UnmarshalJSON(raw []byte) error {
13
+ type wire BrowserFrame
14
+ var d wire
15
+ if err := decodeStrictBounded(raw, &d, 80<<10, "controlId", "generation", "instanceId", "url", "title", "text", "jpegBase64", "width", "height"); err != nil {
16
+ return err
17
+ }
18
+ *v = BrowserFrame(d)
19
+ return nil
20
+ }
21
+ func (v *RuntimeProof) UnmarshalJSON(raw []byte) error {
22
+ type wire RuntimeProof
23
+ var d wire
24
+ if err := decodeStrictBounded(raw, &d, 4096, "instanceId", "artifactSHA256", "filesystemId", "command", "fileRead", "fileWrite", "browser", "docker", "credentialsHidden", "namespacesIsolated", "dockerImage"); err != nil {
25
+ return err
26
+ }
27
+ *v = RuntimeProof(d)
28
+ return nil
29
+ }
30
+
31
+ func (v *RuntimeOperation) UnmarshalJSON(raw []byte) error {
32
+ type wire RuntimeOperation
33
+ var decoded wire
34
+ if err := decodeStrictBounded(raw, &decoded, 320<<10, "schemaVersion", "operationId", "binding", "kind", "request", "deadline", "file", "browser", "dockerImage", "source", "requestHash", "artifactSHA256"); err != nil {
35
+ return err
36
+ }
37
+ *v = RuntimeOperation(decoded)
38
+ return nil
39
+ }
40
+ func (v *RuntimeFileInput) UnmarshalJSON(raw []byte) error {
41
+ type wire RuntimeFileInput
42
+ var decoded wire
43
+ if err := decodeStrictBounded(raw, &decoded, 64<<10, "path", "contentBase64", "expectedSHA256"); err != nil {
44
+ return err
45
+ }
46
+ *v = RuntimeFileInput(decoded)
47
+ return nil
48
+ }
49
+ func (v *BrowserAction) UnmarshalJSON(raw []byte) error {
50
+ type wire BrowserAction
51
+ var decoded wire
52
+ if err := decodeStrictBounded(raw, &decoded, 32<<10, "action", "controlId", "generation", "owner", "taskId", "url", "selector", "text", "x", "y"); err != nil {
53
+ return err
54
+ }
55
+ *v = BrowserAction(decoded)
56
+ return nil
57
+ }
58
+ func (v *RuntimeResult) UnmarshalJSON(raw []byte) error {
59
+ type wire RuntimeResult
60
+ var decoded wire
61
+ if err := decodeStrictBounded(raw, &decoded, 320<<10, "schemaVersion", "operationId", "bindingId", "environment", "file", "browser", "proof"); err != nil {
62
+ return err
63
+ }
64
+ *v = RuntimeResult(decoded)
65
+ return nil
66
+ }
67
+ func (v *ToolsPlan) UnmarshalJSON(raw []byte) error {
68
+ type wire ToolsPlan
69
+ var decoded wire
70
+ if err := decodeStrictBounded(raw, &decoded, 8192, "schemaVersion", "operationId", "binding", "artifactSHA256", "diskQuotaBytes", "cpuQuotaMilli", "memoryBytes", "pidsLimit", "dockerImage", "bubblewrapVersion", "dockerVersion", "chromiumVersion"); err != nil {
71
+ return err
72
+ }
73
+ *v = ToolsPlan(decoded)
74
+ return nil
75
+ }
@@ -0,0 +1,110 @@
1
+ package guest
2
+
3
+ import (
4
+ "encoding/base64"
5
+ "encoding/json"
6
+ "strings"
7
+ "testing"
8
+ "time"
9
+
10
+ env "github.com/opensoha/soha-contracts/agent/environment"
11
+ )
12
+
13
+ func runtimeFixture(t *testing.T) (RuntimeOperation, time.Time) {
14
+ t.Helper()
15
+ binding := guestFixture[Binding](t, "binding")
16
+ now := binding.IssuedAt.Add(time.Second)
17
+ op := RuntimeOperation{SchemaVersion: RuntimeOperationVersion, OperationID: binding.ID, Binding: binding, Kind: "environment", Deadline: now.Add(30 * time.Second), Request: env.Request{SchemaVersion: env.RequestVersion, Action: "inspect", Lease: env.Lease{SchemaVersion: env.LeaseVersion, ID: binding.ID, RunID: binding.ID, OwnerID: binding.AssistantID, ScopeHash: "sha256:" + strings.Repeat("a", 64), Mode: "restricted-write", IssuedAt: binding.IssuedAt, ExpiresAt: binding.ExpiresAt, Quota: env.Quota{DiskBytes: 16 << 20, MemoryBytes: 2 << 30, CPUUnits: 1000, MaxProcesses: 64}}}}
18
+ op.ArtifactSHA256 = strings.Repeat("a", 64)
19
+ return op, now
20
+ }
21
+
22
+ func TestRuntimeJSONRejectsAmbiguity(t *testing.T) {
23
+ for _, raw := range []string{`{"path":"a","path":"b"}`, `{"Path":"a"}`, `{"path":"a","unknown":true}`} {
24
+ var value RuntimeFileInput
25
+ if json.Unmarshal([]byte(raw), &value) == nil {
26
+ t.Fatal("ambiguous runtime JSON accepted", raw)
27
+ }
28
+ }
29
+ for _, raw := range []string{`{"url":"https://example.com/","method":"GET","method":"POST"}`, `{"url":"https://example.com/","method":"CONNECT"}`, `{"url":"file:///etc/shadow","method":"GET"}`} {
30
+ var value BrowserNetworkRequest
31
+ if json.Unmarshal([]byte(raw), &value) == nil {
32
+ t.Fatal("invalid network frame accepted", raw)
33
+ }
34
+ }
35
+ }
36
+
37
+ func TestRuntimeAuthorityAndWriteBounds(t *testing.T) {
38
+ op, now := runtimeFixture(t)
39
+ if err := op.Validate(now); err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ for name, change := range map[string]func(*RuntimeOperation){
43
+ "owner": func(o *RuntimeOperation) { o.Request.Lease.OwnerID = o.Binding.OwnerID },
44
+ "expired": func(o *RuntimeOperation) { o.Deadline = now },
45
+ "long deadline": func(o *RuntimeOperation) { o.Deadline = now.Add(time.Minute + time.Second) },
46
+ "creation": func(o *RuntimeOperation) { o.Request.CreateData = true },
47
+ "disposal": func(o *RuntimeOperation) { o.Request.Action = "delete_data" },
48
+ "wrong kind": func(o *RuntimeOperation) { o.Kind = "root" },
49
+ } {
50
+ t.Run(name, func(t *testing.T) {
51
+ other := op
52
+ change(&other)
53
+ if other.Validate(now) == nil {
54
+ t.Fatal("invalid authority accepted")
55
+ }
56
+ })
57
+ }
58
+ op.Kind, op.File = "file_write", &RuntimeFileInput{Path: "notes/example.txt", ContentBase64: base64.StdEncoding.EncodeToString([]byte("ok"))}
59
+ if err := op.Validate(now); err != nil {
60
+ t.Fatal(err)
61
+ }
62
+ op.Request.Lease.Mode = "observe-only"
63
+ if op.Validate(now) == nil {
64
+ t.Fatal("read-only lease wrote a file")
65
+ }
66
+ op.Request.Lease.Mode = "restricted-write"
67
+ op.File.ContentBase64 = base64.StdEncoding.EncodeToString(make([]byte, RuntimeFileLimit+1))
68
+ if op.Validate(now) == nil {
69
+ t.Fatal("oversized write accepted")
70
+ }
71
+ }
72
+
73
+ func TestRuntimePathsAndBrowserTargets(t *testing.T) {
74
+ for _, path := range []string{"", "/etc/shadow", "..", "foo/../bar", "a//b", "a/./b", "a\\b", "x\x00"} {
75
+ if RuntimeFilePath(path) {
76
+ t.Fatalf("unsafe path %q accepted", path)
77
+ }
78
+ }
79
+ for _, target := range []string{"file:///etc/passwd", "file:///data/../private", "file:///data/%2e%2e/private", "file://host/data/a", "https://user:pass@example.com/", "https://example.com:8080/", "javascript:alert(1)", "http://example.com"} {
80
+ if BrowserURL(target) {
81
+ t.Fatalf("unsafe target %q accepted", target)
82
+ }
83
+ }
84
+ for _, target := range []string{"https://example.com/", "file:///data/acceptance.html"} {
85
+ if !BrowserURL(target) {
86
+ t.Fatalf("bounded target %q refused", target)
87
+ }
88
+ }
89
+ }
90
+
91
+ func TestRuntimeReceiptCorrelationAndDigest(t *testing.T) {
92
+ op, now := runtimeFixture(t)
93
+ result := RuntimeResult{SchemaVersion: RuntimeResultVersion, OperationID: op.OperationID, BindingID: op.Binding.ID, Environment: env.Result{SchemaVersion: env.ResultVersion, LeaseID: op.Request.Lease.ID, RunID: op.Request.Lease.RunID, State: "active", ObservedAt: now, Snapshot: &env.Snapshot{SchemaVersion: env.SnapshotVersion, LeaseID: op.Request.Lease.ID, RunID: op.Request.Lease.RunID, State: "active", ObservedAt: now}}}
94
+ if err := result.ValidateFor(op); err != nil {
95
+ t.Fatal(err)
96
+ }
97
+ result.BindingID = op.Binding.DataRef
98
+ if result.ValidateFor(op) == nil {
99
+ t.Fatal("foreign receipt accepted")
100
+ }
101
+ digest, err := op.Digest()
102
+ if err != nil {
103
+ t.Fatal(err)
104
+ }
105
+ op.Kind, op.File = "file_read", &RuntimeFileInput{Path: "a"}
106
+ changed, err := op.Digest()
107
+ if err != nil || digest == changed {
108
+ t.Fatal("payload did not change request identity")
109
+ }
110
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "type": "object",
3
+ "additionalProperties": false,
4
+ "required": [
5
+ "url",
6
+ "method"
7
+ ],
8
+ "properties": {
9
+ "url": {
10
+ "type": "string",
11
+ "pattern": "^https://",
12
+ "maxLength": 4096
13
+ },
14
+ "method": {
15
+ "enum": [
16
+ "GET",
17
+ "HEAD",
18
+ "POST",
19
+ "PUT",
20
+ "PATCH",
21
+ "DELETE",
22
+ "OPTIONS"
23
+ ]
24
+ },
25
+ "headers": {
26
+ "type": "object",
27
+ "maxProperties": 64,
28
+ "additionalProperties": {
29
+ "type": "string",
30
+ "maxLength": 8192
31
+ }
32
+ },
33
+ "bodyBase64": {
34
+ "type": "string",
35
+ "maxLength": 87384
36
+ }
37
+ },
38
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
39
+ "$id": "https://opensoha.dev/schemas/personal-browser-network-request.schema.json"
40
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "type": "object",
3
+ "additionalProperties": false,
4
+ "required": [
5
+ "status",
6
+ "headers",
7
+ "bodyBase64"
8
+ ],
9
+ "properties": {
10
+ "status": {
11
+ "type": "integer",
12
+ "minimum": 100,
13
+ "maximum": 599
14
+ },
15
+ "headers": {
16
+ "type": "array",
17
+ "maxItems": 128,
18
+ "items": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "required": [
22
+ "name",
23
+ "value"
24
+ ],
25
+ "properties": {
26
+ "name": {
27
+ "type": "string",
28
+ "maxLength": 128
29
+ },
30
+ "value": {
31
+ "type": "string",
32
+ "maxLength": 8192
33
+ }
34
+ }
35
+ }
36
+ },
37
+ "bodyBase64": {
38
+ "type": "string",
39
+ "maxLength": 1398104
40
+ }
41
+ },
42
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
43
+ "$id": "https://opensoha.dev/schemas/personal-browser-network-response.schema.json"
44
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://opensoha.dev/schemas/personal-environment-export.schema.json",
4
+ "title": "Personal environment export receipt",
5
+ "description": "Final receipt after all length-prefixed TAR chunks, producer completion and owned helper cleanup. The receiver must check size, SHA-256, exact original lease/owner/scope and fresh observedAt before exposing any file.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schemaVersion",
10
+ "leaseId",
11
+ "runId",
12
+ "ownerId",
13
+ "scopeHash",
14
+ "sizeBytes",
15
+ "sha256",
16
+ "observedAt"
17
+ ],
18
+ "properties": {
19
+ "schemaVersion": {
20
+ "const": "opensoha.dev/personal-environment-export/v1"
21
+ },
22
+ "leaseId": {
23
+ "type": "string",
24
+ "minLength": 1,
25
+ "maxLength": 128
26
+ },
27
+ "runId": {
28
+ "type": "string",
29
+ "minLength": 1,
30
+ "maxLength": 128
31
+ },
32
+ "ownerId": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "maxLength": 128
36
+ },
37
+ "scopeHash": {
38
+ "type": "string",
39
+ "pattern": "^sha256:[a-f0-9]{64}$"
40
+ },
41
+ "sizeBytes": {
42
+ "type": "integer",
43
+ "minimum": 1,
44
+ "maximum": 268435456
45
+ },
46
+ "sha256": {
47
+ "type": "string",
48
+ "pattern": "^sha256:[a-f0-9]{64}$"
49
+ },
50
+ "observedAt": {
51
+ "type": "string",
52
+ "format": "date-time"
53
+ }
54
+ }
55
+ }