@opensoha/contracts 0.1.23 → 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.
- package/COMPATIBILITY.md +95 -0
- package/README.md +24 -6
- package/agent/environment/README.md +55 -0
- package/agent/environment/export.go +139 -0
- package/agent/environment/export_test.go +62 -0
- package/agent/environment/preflight.go +62 -0
- package/agent/environment/preflight_test.go +41 -0
- package/agent/environment/protocol.go +221 -0
- package/agent/environment/protocol_test.go +81 -0
- package/agent/environment-lease.schema.json +2 -2
- package/agent/environment-template.schema.json +1 -1
- package/agent/guest/browser_network.go +77 -0
- package/agent/guest/data.go +62 -0
- package/agent/guest/protocol.go +235 -0
- package/agent/guest/protocol_test.go +142 -0
- package/agent/guest/renewal.go +56 -0
- package/agent/guest/renewal_test.go +53 -0
- package/agent/guest/runtime.go +315 -0
- package/agent/guest/runtime_json.go +75 -0
- package/agent/guest/runtime_test.go +110 -0
- package/agent/personal-browser-network-request.schema.json +40 -0
- package/agent/personal-browser-network-response.schema.json +44 -0
- package/agent/personal-environment-export.schema.json +55 -0
- package/agent/personal-environment-preflight-request.schema.json +52 -0
- package/agent/personal-environment-preflight-result.schema.json +69 -0
- package/agent/personal-environment-request.schema.json +137 -0
- package/agent/personal-environment-result.schema.json +101 -0
- package/agent/personal-guest-binding.schema.json +92 -0
- package/agent/personal-guest-data-plan.schema.json +32 -0
- package/agent/personal-guest-enrollment.schema.json +21 -0
- package/agent/personal-guest-heartbeat.schema.json +44 -0
- package/agent/personal-guest-provisioning.schema.json +71 -0
- package/agent/personal-guest-renewal.schema.json +33 -0
- package/agent/personal-guest-runtime-result.schema.json +164 -0
- package/agent/personal-guest-runtime.schema.json +304 -0
- package/agent/personal-guest-tools.schema.json +75 -0
- package/auth/catalog_generated.go +2 -2
- package/auth/permission-catalog.json +32 -1
- package/auth/token-claims.schema.json +5 -0
- package/compat/consumer-revisions.json +3 -2
- package/compat/json-schema-ecosystem-0.1.0.json +1 -1
- package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
- package/connectors/connector-action.schema.json +138 -0
- package/fixtures/json-schema/connector-action/invalid/empty-values.json +11 -0
- package/fixtures/json-schema/connector-action/invalid/invalid-expiry.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/lowercase-alias.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/unknown-entry-field.json +17 -0
- package/fixtures/json-schema/connector-action/invalid/unknown-frame-field.json +17 -0
- package/fixtures/json-schema/connector-action/invalid/unknown-version.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/unversioned-reference.json +16 -0
- package/fixtures/json-schema/connector-action/invalid/zero-version.json +16 -0
- package/fixtures/json-schema/connector-action/valid/pinned.json +16 -0
- package/fixtures/json-schema/connector-definition/invalid/credential-userinfo.json +8 -0
- package/fixtures/json-schema/connector-definition/invalid/reference-traversal.json +8 -0
- package/fixtures/json-schema/connector-definition/invalid/zero-version.json +8 -0
- package/fixtures/json-schema/connector-definition/valid/canonical.json +8 -0
- package/fixtures/json-schema/connector-definition/valid/pinned.json +8 -0
- package/fixtures/json-schema/personal-browser-network-request/invalid/bounded.json +4 -0
- package/fixtures/json-schema/personal-browser-network-request/valid/bounded.json +4 -0
- package/fixtures/json-schema/personal-browser-network-response/invalid/bounded.json +5 -0
- package/fixtures/json-schema/personal-browser-network-response/valid/bounded.json +5 -0
- package/fixtures/json-schema/personal-environment-export/invalid/extra.json +11 -0
- package/fixtures/json-schema/personal-environment-export/invalid/ownerId.json +10 -0
- package/fixtures/json-schema/personal-environment-export/invalid/sha256.json +10 -0
- package/fixtures/json-schema/personal-environment-export/invalid/sizeBytes.json +10 -0
- package/fixtures/json-schema/personal-environment-export/valid/complete.json +10 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/disk.json +11 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/network.json +11 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/node.json +12 -0
- package/fixtures/json-schema/personal-environment-preflight-request/invalid/owner.json +12 -0
- package/fixtures/json-schema/personal-environment-preflight-request/valid/bounded.json +11 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/disk.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/network.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/no-hard-quota.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/node.json +16 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/not-ready.json +15 -0
- package/fixtures/json-schema/personal-environment-preflight-result/invalid/owner.json +16 -0
- package/fixtures/json-schema/personal-environment-preflight-result/valid/bounded.json +15 -0
- package/fixtures/json-schema/personal-environment-request/invalid/delete-data-argv.json +24 -0
- package/fixtures/json-schema/personal-environment-request/invalid/delete-data-createData.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/delete-data-taskId.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/export-argv.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/export-createData.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/export-taskId.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/extra-command.json +24 -0
- package/fixtures/json-schema/personal-environment-request/invalid/host-workspace.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/image-injection.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/network-egress.json +21 -0
- package/fixtures/json-schema/personal-environment-request/invalid/no-command.json +21 -0
- package/fixtures/json-schema/personal-environment-request/invalid/recreate-on-release.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/relative-command.json +24 -0
- package/fixtures/json-schema/personal-environment-request/invalid/released-identity.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-empty.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-host-path.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-noncanonical.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-null.json +27 -0
- package/fixtures/json-schema/personal-environment-request/invalid/task-on-inspection.json +22 -0
- package/fixtures/json-schema/personal-environment-request/invalid/unbounded-quota.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/acquire.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/delete-data.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/execute.json +26 -0
- package/fixtures/json-schema/personal-environment-request/valid/export-data.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/inspect.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/provision.json +22 -0
- package/fixtures/json-schema/personal-environment-request/valid/release.json +21 -0
- package/fixtures/json-schema/personal-environment-request/valid/task-workspace.json +27 -0
- package/fixtures/json-schema/personal-environment-result/invalid/active-without-observation.json +7 -0
- package/fixtures/json-schema/personal-environment-result/invalid/deleted-output.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/deleted-snapshot.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/deleted-taskId.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/private-handle.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/stopped-fake-observation.json +20 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-empty.json +22 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-host-path.json +22 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-null.json +22 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-on-stopped.json +8 -0
- package/fixtures/json-schema/personal-environment-result/invalid/task-without-command-output.json +21 -0
- package/fixtures/json-schema/personal-environment-result/valid/active.json +21 -0
- package/fixtures/json-schema/personal-environment-result/valid/deleted.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/expired.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/missing.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/released.json +7 -0
- package/fixtures/json-schema/personal-environment-result/valid/task-workspace.json +22 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/host-path.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/invalid-policy.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/invalid-serial.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/legacy-version.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/missing-generation.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/missing-identity.json +16 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/missing-provider-identity.json +16 -0
- package/fixtures/json-schema/personal-guest-binding/invalid/raw-provider-identity.json +17 -0
- package/fixtures/json-schema/personal-guest-binding/valid/minimal.json +17 -0
- package/fixtures/json-schema/personal-guest-data-plan/invalid/quota.json +1 -0
- package/fixtures/json-schema/personal-guest-data-plan/valid/basic.json +1 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/capabilities.json +7 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/legacy-version.json +4 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/missing-identity.json +3 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/ownerId.json +5 -0
- package/fixtures/json-schema/personal-guest-enrollment/invalid/token.json +5 -0
- package/fixtures/json-schema/personal-guest-enrollment/valid/minimal.json +4 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/argv.json +11 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/legacy-version.json +8 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/missing-identity.json +7 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/owner.json +9 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/self-ready.json +9 -0
- package/fixtures/json-schema/personal-guest-heartbeat/invalid/zero-sequence.json +8 -0
- package/fixtures/json-schema/personal-guest-heartbeat/valid/minimal.json +8 -0
- package/fixtures/json-schema/personal-guest-provisioning/invalid/network.json +1 -0
- package/fixtures/json-schema/personal-guest-provisioning/valid/basic.json +1 -0
- package/fixtures/json-schema/personal-guest-renewal/invalid/unknown.json +1 -0
- package/fixtures/json-schema/personal-guest-renewal/valid/basic.json +1 -0
- package/fixtures/json-schema/personal-guest-runtime/invalid/minimal.json +45 -0
- package/fixtures/json-schema/personal-guest-runtime/valid/minimal.json +45 -0
- package/fixtures/json-schema/personal-guest-runtime-result/invalid/minimal.json +28 -0
- package/fixtures/json-schema/personal-guest-runtime-result/valid/minimal.json +27 -0
- package/fixtures/json-schema/personal-guest-tools/invalid/minimal.json +31 -0
- package/fixtures/json-schema/personal-guest-tools/valid/minimal.json +30 -0
- package/fixtures/json-schema/source-import-policy/invalid/case-variant-scope.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/case-variant-version.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/duplicate-grant.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/empty-id.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/future-version.json +4 -0
- package/fixtures/json-schema/source-import-policy/invalid/unknown-field.json +4 -0
- package/fixtures/json-schema/source-import-policy/valid/deny-all.json +4 -0
- package/fixtures/json-schema/source-import-policy/valid/shared.json +4 -0
- package/fixtures/json-schema/token-claims/invalid/authz-negative.json +23 -0
- package/fixtures/json-schema/token-claims/invalid/authz-string.json +23 -0
- package/fixtures/json-schema/token-claims/invalid/authz-zero.json +23 -0
- package/fixtures/json-schema/token-claims/valid/versioned-session.json +23 -0
- package/fixtures/openapi/AgentRunEventListEnvelope/invalid/basic.json +8 -0
- package/fixtures/openapi/AgentRunEventListEnvelope/valid/basic.json +8 -0
- package/fixtures/openapi/AgentRunEventListEnvelope/valid/native.json +1 -0
- package/fixtures/openapi/AgentRunResumeRequest/invalid/basic.json +3 -0
- package/fixtures/openapi/AgentRunResumeRequest/valid/basic.json +3 -0
- package/fixtures/openapi/AgentSSHCommandRequest/invalid/task51.json +24 -0
- package/fixtures/openapi/AgentSSHCommandRequest/valid/task51.json +23 -0
- package/fixtures/openapi/AgentSSHCommandResult/invalid/task51.json +8 -0
- package/fixtures/openapi/AgentSSHCommandResult/valid/task51.json +7 -0
- package/fixtures/openapi/AgentSSHSessionRequest/invalid/task51.json +20 -0
- package/fixtures/openapi/AgentSSHSessionRequest/valid/task51.json +19 -0
- package/fixtures/openapi/CapabilityTaskInput/invalid/duplicate-scope.json +36 -0
- package/fixtures/openapi/CapabilityTaskInput/valid/scoped.json +32 -0
- package/fixtures/openapi/DockerQuickCreateHostInput/invalid/guest-access.json +7 -0
- package/fixtures/openapi/DockerQuickCreateHostInput/valid/guest-access.json +9 -0
- package/fixtures/openapi/KnowledgeSourceDocument/invalid/tombstone-with-content.json +1 -0
- package/fixtures/openapi/KnowledgeSourceDocument/valid/tombstone.json +1 -0
- package/fixtures/openapi/KubernetesResourceUpdatePlanRequest/invalid/missing-required.json +1 -0
- package/fixtures/openapi/KubernetesResourceYaml/invalid/missing-required.json +1 -0
- package/fixtures/openapi/ObservabilityDataSource/invalid/missing-required.json +1 -0
- package/fixtures/openapi/PersonalAgentAccess/invalid/invalid.json +1 -0
- package/fixtures/openapi/PersonalAgentAccess/valid/valid.json +1 -0
- package/fixtures/openapi/PersonalAgentAccessDecision/invalid/invalid.json +1 -0
- package/fixtures/openapi/PersonalAgentAccessDecision/valid/valid.json +1 -0
- package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/invalid/owner.json +8 -0
- package/fixtures/openapi/PersonalAssistantArtifactListEnvelope/valid/side-chat.json +9 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/motion.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/owner.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/remote.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/invalid/svg.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/raster.json +1 -0
- package/fixtures/openapi/PersonalAssistantAvatarRequest/valid/reset.json +1 -0
- package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/empty.json +1 -0
- package/fixtures/openapi/PersonalAssistantUpdateRequest/invalid/owner.json +1 -0
- package/fixtures/openapi/PersonalAssistantUpdateRequest/valid/name.json +1 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/host-url.json +12 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/lifetime.json +11 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/invalid/unknown-agent.json +11 -0
- package/fixtures/openapi/PersonalDockerEnvironmentPolicy/valid/basic.json +11 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/invented-state.json +6 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/missing-state.json +5 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/negative-generation.json +7 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/network.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/private-node.json +24 -0
- package/fixtures/openapi/PersonalEnvironment/invalid/unsupported-backend.json +7 -0
- package/fixtures/openapi/PersonalEnvironment/valid/deleted.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/deleting.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/deletion_unknown.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/frozen-binding.json +13 -0
- package/fixtures/openapi/PersonalEnvironment/valid/ready.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/unknown.json +23 -0
- package/fixtures/openapi/PersonalEnvironment/valid/unprovisioned.json +6 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/command.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/node.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/owner.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/invalid/quota.json +10 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/reconcile.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/sleep.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentActionRequest/valid/wake.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/empty-name.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/long-name.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/missing-name.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/owner.json +6 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/invalid/stale-version.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataDeletionRequest/valid/confirmed.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/assistantId.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/environmentVersion.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/invalid/ownerId.json +5 -0
- package/fixtures/openapi/PersonalEnvironmentDataExportRequest/valid/current.json +4 -0
- package/fixtures/openapi/PersonalEnvironmentEnvelope/invalid/missing-data.json +1 -0
- package/fixtures/openapi/PersonalEnvironmentEnvelope/valid/state.json +8 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/cpu.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/disk.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/network.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/invalid/processes.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentQuota/valid/basic.json +7 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/mode.json +13 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/network.json +13 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/invalid/node.json +14 -0
- package/fixtures/openapi/PersonalEnvironmentTemplate/valid/template.json +13 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/missing.json +1 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/invalid/unbounded.json +1317 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/choices.json +17 -0
- package/fixtures/openapi/PersonalEnvironmentTemplateListEnvelope/valid/empty.json +3 -0
- package/fixtures/openapi/PersonalEnvironmentUsage/invalid/negative.json +6 -0
- package/fixtures/openapi/PersonalEnvironmentUsage/valid/usage.json +6 -0
- package/fixtures/openapi/PersonalFeedFeedback/invalid/owner.json +4 -0
- package/fixtures/openapi/PersonalFeedFeedback/valid/liked.json +3 -0
- package/fixtures/openapi/PersonalFeedItem/invalid/no-source.json +9 -0
- package/fixtures/openapi/PersonalFeedItem/valid/post.json +15 -0
- package/fixtures/openapi/PersonalFeedSettings/invalid/owner.json +6 -0
- package/fixtures/openapi/PersonalFeedSettings/valid/default.json +5 -0
- package/fixtures/openapi/PersonalGuestPreparationInput/invalid/foreign-owner.json +5 -0
- package/fixtures/openapi/PersonalGuestPreparationInput/invalid/unsafe-counter.json +4 -0
- package/fixtures/openapi/PersonalGuestPreparationInput/valid/ed25519-csr.json +4 -0
- package/fixtures/openapi/PersonalGuestRuntimeEnvelope/invalid/minimal.json +10 -0
- package/fixtures/openapi/PersonalGuestRuntimeEnvelope/valid/minimal.json +9 -0
- package/fixtures/openapi/PersonalGuestRuntimeInput/invalid/minimal.json +6 -0
- package/fixtures/openapi/PersonalGuestRuntimeInput/valid/minimal.json +8 -0
- package/fixtures/openapi/PersonalGuestToolsInput/invalid/minimal.json +5 -0
- package/fixtures/openapi/PersonalGuestToolsInput/valid/minimal.json +4 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/device.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/fractional-size.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/network.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/owner.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/provider.json +15 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/invalid/unknown.json +16 -0
- package/fixtures/openapi/PersonalVMEnvironmentPolicy/valid/pve.json +15 -0
- package/fixtures/openapi/PersonalWorkspaceRuntime/invalid/shared.json +3 -0
- package/fixtures/openapi/PersonalWorkspaceRuntime/valid/shared.json +4 -0
- package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/invalid/shared.json +5 -0
- package/fixtures/openapi/PersonalWorkspaceRuntimeEnvelope/valid/shared.json +6 -0
- package/fixtures/openapi/SecretGenerateSSHKeyRequest/invalid/task51.json +6 -0
- package/fixtures/openapi/SecretGenerateSSHKeyRequest/valid/task51.json +12 -0
- package/fixtures/openapi/SecretReferenceUsage/invalid/version.json +6 -0
- package/fixtures/openapi/SecretReferenceUsage/valid/version.json +8 -0
- package/fixtures/openapi/VMSSHCredentialRedemptionInput/invalid/task51.json +7 -0
- package/fixtures/openapi/VMSSHCredentialRedemptionInput/valid/task51.json +6 -0
- package/fixtures/openapi/VirtualMachineAccessBindingInput/invalid/task51.json +10 -0
- package/fixtures/openapi/VirtualMachineAccessBindingInput/valid/task51.json +10 -0
- package/fixtures/openapi/VirtualMachineActionInput/invalid/capacity-not-boolean.json +1 -0
- package/fixtures/openapi/VirtualMachineActionInput/valid/new-data-with-admission.json +1 -0
- package/fixtures/openapi/VirtualMachineCreateInput/valid/cloud-image.json +12 -0
- package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-config-injection.json +1 -0
- package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-existing-disk.json +1 -0
- package/fixtures/openapi/VirtualMachineDiskChange/invalid/serial-too-long.json +1 -0
- package/fixtures/openapi/VirtualMachineDiskChange/valid/personal-data.json +1 -0
- package/fixtures/openapi/VirtualMachineGuestAccessInput/invalid/task51.json +4 -0
- package/fixtures/openapi/VirtualMachineGuestAccessInput/valid/task51.json +6 -0
- package/fixtures/openapi/VirtualMachineSSHCommand/invalid/task51.json +9 -0
- package/fixtures/openapi/VirtualMachineSSHCommand/valid/task51.json +8 -0
- package/fixtures/openapi/VirtualMachineSSHCommandInput/invalid/task51.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHCommandInput/valid/task51.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/invalid/encoding.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/invalid/task51.json +4 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/valid/binary.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHFrame/valid/task51.json +5 -0
- package/fixtures/openapi/VirtualMachineSSHSessionInput/invalid/task51.json +4 -0
- package/fixtures/openapi/VirtualMachineSSHSessionInput/valid/task51.json +5 -0
- package/fixtures/openapi/VirtualizationCapacityPolicyInput/invalid/task51.json +12 -0
- package/fixtures/openapi/VirtualizationCapacityPolicyInput/valid/task51.json +13 -0
- package/fixtures/openapi/VirtualizationImageInput/invalid/cloud-image-config-type.json +6 -0
- package/fixtures/openapi/VirtualizationImageInput/valid/cloud-image.json +11 -0
- package/fixtures/openapi/WorkbenchAssistantTask/invalid/kind.json +1 -0
- package/fixtures/openapi/WorkbenchAssistantTask/invalid/owner.json +1 -0
- package/fixtures/openapi/WorkbenchAssistantTask/valid/generate.json +1 -0
- package/fixtures/openapi/WorkbenchAssistantTask/valid/undo.json +1 -0
- package/fixtures/openapi/WorkbenchContextSelection/invalid/remote-image.json +1 -0
- package/fixtures/openapi/WorkbenchContextSelection/invalid/too-many-images.json +1 -0
- package/fixtures/openapi/WorkbenchContextSelection/valid/chat-image.json +9 -0
- package/fixtures/openapi/WorkbenchContextUsage/invalid/fractional-capacity.json +1 -0
- package/fixtures/openapi/WorkbenchContextUsage/invalid/negative.json +1 -0
- package/fixtures/openapi/WorkbenchContextUsage/valid/reported.json +1 -0
- package/fixtures/openapi/WorkbenchContextUsage/valid/unknown.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/owner.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/invalid/provider.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/valid/empty.json +1 -0
- package/fixtures/openapi/WorkbenchMainSessionRequest/valid/provider.json +1 -0
- package/fixtures/openapi/WorkbenchMessageSearchPage/invalid/role.json +12 -0
- package/fixtures/openapi/WorkbenchMessageSearchPage/valid/page.json +12 -0
- package/fixtures/openapi/WorkbenchMessageWindow/invalid/missing.json +1 -0
- package/fixtures/openapi/WorkbenchMessageWindow/valid/empty.json +3 -0
- package/fixtures/openapi/WorkbenchModelOption/invalid/zero-capacity.json +1 -0
- package/fixtures/openapi/WorkbenchModelOption/valid/capacity.json +1 -0
- package/fixtures/openapi/WorkbenchModelOption/valid/unknown.json +1 -0
- package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/invalid/basic.json +7 -0
- package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/basic.json +11 -0
- package/fixtures/openapi/WorkbenchProfessionalCapabilityInput/valid/selected-profile.json +23 -0
- package/fixtures/openapi/WorkbenchSendMessageStreamRequest/invalid/missing-required.json +1 -0
- package/fixtures/openapi/WorkbenchSendMessageStreamRequest/valid/mixed-professional-task.json +22 -0
- package/fixtures/openapi/WorkbenchTaskIntent/invalid/basic.json +5 -0
- package/fixtures/openapi/WorkbenchTaskIntent/invalid/oversized-capability-source.json +3 -0
- package/fixtures/openapi/WorkbenchTaskIntent/valid/basic.json +9 -0
- package/fixtures/openapi/WorkbenchTaskIntent/valid/independent-sources.json +17 -0
- package/gen/go/sohaapi/client.go +1 -1
- package/gen/go/sohaapi/personal_environment_test.go +25 -0
- package/gen/go/sohaapi/types.go +2195 -205
- package/gen/go/sohaapi/vm_disk_test.go +21 -0
- package/gen/ts/sohaapi/index.ts +3427 -485
- package/go.mod +61 -49
- package/go.sum +202 -176
- package/knowledge/connector-definition.schema.json +6 -1
- package/knowledge/source-import-policy.schema.json +32 -0
- package/memory/memory-record.schema.json +1 -1
- package/openapi/soha-api.json +12645 -7451
- package/openapi/soha-api.yaml +1806 -12
- package/openapi/spec_generated.go +3 -3
- package/package.json +19 -3
- /package/fixtures/openapi/DeliveryExecutionHistoryPage/{invalid-kind.json → invalid/invalid-kind.json} +0 -0
- /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-build.json → valid/valid-build.json} +0 -0
- /package/fixtures/openapi/DeliveryExecutionHistoryPage/{valid-empty.json → valid/valid-empty.json} +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Package environment carries the Core-to-Agent personal environment protocol.
|
|
2
|
+
// JSON Schemas in agent/ own the wire shapes; Docker handles stay Agent-private.
|
|
3
|
+
package environment
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"bytes"
|
|
7
|
+
"encoding/json"
|
|
8
|
+
"errors"
|
|
9
|
+
"regexp"
|
|
10
|
+
"strings"
|
|
11
|
+
"time"
|
|
12
|
+
"unicode/utf8"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const RequestVersion = "opensoha.dev/personal-environment-request/v1"
|
|
16
|
+
const ResultVersion = "opensoha.dev/personal-environment-result/v1"
|
|
17
|
+
const LeaseVersion = "opensoha.dev/environment-lease/v1"
|
|
18
|
+
const SnapshotVersion = "opensoha.dev/environment-snapshot/v1"
|
|
19
|
+
|
|
20
|
+
type Quota struct {
|
|
21
|
+
DiskBytes int64 `json:"diskBytes"`
|
|
22
|
+
MemoryBytes int64 `json:"memoryBytes"`
|
|
23
|
+
CPUUnits int64 `json:"cpuUnits"`
|
|
24
|
+
MaxProcesses int `json:"maxProcesses"`
|
|
25
|
+
NetworkEgress bool `json:"networkEgress"`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type Lease struct {
|
|
29
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
30
|
+
ID string `json:"id"`
|
|
31
|
+
RunID string `json:"runId"`
|
|
32
|
+
OwnerID string `json:"ownerId"`
|
|
33
|
+
ScopeHash string `json:"scopeHash"`
|
|
34
|
+
Mode string `json:"mode"`
|
|
35
|
+
WorkspacePath string `json:"workspacePath,omitempty"`
|
|
36
|
+
Quota Quota `json:"quota"`
|
|
37
|
+
IssuedAt time.Time `json:"issuedAt"`
|
|
38
|
+
ExpiresAt time.Time `json:"expiresAt"`
|
|
39
|
+
ReleasedAt *time.Time `json:"releasedAt,omitempty"`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type Usage struct {
|
|
43
|
+
DiskBytes int64 `json:"diskBytes"`
|
|
44
|
+
MemoryBytes int64 `json:"memoryBytes"`
|
|
45
|
+
CPUTimeNanos int64 `json:"cpuTimeNanos"`
|
|
46
|
+
Processes int `json:"processes"`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type Snapshot struct {
|
|
50
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
51
|
+
LeaseID string `json:"leaseId"`
|
|
52
|
+
RunID string `json:"runId"`
|
|
53
|
+
State string `json:"state"`
|
|
54
|
+
ResourceUsage Usage `json:"resourceUsage"`
|
|
55
|
+
ArtifactRefs []string `json:"artifactRefs,omitempty"`
|
|
56
|
+
ObservedAt time.Time `json:"observedAt"`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Request is sent only by the trusted Core connection. Owner, scope, quota and
|
|
60
|
+
// the original lease timestamps are server-derived, never user-selected.
|
|
61
|
+
// Execute is not retryable: a lost receipt must be treated as an unknown outcome.
|
|
62
|
+
type Request struct {
|
|
63
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
64
|
+
Action string `json:"action"`
|
|
65
|
+
Lease Lease `json:"lease"`
|
|
66
|
+
Argv []string `json:"argv,omitempty"`
|
|
67
|
+
// TaskID selects a server-owned temporary directory inside the sandbox.
|
|
68
|
+
// It never changes the personal owner, lease or persistent mount.
|
|
69
|
+
TaskID string `json:"taskId,omitempty"`
|
|
70
|
+
// Core permits this only before the first successful provisioning receipt.
|
|
71
|
+
CreateData bool `json:"createData,omitempty"`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type Result struct {
|
|
75
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
76
|
+
LeaseID string `json:"leaseId"`
|
|
77
|
+
RunID string `json:"runId"`
|
|
78
|
+
TaskID string `json:"taskId,omitempty"`
|
|
79
|
+
State string `json:"state"`
|
|
80
|
+
Snapshot *Snapshot `json:"snapshot,omitempty"`
|
|
81
|
+
Output *string `json:"output,omitempty"`
|
|
82
|
+
ExitCode *int `json:"exitCode,omitempty"`
|
|
83
|
+
ObservedAt time.Time `json:"observedAt"`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
var scopePattern = regexp.MustCompile(`^sha256:[a-f0-9]{64}$`)
|
|
87
|
+
var canonicalUUIDPattern = regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)
|
|
88
|
+
|
|
89
|
+
func (r *Request) UnmarshalJSON(data []byte) error {
|
|
90
|
+
if !utf8.Valid(data) {
|
|
91
|
+
return errors.New("environment request must be UTF-8")
|
|
92
|
+
}
|
|
93
|
+
type wireRequest Request
|
|
94
|
+
var decoded wireRequest
|
|
95
|
+
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
96
|
+
decoder.DisallowUnknownFields()
|
|
97
|
+
if err := decoder.Decode(&decoded); err != nil {
|
|
98
|
+
return err
|
|
99
|
+
}
|
|
100
|
+
var properties map[string]json.RawMessage
|
|
101
|
+
if err := json.Unmarshal(data, &properties); err != nil {
|
|
102
|
+
return err
|
|
103
|
+
}
|
|
104
|
+
if _, present := properties["argv"]; present && decoded.Action != "execute" {
|
|
105
|
+
return errors.New("only execute accepts command arguments")
|
|
106
|
+
}
|
|
107
|
+
if _, present := properties["taskId"]; present && (decoded.Action != "execute" || !canonicalUUIDPattern.MatchString(decoded.TaskID)) {
|
|
108
|
+
return errors.New("only execute accepts a canonical server task identity")
|
|
109
|
+
}
|
|
110
|
+
*r = Request(decoded)
|
|
111
|
+
return r.Validate()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func (r Request) Validate() error {
|
|
115
|
+
l := r.Lease
|
|
116
|
+
if r.SchemaVersion != RequestVersion || l.SchemaVersion != LeaseVersion || !scopePattern.MatchString(l.ScopeHash) {
|
|
117
|
+
return errors.New("invalid environment protocol identity")
|
|
118
|
+
}
|
|
119
|
+
for _, id := range []string{l.ID, l.RunID, l.OwnerID} {
|
|
120
|
+
if strings.TrimSpace(id) == "" || utf8.RuneCountInString(id) > 128 || !utf8.ValidString(id) || strings.ContainsRune(id, '\x00') {
|
|
121
|
+
return errors.New("invalid environment lease identity")
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (l.Mode != "observe-only" && l.Mode != "restricted-write") || l.WorkspacePath != "" || l.ReleasedAt != nil || l.IssuedAt.IsZero() || !l.ExpiresAt.After(l.IssuedAt) || l.ExpiresAt.Sub(l.IssuedAt) > 24*time.Hour {
|
|
125
|
+
return errors.New("invalid original personal environment lease")
|
|
126
|
+
}
|
|
127
|
+
if err := l.Quota.ValidatePersonalDocker(); err != nil {
|
|
128
|
+
return err
|
|
129
|
+
}
|
|
130
|
+
return r.validateAction()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func (q Quota) ValidatePersonalDocker() error {
|
|
134
|
+
if q.DiskBytes < 16<<20 || q.DiskBytes > 1<<40 || q.MemoryBytes < 32<<20 || q.MemoryBytes > 16<<30 || q.CPUUnits < 1 || q.CPUUnits > 8000 || q.MaxProcesses < 4 || q.MaxProcesses > 256 || q.NetworkEgress {
|
|
135
|
+
return errors.New("unsupported personal environment quota")
|
|
136
|
+
}
|
|
137
|
+
return nil
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func (r Request) validateAction() error {
|
|
141
|
+
if r.TaskID != "" && (r.Action != "execute" || !canonicalUUIDPattern.MatchString(r.TaskID)) {
|
|
142
|
+
return errors.New("invalid personal execution task identity")
|
|
143
|
+
}
|
|
144
|
+
if r.CreateData && r.Action != "acquire" {
|
|
145
|
+
return errors.New("only initial acquisition can create persistent data")
|
|
146
|
+
}
|
|
147
|
+
if r.Action != "execute" {
|
|
148
|
+
if (r.Action != "acquire" && r.Action != "inspect" && r.Action != "release" && r.Action != "delete_data" && r.Action != "export_data") || r.Argv != nil {
|
|
149
|
+
return errors.New("invalid environment action")
|
|
150
|
+
}
|
|
151
|
+
return nil
|
|
152
|
+
}
|
|
153
|
+
if len(r.Argv) == 0 || len(r.Argv) > 64 || !strings.HasPrefix(r.Argv[0], "/") {
|
|
154
|
+
return errors.New("bounded absolute sandbox command required")
|
|
155
|
+
}
|
|
156
|
+
for _, arg := range r.Argv {
|
|
157
|
+
if len(arg) > 4096 || !utf8.ValidString(arg) || strings.ContainsRune(arg, '\x00') {
|
|
158
|
+
return errors.New("invalid sandbox command argument")
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return nil
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ValidateFor binds a receipt to the dispatched operation. Stopped or missing
|
|
165
|
+
// containers cannot claim fresh resource measurements or successful commands.
|
|
166
|
+
func (result Result) ValidateFor(request Request) error {
|
|
167
|
+
if result.ExitCode != nil && (request.Action != "execute" || *result.ExitCode < 0 || *result.ExitCode > 255) {
|
|
168
|
+
return errors.New("invalid command exit receipt")
|
|
169
|
+
}
|
|
170
|
+
if request.Action == "export_data" {
|
|
171
|
+
return errors.New("export requires its complete binary stream receipt")
|
|
172
|
+
}
|
|
173
|
+
if result.SchemaVersion != ResultVersion || result.LeaseID != request.Lease.ID || result.RunID != request.Lease.RunID || result.TaskID != request.TaskID || result.ObservedAt.IsZero() {
|
|
174
|
+
return errors.New("environment receipt identity mismatch")
|
|
175
|
+
}
|
|
176
|
+
active := result.State == "active" || result.State == "quota_exceeded"
|
|
177
|
+
if !active && result.State != "released" && result.State != "expired" && result.State != "missing" && result.State != "deleted" {
|
|
178
|
+
return errors.New("invalid environment receipt state")
|
|
179
|
+
}
|
|
180
|
+
if request.Action == "release" && (result.State != "released" && result.State != "missing") {
|
|
181
|
+
return errors.New("release has no stopped receipt")
|
|
182
|
+
}
|
|
183
|
+
if request.Action == "delete_data" && result.State != "deleted" {
|
|
184
|
+
return errors.New("data deletion has no confirmed receipt")
|
|
185
|
+
}
|
|
186
|
+
if (request.Action == "acquire" || request.Action == "execute") && !active {
|
|
187
|
+
return errors.New("operation has no active receipt")
|
|
188
|
+
}
|
|
189
|
+
if !active {
|
|
190
|
+
if result.Snapshot != nil || result.Output != nil {
|
|
191
|
+
return errors.New("inactive receipt contains invented observation")
|
|
192
|
+
}
|
|
193
|
+
return nil
|
|
194
|
+
}
|
|
195
|
+
if err := result.validateObservation(); err != nil {
|
|
196
|
+
return err
|
|
197
|
+
}
|
|
198
|
+
if (request.Action == "execute") != (result.Output != nil) || (result.Output != nil && len(*result.Output) > 64<<10) {
|
|
199
|
+
return errors.New("invalid command receipt")
|
|
200
|
+
}
|
|
201
|
+
return nil
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func (result Result) validateObservation() error {
|
|
205
|
+
s := result.Snapshot
|
|
206
|
+
if s == nil || s.SchemaVersion != SnapshotVersion || s.LeaseID != result.LeaseID || s.RunID != result.RunID || s.State != result.State || !s.ObservedAt.Equal(result.ObservedAt) {
|
|
207
|
+
return errors.New("environment observation identity mismatch")
|
|
208
|
+
}
|
|
209
|
+
u := s.ResourceUsage
|
|
210
|
+
if u.DiskBytes < 0 || u.MemoryBytes < 0 || u.CPUTimeNanos < 0 || u.Processes < 0 || len(s.ArtifactRefs) > 128 {
|
|
211
|
+
return errors.New("invalid environment observation")
|
|
212
|
+
}
|
|
213
|
+
seen := make(map[string]bool, len(s.ArtifactRefs))
|
|
214
|
+
for _, ref := range s.ArtifactRefs {
|
|
215
|
+
if utf8.RuneCountInString(ref) > 512 || seen[ref] {
|
|
216
|
+
return errors.New("invalid environment artifact reference")
|
|
217
|
+
}
|
|
218
|
+
seen[ref] = true
|
|
219
|
+
}
|
|
220
|
+
return nil
|
|
221
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
package environment
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
"time"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func TestRequestFixturesMatchRuntimeBoundary(t *testing.T) {
|
|
12
|
+
for _, kind := range []string{"valid", "invalid"} {
|
|
13
|
+
files, err := filepath.Glob(filepath.Join("..", "..", "fixtures", "json-schema", "personal-environment-request", kind, "*.json"))
|
|
14
|
+
if err != nil || len(files) == 0 {
|
|
15
|
+
t.Fatalf("missing fixtures: %v", err)
|
|
16
|
+
}
|
|
17
|
+
for _, file := range files {
|
|
18
|
+
t.Run(kind+"/"+filepath.Base(file), func(t *testing.T) {
|
|
19
|
+
data, err := os.ReadFile(file)
|
|
20
|
+
if err != nil {
|
|
21
|
+
t.Fatal(err)
|
|
22
|
+
}
|
|
23
|
+
var request Request
|
|
24
|
+
err = json.Unmarshal(data, &request)
|
|
25
|
+
if (err == nil) != (kind == "valid") {
|
|
26
|
+
t.Fatalf("fixture acceptance=%t; want %s", err == nil, kind)
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func TestRequestRejectsUnboundedOriginalLeaseAndNullCommand(t *testing.T) {
|
|
34
|
+
data, err := os.ReadFile(filepath.Join("..", "..", "fixtures", "json-schema", "personal-environment-request", "valid", "acquire.json"))
|
|
35
|
+
if err != nil {
|
|
36
|
+
t.Fatal(err)
|
|
37
|
+
}
|
|
38
|
+
var request Request
|
|
39
|
+
if err := json.Unmarshal(data, &request); err != nil {
|
|
40
|
+
t.Fatal(err)
|
|
41
|
+
}
|
|
42
|
+
request.Lease.ExpiresAt = request.Lease.IssuedAt.Add(25 * time.Hour)
|
|
43
|
+
if request.Validate() == nil {
|
|
44
|
+
t.Fatal("unbounded lease accepted")
|
|
45
|
+
}
|
|
46
|
+
var properties map[string]any
|
|
47
|
+
if err := json.Unmarshal(data, &properties); err != nil {
|
|
48
|
+
t.Fatal(err)
|
|
49
|
+
}
|
|
50
|
+
properties["argv"] = nil
|
|
51
|
+
nullCommand, err := json.Marshal(properties)
|
|
52
|
+
if err != nil {
|
|
53
|
+
t.Fatal(err)
|
|
54
|
+
}
|
|
55
|
+
if json.Unmarshal(nullCommand, &request) == nil {
|
|
56
|
+
t.Fatal("null command accepted by non-execute action")
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func TestCommandReceiptPinsTheExactTemporaryTask(t *testing.T) {
|
|
61
|
+
now := time.Now().UTC()
|
|
62
|
+
request := Request{SchemaVersion: RequestVersion, Action: "execute", TaskID: "00000000-0000-4000-8000-000000000048", Argv: []string{"/bin/true"}, Lease: Lease{ID: "lease", RunID: "run"}}
|
|
63
|
+
text := "fixture"
|
|
64
|
+
result := Result{SchemaVersion: ResultVersion, LeaseID: "lease", RunID: "run", State: "active", Output: &text, ObservedAt: now, Snapshot: &Snapshot{SchemaVersion: SnapshotVersion, LeaseID: "lease", RunID: "run", State: "active", ObservedAt: now}}
|
|
65
|
+
for _, task := range []string{"", "00000000-0000-4000-8000-000000000049", request.TaskID} {
|
|
66
|
+
result.TaskID = task
|
|
67
|
+
if err := result.ValidateFor(request); (err == nil) != (task == request.TaskID) {
|
|
68
|
+
t.Fatal("command receipt changed or omitted its task identity")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func TestDataDeletionRequiresAConfirmedReceipt(t *testing.T) {
|
|
74
|
+
request := Request{Action: "delete_data", Lease: Lease{ID: "lease", RunID: "run"}}
|
|
75
|
+
for _, state := range []string{"missing", "released", "active", "deleted"} {
|
|
76
|
+
result := Result{SchemaVersion: ResultVersion, LeaseID: "lease", RunID: "run", State: state, ObservedAt: time.Now().UTC()}
|
|
77
|
+
if err := result.ValidateFor(request); (err == nil) != (state == "deleted") {
|
|
78
|
+
t.Fatalf("unconfirmed %s data deletion: %v", state, err)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"type": "object", "additionalProperties": false,
|
|
18
18
|
"required": ["diskBytes", "memoryBytes", "cpuUnits", "maxProcesses", "networkEgress"],
|
|
19
19
|
"properties": {
|
|
20
|
-
"diskBytes": { "type": "integer", "minimum": 1 },
|
|
20
|
+
"diskBytes": { "type": "integer", "minimum": 1, "description": "Maximum charged persistent storage bytes. The Docker personal backend uses a verified Btrfs subvolume quota; temporary task files use separately bounded memory-backed mounts and count against memoryBytes. Unsupported hard enforcement must fail acquisition." },
|
|
21
21
|
"memoryBytes": { "type": "integer", "minimum": 1 },
|
|
22
|
-
"cpuUnits": { "type": "integer", "minimum": 1 },
|
|
22
|
+
"cpuUnits": { "type": "integer", "minimum": 1, "description": "CPU millicores: 1000 is one CPU. Docker personal leases must hard-enforce this ceiling." },
|
|
23
23
|
"maxProcesses": { "type": "integer", "minimum": 1 },
|
|
24
24
|
"networkEgress": { "type": "boolean" }
|
|
25
25
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"version": { "type": "string", "minLength": 1 },
|
|
12
12
|
"backend": { "enum": ["local-process", "container", "kubernetes"] },
|
|
13
13
|
"isolationMode": { "enum": ["observe-only", "restricted-write", "disposable-write"] },
|
|
14
|
-
"quota": { "type": "object", "required": ["cpuUnits", "memoryBytes", "diskBytes", "maxProcesses"], "properties": { "cpuUnits": { "type": "integer", "minimum": 1 }, "memoryBytes": { "type": "integer", "minimum": 1 }, "diskBytes": { "type": "integer", "minimum": 1 }, "maxProcesses": { "type": "integer", "minimum": 1 } }, "additionalProperties": false },
|
|
14
|
+
"quota": { "type": "object", "required": ["cpuUnits", "memoryBytes", "diskBytes", "maxProcesses"], "properties": { "cpuUnits": { "type": "integer", "minimum": 1, "description": "CPU millicores; 1000 is one CPU." }, "memoryBytes": { "type": "integer", "minimum": 1 }, "diskBytes": { "type": "integer", "minimum": 1, "description": "Maximum charged persistent storage bytes; memory-backed temporary mounts count against memoryBytes." }, "maxProcesses": { "type": "integer", "minimum": 1 } }, "additionalProperties": false },
|
|
15
15
|
"networkEgress": { "type": "boolean" },
|
|
16
16
|
"ttlSeconds": { "type": "integer", "minimum": 1, "maximum": 86400 },
|
|
17
17
|
"enabled": { "type": "boolean" }
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
package guest
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/base64"
|
|
5
|
+
"errors"
|
|
6
|
+
"net/url"
|
|
7
|
+
"strings"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// Only the private browser controller sends these frames over current mTLS.
|
|
11
|
+
type BrowserNetworkRequest struct {
|
|
12
|
+
URL string `json:"url"`
|
|
13
|
+
Method string `json:"method"`
|
|
14
|
+
Headers map[string]string `json:"headers,omitempty"`
|
|
15
|
+
BodyBase64 string `json:"bodyBase64,omitempty"`
|
|
16
|
+
}
|
|
17
|
+
type BrowserNetworkHeader struct {
|
|
18
|
+
Name string `json:"name"`
|
|
19
|
+
Value string `json:"value"`
|
|
20
|
+
}
|
|
21
|
+
type BrowserNetworkResponse struct {
|
|
22
|
+
Status int `json:"status"`
|
|
23
|
+
Headers []BrowserNetworkHeader `json:"headers"`
|
|
24
|
+
BodyBase64 string `json:"bodyBase64"`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func (v BrowserNetworkResponse) Validate() error {
|
|
28
|
+
if v.Status < 100 || v.Status > 599 || len(v.Headers) > 128 {
|
|
29
|
+
return errors.New("invalid browser network receipt")
|
|
30
|
+
}
|
|
31
|
+
data, err := base64.StdEncoding.DecodeString(v.BodyBase64)
|
|
32
|
+
if err != nil || len(data) > 1<<20 {
|
|
33
|
+
return errors.New("browser response exceeds its bound")
|
|
34
|
+
}
|
|
35
|
+
for _, h := range v.Headers {
|
|
36
|
+
if h.Name == "" || len(h.Name) > 128 || len(h.Value) > 8192 || strings.ContainsAny(h.Name+h.Value, "\x00\r\n") {
|
|
37
|
+
return errors.New("invalid browser response header")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return nil
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func (v BrowserNetworkRequest) Validate() error {
|
|
44
|
+
u, err := url.Parse(v.URL)
|
|
45
|
+
if err != nil || !BrowserURL(v.URL) || u.Scheme != "https" || u.Fragment != "" || u.Opaque != "" || len(v.Headers) > 64 {
|
|
46
|
+
return errors.New("invalid browser network request")
|
|
47
|
+
}
|
|
48
|
+
switch v.Method {
|
|
49
|
+
case "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS":
|
|
50
|
+
default:
|
|
51
|
+
return errors.New("unsupported browser network method")
|
|
52
|
+
}
|
|
53
|
+
body, err := base64.StdEncoding.DecodeString(v.BodyBase64)
|
|
54
|
+
if err != nil || len(body) > 64<<10 || (v.Method == "GET" || v.Method == "HEAD") && len(body) > 0 {
|
|
55
|
+
return errors.New("browser network body exceeds its bound")
|
|
56
|
+
}
|
|
57
|
+
size := 0
|
|
58
|
+
for name, value := range v.Headers {
|
|
59
|
+
size += len(name) + len(value)
|
|
60
|
+
if name == "" || strings.ContainsAny(name+value, "\x00\r\n") || len(name) > 128 || len(value) > 8192 {
|
|
61
|
+
return errors.New("invalid browser network header")
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if size > 16<<10 {
|
|
65
|
+
return errors.New("browser network headers exceed their bound")
|
|
66
|
+
}
|
|
67
|
+
return nil
|
|
68
|
+
}
|
|
69
|
+
func (v *BrowserNetworkRequest) UnmarshalJSON(raw []byte) error {
|
|
70
|
+
type wire BrowserNetworkRequest
|
|
71
|
+
var decoded wire
|
|
72
|
+
if err := decodeStrictBounded(raw, &decoded, 128<<10, "url", "method", "headers", "bodyBase64"); err != nil {
|
|
73
|
+
return err
|
|
74
|
+
}
|
|
75
|
+
*v = BrowserNetworkRequest(decoded)
|
|
76
|
+
return v.Validate()
|
|
77
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
package guest
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"net/url"
|
|
6
|
+
"regexp"
|
|
7
|
+
"strings"
|
|
8
|
+
"time"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
const DataPlanVersion = "opensoha.dev/personal-guest-data-plan/v1"
|
|
12
|
+
|
|
13
|
+
// Core reserves operationId before fixed privileged dispatch. Liveness alone
|
|
14
|
+
// cannot authorize this plan; GET /bindings/{id}/data-operations/{operationId}
|
|
15
|
+
// returns it only for that current, running owner-authorized operation.
|
|
16
|
+
type DataPlan struct {
|
|
17
|
+
SchemaVersion string `json:"schemaVersion"`
|
|
18
|
+
OperationID string `json:"operationId"`
|
|
19
|
+
Binding Binding `json:"binding"`
|
|
20
|
+
DiskQuotaBytes int64 `json:"diskQuotaBytes"`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func (p DataPlan) Validate(now time.Time) error {
|
|
24
|
+
if p.SchemaVersion != DataPlanVersion || !uuidPattern.MatchString(p.OperationID) || p.Binding.Validate() != nil || p.Binding.Provider != "pve" || now.Before(p.Binding.IssuedAt) || !now.Before(p.Binding.ExpiresAt) || p.DiskQuotaBytes < 16<<20 || p.DiskQuotaBytes%1024 != 0 || p.DiskQuotaBytes >= p.Binding.DataDiskBytes {
|
|
25
|
+
return errors.New("invalid or expired personal data operation")
|
|
26
|
+
}
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type DataResult struct {
|
|
31
|
+
DataRef string `json:"dataRef"`
|
|
32
|
+
FilesystemID string `json:"filesystemId"`
|
|
33
|
+
DiskQuota int64 `json:"diskQuotaBytes"`
|
|
34
|
+
DiskUsed int64 `json:"diskUsedBytes"`
|
|
35
|
+
Reconciled bool `json:"reconciled"`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Provisioning contains public identity material and a bootstrap-only challenge.
|
|
39
|
+
// It is encrypted in the durable task and supplied only to the fixed QGA installer.
|
|
40
|
+
// The private key is generated in the VM and never transported through Core.
|
|
41
|
+
type Provisioning struct {
|
|
42
|
+
Plan DataPlan `json:"plan"`
|
|
43
|
+
Endpoint string `json:"endpoint"`
|
|
44
|
+
EnrollmentID string `json:"enrollmentId"`
|
|
45
|
+
Challenge string `json:"challenge"`
|
|
46
|
+
CertificatePEM string `json:"certificatePEM"`
|
|
47
|
+
ClientCAPEM string `json:"clientCAPEM"`
|
|
48
|
+
ArtifactSHA256 string `json:"artifactSHA256"`
|
|
49
|
+
CPUQuotaMilli int64 `json:"cpuQuotaMilli"`
|
|
50
|
+
MemoryBytes int64 `json:"memoryBytes"`
|
|
51
|
+
PidsLimit int64 `json:"pidsLimit"`
|
|
52
|
+
Network bool `json:"network"`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func (p Provisioning) Validate(now time.Time) error {
|
|
56
|
+
u, err := url.Parse(p.Endpoint)
|
|
57
|
+
_, tokenErr := EnrollmentTokenHash("Bearer " + p.Challenge)
|
|
58
|
+
if p.Plan.Validate(now) != nil || err != nil || u.Scheme != "https" || u.Host == "" || u.Path != "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || len(p.Endpoint) > 512 || strings.TrimSpace(p.Endpoint) != p.Endpoint || tokenErr != nil || (Enrollment{SchemaVersion: EnrollmentVersion, EnrollmentID: p.EnrollmentID}).Validate() != nil || !regexp.MustCompile(`^[a-f0-9]{64}$`).MatchString(p.ArtifactSHA256) || len(p.CertificatePEM) < 1 || len(p.CertificatePEM) > 4096 || len(p.ClientCAPEM) < 1 || len(p.ClientCAPEM) > 8192 || p.CPUQuotaMilli < 1 || p.CPUQuotaMilli > 8000 || p.MemoryBytes < 32<<20 || p.MemoryBytes > 16<<30 || p.PidsLimit < 4 || p.PidsLimit > 256 || p.Network {
|
|
59
|
+
return errors.New("invalid personal guest provisioning bundle")
|
|
60
|
+
}
|
|
61
|
+
return nil
|
|
62
|
+
}
|