@opensoha/contracts 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/gateway_permissions_test.go +48 -0
- package/auth/permission-catalog.json +331 -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/KubernetesAgentActionCheck/invalid/unproven-state.json +6 -0
- package/fixtures/openapi/KubernetesAgentActionCheck/valid/configured-denied.json +9 -0
- package/fixtures/openapi/KubernetesAgentActionProfile/invalid/wildcard.json +1 -0
- package/fixtures/openapi/KubernetesAgentActionProfile/valid/workbench.json +1 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/action-profile.json +3 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/action-profile.json +5 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/condition-status-boolean.json +17 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/taint-effect-missing.json +16 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/legacy-fields-absent.json +11 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/no-taints.json +13 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/ready-cordoned-tainted.json +28 -0
- package/fixtures/openapi/KubernetesNodeListEnvelope/valid/unknown-fields-null.json +13 -0
- package/fixtures/openapi/KubernetesResourceUpdatePlanRequest/invalid/missing-required.json +1 -0
- package/fixtures/openapi/KubernetesResourceYaml/invalid/missing-required.json +1 -0
- package/fixtures/openapi/KubernetesTopologyCoverage/invalid/empty-as-success.json +5 -0
- package/fixtures/openapi/KubernetesTopologyCoverage/valid/not-installed.json +6 -0
- package/fixtures/openapi/KubernetesTopologyGraphEdge/invalid/negative-weight.json +8 -0
- package/fixtures/openapi/KubernetesTopologyGraphEdge/valid/zero-weight.json +20 -0
- package/fixtures/openapi/KubernetesTopologyGraphNode/invalid/fake-flow-kind.json +42 -0
- package/fixtures/openapi/KubernetesTopologyGraphNode/valid/rule.json +42 -0
- package/fixtures/openapi/KubernetesTopologySnapshot/invalid/raw-source-field.json +33 -0
- package/fixtures/openapi/KubernetesTopologySnapshot/valid/projected-source.json +32 -0
- package/fixtures/openapi/KubernetesTopologySnapshotRequest/invalid/arbitrary-gvr.json +8 -0
- package/fixtures/openapi/KubernetesTopologySnapshotRequest/valid/bounded-sources.json +11 -0
- package/fixtures/openapi/KubernetesTopologySourceQuery/invalid/cluster-namespace.json +4 -0
- package/fixtures/openapi/KubernetesTopologySourceQuery/valid/all-namespaces.json +4 -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/ResourceMonitoringPanel/invalid/unknown-quality.json +7 -0
- package/fixtures/openapi/ResourceMonitoringPanel/valid/not-collected.json +7 -0
- package/fixtures/openapi/ResourceMonitoringPoint/invalid/string-zero.json +4 -0
- package/fixtures/openapi/ResourceMonitoringPoint/valid/missing.json +4 -0
- package/fixtures/openapi/ResourceMonitoringPoint/valid/zero.json +4 -0
- package/fixtures/openapi/ResourceMonitoringView/invalid/raw-query.json +12 -0
- package/fixtures/openapi/ResourceMonitoringView/valid/unconfigured.json +12 -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/kubernetes_monitoring_test.go +67 -0
- package/gen/go/sohaapi/personal_environment_test.go +25 -0
- package/gen/go/sohaapi/types.go +3460 -433
- package/gen/go/sohaapi/vm_disk_test.go +21 -0
- package/gen/ts/sohaapi/index.ts +5454 -1397
- 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 +24199 -16810
- package/openapi/soha-api.yaml +2537 -14
- package/openapi/spec_generated.go +3 -3
- package/openapi/spec_test.go +34 -0
- package/package.json +19 -3
- package/resource/agent_access.go +12 -0
- package/resource/agent_profiles.go +62 -0
- package/resource/agent_profiles_test.go +36 -0
- package/resource/kubernetes_kinds.go +73 -0
- package/resource/kubernetes_kinds_test.go +38 -0
- package/resource/models.go +23 -14
- package/resource/network_topology.go +166 -0
- package/resource/network_topology_projection.go +208 -0
- package/resource/network_topology_projection_test.go +53 -0
- package/resource/network_topology_test.go +51 -0
- package/resource/network_topology_validation.go +29 -0
- package/resource/node_drain.go +34 -0
- package/resource/resource_monitoring.go +99 -0
- package/resource/runtime/network_topology.go +232 -0
- package/resource/runtime/network_topology_test.go +62 -0
- package/resource/runtime/node_drain.go +124 -0
- package/resource/runtime/node_drain_test.go +95 -0
- package/resource/runtime/resource_edit.go +128 -0
- package/resource/runtime/resource_edit_test.go +183 -0
- package/resource/runtime/resource_monitoring.go +243 -0
- package/resource/runtime/resource_monitoring_test.go +88 -0
- /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,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
|
+
}
|