@cuna_labs/cli 0.1.0
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/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +169 -0
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/dist/api/capability-evidence.d.ts +19 -0
- package/dist/api/capability-evidence.d.ts.map +1 -0
- package/dist/api/capability-evidence.js +38 -0
- package/dist/api/capability-evidence.js.map +1 -0
- package/dist/api/client.d.ts +167 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +937 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/contracts.d.ts +345 -0
- package/dist/api/contracts.d.ts.map +1 -0
- package/dist/api/contracts.js +976 -0
- package/dist/api/contracts.js.map +1 -0
- package/dist/api/execution-workspaces.d.ts +17 -0
- package/dist/api/execution-workspaces.d.ts.map +1 -0
- package/dist/api/execution-workspaces.js +46 -0
- package/dist/api/execution-workspaces.js.map +1 -0
- package/dist/api/http.d.ts +76 -0
- package/dist/api/http.d.ts.map +1 -0
- package/dist/api/http.js +838 -0
- package/dist/api/http.js.map +1 -0
- package/dist/api/managed-executions.d.ts +37 -0
- package/dist/api/managed-executions.d.ts.map +1 -0
- package/dist/api/managed-executions.js +80 -0
- package/dist/api/managed-executions.js.map +1 -0
- package/dist/api/observer-v2-schema.d.ts +206 -0
- package/dist/api/observer-v2-schema.d.ts.map +1 -0
- package/dist/api/observer-v2-schema.js +264 -0
- package/dist/api/observer-v2-schema.js.map +1 -0
- package/dist/api/observer-v2.d.ts +33 -0
- package/dist/api/observer-v2.d.ts.map +1 -0
- package/dist/api/observer-v2.js +60 -0
- package/dist/api/observer-v2.js.map +1 -0
- package/dist/api/owner-observe-grants-v2-schema.d.ts +678 -0
- package/dist/api/owner-observe-grants-v2-schema.d.ts.map +1 -0
- package/dist/api/owner-observe-grants-v2-schema.js +887 -0
- package/dist/api/owner-observe-grants-v2-schema.js.map +1 -0
- package/dist/api/owner-observe-grants-v2.d.ts +268 -0
- package/dist/api/owner-observe-grants-v2.d.ts.map +1 -0
- package/dist/api/owner-observe-grants-v2.js +604 -0
- package/dist/api/owner-observe-grants-v2.js.map +1 -0
- package/dist/api/provider-v2-schema.d.ts +293 -0
- package/dist/api/provider-v2-schema.d.ts.map +1 -0
- package/dist/api/provider-v2-schema.js +381 -0
- package/dist/api/provider-v2-schema.js.map +1 -0
- package/dist/api/provider-v2.d.ts +55 -0
- package/dist/api/provider-v2.d.ts.map +1 -0
- package/dist/api/provider-v2.js +54 -0
- package/dist/api/provider-v2.js.map +1 -0
- package/dist/api/remote-workspace.d.ts +29 -0
- package/dist/api/remote-workspace.d.ts.map +1 -0
- package/dist/api/remote-workspace.js +62 -0
- package/dist/api/remote-workspace.js.map +1 -0
- package/dist/api/supervisor-live-update.d.ts +248 -0
- package/dist/api/supervisor-live-update.d.ts.map +1 -0
- package/dist/api/supervisor-live-update.js +434 -0
- package/dist/api/supervisor-live-update.js.map +1 -0
- package/dist/api/terminal-reason.d.ts +5 -0
- package/dist/api/terminal-reason.d.ts.map +1 -0
- package/dist/api/terminal-reason.js +12 -0
- package/dist/api/terminal-reason.js.map +1 -0
- package/dist/auth/browser-handoff.d.ts +59 -0
- package/dist/auth/browser-handoff.d.ts.map +1 -0
- package/dist/auth/browser-handoff.js +33 -0
- package/dist/auth/browser-handoff.js.map +1 -0
- package/dist/auth/browser.d.ts +11 -0
- package/dist/auth/browser.d.ts.map +1 -0
- package/dist/auth/browser.js +47 -0
- package/dist/auth/browser.js.map +1 -0
- package/dist/auth/human-client.d.ts +39 -0
- package/dist/auth/human-client.d.ts.map +1 -0
- package/dist/auth/human-client.js +66 -0
- package/dist/auth/human-client.js.map +1 -0
- package/dist/auth/human-contracts.d.ts +52 -0
- package/dist/auth/human-contracts.d.ts.map +1 -0
- package/dist/auth/human-contracts.js +229 -0
- package/dist/auth/human-contracts.js.map +1 -0
- package/dist/auth/human-session.d.ts +48 -0
- package/dist/auth/human-session.d.ts.map +1 -0
- package/dist/auth/human-session.js +902 -0
- package/dist/auth/human-session.js.map +1 -0
- package/dist/auth/onboarding-state.d.ts +31 -0
- package/dist/auth/onboarding-state.d.ts.map +1 -0
- package/dist/auth/onboarding-state.js +52 -0
- package/dist/auth/onboarding-state.js.map +1 -0
- package/dist/auth/pkce.d.ts +15 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +19 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/bin/cuna.d.ts +3 -0
- package/dist/bin/cuna.d.ts.map +1 -0
- package/dist/bin/cuna.js +4 -0
- package/dist/bin/cuna.js.map +1 -0
- package/dist/build-identity.d.ts +20 -0
- package/dist/build-identity.d.ts.map +1 -0
- package/dist/build-identity.js +77 -0
- package/dist/build-identity.js.map +1 -0
- package/dist/cli/command-help.d.ts +9 -0
- package/dist/cli/command-help.d.ts.map +1 -0
- package/dist/cli/command-help.js +324 -0
- package/dist/cli/command-help.js.map +1 -0
- package/dist/cli/help.d.ts +40 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +256 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/output.d.ts +28 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +95 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/parser.d.ts +65 -0
- package/dist/cli/parser.d.ts.map +1 -0
- package/dist/cli/parser.js +294 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/cli/process-entrypoint.d.ts +18 -0
- package/dist/cli/process-entrypoint.d.ts.map +1 -0
- package/dist/cli/process-entrypoint.js +26 -0
- package/dist/cli/process-entrypoint.js.map +1 -0
- package/dist/cli/run.d.ts +114 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +1969 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/commands/commands.d.ts +74 -0
- package/dist/commands/commands.d.ts.map +1 -0
- package/dist/commands/commands.js +2814 -0
- package/dist/commands/commands.js.map +1 -0
- package/dist/config/config.d.ts +86 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +323 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/infra-contract-witness.d.ts +29 -0
- package/dist/config/infra-contract-witness.d.ts.map +1 -0
- package/dist/config/infra-contract-witness.js +30 -0
- package/dist/config/infra-contract-witness.js.map +1 -0
- package/dist/core/browser-url.d.ts +2 -0
- package/dist/core/browser-url.d.ts.map +1 -0
- package/dist/core/browser-url.js +20 -0
- package/dist/core/browser-url.js.map +1 -0
- package/dist/core/deployed-wire-compatibility.d.ts +15 -0
- package/dist/core/deployed-wire-compatibility.d.ts.map +1 -0
- package/dist/core/deployed-wire-compatibility.js +15 -0
- package/dist/core/deployed-wire-compatibility.js.map +1 -0
- package/dist/core/errors.d.ts +59 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +82 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/exit-codes.d.ts +68 -0
- package/dist/core/exit-codes.d.ts.map +1 -0
- package/dist/core/exit-codes.js +84 -0
- package/dist/core/exit-codes.js.map +1 -0
- package/dist/core/instant.d.ts +60 -0
- package/dist/core/instant.d.ts.map +1 -0
- package/dist/core/instant.js +90 -0
- package/dist/core/instant.js.map +1 -0
- package/dist/core/namespace.d.ts +171 -0
- package/dist/core/namespace.d.ts.map +1 -0
- package/dist/core/namespace.js +227 -0
- package/dist/core/namespace.js.map +1 -0
- package/dist/core/observation-budget.d.ts +185 -0
- package/dist/core/observation-budget.d.ts.map +1 -0
- package/dist/core/observation-budget.js +209 -0
- package/dist/core/observation-budget.js.map +1 -0
- package/dist/core/product-web.d.ts +81 -0
- package/dist/core/product-web.d.ts.map +1 -0
- package/dist/core/product-web.js +87 -0
- package/dist/core/product-web.js.map +1 -0
- package/dist/core/validation.d.ts +90 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +199 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/credentials/contracts.d.ts +82 -0
- package/dist/credentials/contracts.d.ts.map +1 -0
- package/dist/credentials/contracts.js +2 -0
- package/dist/credentials/contracts.js.map +1 -0
- package/dist/credentials/errors.d.ts +19 -0
- package/dist/credentials/errors.d.ts.map +1 -0
- package/dist/credentials/errors.js +16 -0
- package/dist/credentials/errors.js.map +1 -0
- package/dist/credentials/index.d.ts +6 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +6 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/credentials/local-session.d.ts +101 -0
- package/dist/credentials/local-session.d.ts.map +1 -0
- package/dist/credentials/local-session.js +839 -0
- package/dist/credentials/local-session.js.map +1 -0
- package/dist/credentials/secret-material.d.ts +17 -0
- package/dist/credentials/secret-material.d.ts.map +1 -0
- package/dist/credentials/secret-material.js +49 -0
- package/dist/credentials/secret-material.js.map +1 -0
- package/dist/credentials/vault.d.ts +41 -0
- package/dist/credentials/vault.d.ts.map +1 -0
- package/dist/credentials/vault.js +719 -0
- package/dist/credentials/vault.js.map +1 -0
- package/dist/daemon/endpoint.d.ts +29 -0
- package/dist/daemon/endpoint.d.ts.map +1 -0
- package/dist/daemon/endpoint.js +66 -0
- package/dist/daemon/endpoint.js.map +1 -0
- package/dist/daemon/journal.d.ts +62 -0
- package/dist/daemon/journal.d.ts.map +1 -0
- package/dist/daemon/journal.js +299 -0
- package/dist/daemon/journal.js.map +1 -0
- package/dist/daemon/lease.d.ts +25 -0
- package/dist/daemon/lease.d.ts.map +1 -0
- package/dist/daemon/lease.js +94 -0
- package/dist/daemon/lease.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +14 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +29 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/protocol.d.ts +65 -0
- package/dist/daemon/protocol.d.ts.map +1 -0
- package/dist/daemon/protocol.js +194 -0
- package/dist/daemon/protocol.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +29 -0
- package/dist/daemon/supervisor.d.ts.map +1 -0
- package/dist/daemon/supervisor.js +87 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/daemon/views.d.ts +31 -0
- package/dist/daemon/views.d.ts.map +1 -0
- package/dist/daemon/views.js +86 -0
- package/dist/daemon/views.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/journey/api-effects.d.ts +59 -0
- package/dist/journey/api-effects.d.ts.map +1 -0
- package/dist/journey/api-effects.js +386 -0
- package/dist/journey/api-effects.js.map +1 -0
- package/dist/journey/derived-identity.d.ts +91 -0
- package/dist/journey/derived-identity.d.ts.map +1 -0
- package/dist/journey/derived-identity.js +123 -0
- package/dist/journey/derived-identity.js.map +1 -0
- package/dist/journey/index.d.ts +6 -0
- package/dist/journey/index.d.ts.map +1 -0
- package/dist/journey/index.js +6 -0
- package/dist/journey/index.js.map +1 -0
- package/dist/journey/intent.d.ts +36 -0
- package/dist/journey/intent.d.ts.map +1 -0
- package/dist/journey/intent.js +191 -0
- package/dist/journey/intent.js.map +1 -0
- package/dist/journey/orchestrator.d.ts +140 -0
- package/dist/journey/orchestrator.d.ts.map +1 -0
- package/dist/journey/orchestrator.js +415 -0
- package/dist/journey/orchestrator.js.map +1 -0
- package/dist/journey/provider-launch-intent.d.ts +12 -0
- package/dist/journey/provider-launch-intent.d.ts.map +1 -0
- package/dist/journey/provider-launch-intent.js +40 -0
- package/dist/journey/provider-launch-intent.js.map +1 -0
- package/dist/journey/remote-workspace.d.ts +34 -0
- package/dist/journey/remote-workspace.d.ts.map +1 -0
- package/dist/journey/remote-workspace.js +91 -0
- package/dist/journey/remote-workspace.js.map +1 -0
- package/dist/journey/root-entry.d.ts +56 -0
- package/dist/journey/root-entry.d.ts.map +1 -0
- package/dist/journey/root-entry.js +19 -0
- package/dist/journey/root-entry.js.map +1 -0
- package/dist/journey/selection.d.ts +165 -0
- package/dist/journey/selection.d.ts.map +1 -0
- package/dist/journey/selection.js +565 -0
- package/dist/journey/selection.js.map +1 -0
- package/dist/journey/session-failure.d.ts +4 -0
- package/dist/journey/session-failure.d.ts.map +1 -0
- package/dist/journey/session-failure.js +28 -0
- package/dist/journey/session-failure.js.map +1 -0
- package/dist/journey/workspace-effects.d.ts +30 -0
- package/dist/journey/workspace-effects.d.ts.map +1 -0
- package/dist/journey/workspace-effects.js +348 -0
- package/dist/journey/workspace-effects.js.map +1 -0
- package/dist/local-actions/broker.d.ts +32 -0
- package/dist/local-actions/broker.d.ts.map +1 -0
- package/dist/local-actions/broker.js +400 -0
- package/dist/local-actions/broker.js.map +1 -0
- package/dist/local-actions/browser-action.d.ts +78 -0
- package/dist/local-actions/browser-action.d.ts.map +1 -0
- package/dist/local-actions/browser-action.js +307 -0
- package/dist/local-actions/browser-action.js.map +1 -0
- package/dist/local-actions/callback-relay.d.ts +45 -0
- package/dist/local-actions/callback-relay.d.ts.map +1 -0
- package/dist/local-actions/callback-relay.js +265 -0
- package/dist/local-actions/callback-relay.js.map +1 -0
- package/dist/local-actions/clipboard.d.ts +3 -0
- package/dist/local-actions/clipboard.d.ts.map +1 -0
- package/dist/local-actions/clipboard.js +18 -0
- package/dist/local-actions/clipboard.js.map +1 -0
- package/dist/local-actions/contracts.d.ts +63 -0
- package/dist/local-actions/contracts.d.ts.map +1 -0
- package/dist/local-actions/contracts.js +18 -0
- package/dist/local-actions/contracts.js.map +1 -0
- package/dist/local-actions/desktop-presentation.d.ts +141 -0
- package/dist/local-actions/desktop-presentation.d.ts.map +1 -0
- package/dist/local-actions/desktop-presentation.js +253 -0
- package/dist/local-actions/desktop-presentation.js.map +1 -0
- package/dist/local-actions/device-selection.d.ts +43 -0
- package/dist/local-actions/device-selection.d.ts.map +1 -0
- package/dist/local-actions/device-selection.js +154 -0
- package/dist/local-actions/device-selection.js.map +1 -0
- package/dist/local-actions/executor.d.ts +49 -0
- package/dist/local-actions/executor.d.ts.map +1 -0
- package/dist/local-actions/executor.js +329 -0
- package/dist/local-actions/executor.js.map +1 -0
- package/dist/local-actions/file-transfers.d.ts +141 -0
- package/dist/local-actions/file-transfers.d.ts.map +1 -0
- package/dist/local-actions/file-transfers.js +446 -0
- package/dist/local-actions/file-transfers.js.map +1 -0
- package/dist/local-actions/foreground-notifications.d.ts +45 -0
- package/dist/local-actions/foreground-notifications.d.ts.map +1 -0
- package/dist/local-actions/foreground-notifications.js +163 -0
- package/dist/local-actions/foreground-notifications.js.map +1 -0
- package/dist/local-actions/git-signing.d.ts +39 -0
- package/dist/local-actions/git-signing.d.ts.map +1 -0
- package/dist/local-actions/git-signing.js +135 -0
- package/dist/local-actions/git-signing.js.map +1 -0
- package/dist/local-actions/index.d.ts +19 -0
- package/dist/local-actions/index.d.ts.map +1 -0
- package/dist/local-actions/index.js +19 -0
- package/dist/local-actions/index.js.map +1 -0
- package/dist/local-actions/local-service-actions.d.ts +87 -0
- package/dist/local-actions/local-service-actions.d.ts.map +1 -0
- package/dist/local-actions/local-service-actions.js +347 -0
- package/dist/local-actions/local-service-actions.js.map +1 -0
- package/dist/local-actions/loopback.d.ts +5 -0
- package/dist/local-actions/loopback.d.ts.map +1 -0
- package/dist/local-actions/loopback.js +15 -0
- package/dist/local-actions/loopback.js.map +1 -0
- package/dist/local-actions/policy-store.d.ts +7 -0
- package/dist/local-actions/policy-store.d.ts.map +1 -0
- package/dist/local-actions/policy-store.js +70 -0
- package/dist/local-actions/policy-store.js.map +1 -0
- package/dist/local-actions/policy.d.ts +21 -0
- package/dist/local-actions/policy.d.ts.map +1 -0
- package/dist/local-actions/policy.js +43 -0
- package/dist/local-actions/policy.js.map +1 -0
- package/dist/local-actions/private-port-forward.d.ts +36 -0
- package/dist/local-actions/private-port-forward.d.ts.map +1 -0
- package/dist/local-actions/private-port-forward.js +154 -0
- package/dist/local-actions/private-port-forward.js.map +1 -0
- package/dist/local-actions/provider-mcp-config.d.ts +101 -0
- package/dist/local-actions/provider-mcp-config.d.ts.map +1 -0
- package/dist/local-actions/provider-mcp-config.js +166 -0
- package/dist/local-actions/provider-mcp-config.js.map +1 -0
- package/dist/local-actions/providers.d.ts +12 -0
- package/dist/local-actions/providers.d.ts.map +1 -0
- package/dist/local-actions/providers.js +42 -0
- package/dist/local-actions/providers.js.map +1 -0
- package/dist/local-actions/schemas.d.ts +4 -0
- package/dist/local-actions/schemas.d.ts.map +1 -0
- package/dist/local-actions/schemas.js +314 -0
- package/dist/local-actions/schemas.js.map +1 -0
- package/dist/local-actions/sensitive-consent.d.ts +32 -0
- package/dist/local-actions/sensitive-consent.d.ts.map +1 -0
- package/dist/local-actions/sensitive-consent.js +47 -0
- package/dist/local-actions/sensitive-consent.js.map +1 -0
- package/dist/machines/command-launch.d.ts +17 -0
- package/dist/machines/command-launch.d.ts.map +1 -0
- package/dist/machines/command-launch.js +52 -0
- package/dist/machines/command-launch.js.map +1 -0
- package/dist/machines/execution-receipt.d.ts +18 -0
- package/dist/machines/execution-receipt.d.ts.map +1 -0
- package/dist/machines/execution-receipt.js +87 -0
- package/dist/machines/execution-receipt.js.map +1 -0
- package/dist/machines/executions-screen.d.ts +6 -0
- package/dist/machines/executions-screen.d.ts.map +1 -0
- package/dist/machines/executions-screen.js +410 -0
- package/dist/machines/executions-screen.js.map +1 -0
- package/dist/machines/explorer.d.ts +75 -0
- package/dist/machines/explorer.d.ts.map +1 -0
- package/dist/machines/explorer.js +1726 -0
- package/dist/machines/explorer.js.map +1 -0
- package/dist/machines/live-supervisor-update.d.ts +325 -0
- package/dist/machines/live-supervisor-update.d.ts.map +1 -0
- package/dist/machines/live-supervisor-update.js +513 -0
- package/dist/machines/live-supervisor-update.js.map +1 -0
- package/dist/machines/machine-first.d.ts +123 -0
- package/dist/machines/machine-first.d.ts.map +1 -0
- package/dist/machines/machine-first.js +119 -0
- package/dist/machines/machine-first.js.map +1 -0
- package/dist/machines/opencode-supervisor.d.ts +51 -0
- package/dist/machines/opencode-supervisor.d.ts.map +1 -0
- package/dist/machines/opencode-supervisor.js +124 -0
- package/dist/machines/opencode-supervisor.js.map +1 -0
- package/dist/machines/pagination.d.ts +4 -0
- package/dist/machines/pagination.d.ts.map +1 -0
- package/dist/machines/pagination.js +7 -0
- package/dist/machines/pagination.js.map +1 -0
- package/dist/machines/provider-availability.d.ts +42 -0
- package/dist/machines/provider-availability.d.ts.map +1 -0
- package/dist/machines/provider-availability.js +82 -0
- package/dist/machines/provider-availability.js.map +1 -0
- package/dist/machines/provider-screen.d.ts +12 -0
- package/dist/machines/provider-screen.d.ts.map +1 -0
- package/dist/machines/provider-screen.js +129 -0
- package/dist/machines/provider-screen.js.map +1 -0
- package/dist/machines/session-actionability.d.ts +86 -0
- package/dist/machines/session-actionability.d.ts.map +1 -0
- package/dist/machines/session-actionability.js +125 -0
- package/dist/machines/session-actionability.js.map +1 -0
- package/dist/machines/session-visibility.d.ts +139 -0
- package/dist/machines/session-visibility.d.ts.map +1 -0
- package/dist/machines/session-visibility.js +109 -0
- package/dist/machines/session-visibility.js.map +1 -0
- package/dist/platform/adapter.d.ts +62 -0
- package/dist/platform/adapter.d.ts.map +1 -0
- package/dist/platform/adapter.js +240 -0
- package/dist/platform/adapter.js.map +1 -0
- package/dist/platform/support.d.ts +12 -0
- package/dist/platform/support.d.ts.map +1 -0
- package/dist/platform/support.js +25 -0
- package/dist/platform/support.js.map +1 -0
- package/dist/pty/contracts.d.ts +45 -0
- package/dist/pty/contracts.d.ts.map +1 -0
- package/dist/pty/contracts.js +2 -0
- package/dist/pty/contracts.js.map +1 -0
- package/dist/pty/evidence-gate.d.ts +14 -0
- package/dist/pty/evidence-gate.d.ts.map +1 -0
- package/dist/pty/evidence-gate.js +35 -0
- package/dist/pty/evidence-gate.js.map +1 -0
- package/dist/pty/index.d.ts +5 -0
- package/dist/pty/index.d.ts.map +1 -0
- package/dist/pty/index.js +5 -0
- package/dist/pty/index.js.map +1 -0
- package/dist/pty/node-host-terminal.d.ts +14 -0
- package/dist/pty/node-host-terminal.d.ts.map +1 -0
- package/dist/pty/node-host-terminal.js +173 -0
- package/dist/pty/node-host-terminal.js.map +1 -0
- package/dist/pty/node-process.d.ts +26 -0
- package/dist/pty/node-process.d.ts.map +1 -0
- package/dist/pty/node-process.js +102 -0
- package/dist/pty/node-process.js.map +1 -0
- package/dist/runtime/api-terminal-control-plane.d.ts +7 -0
- package/dist/runtime/api-terminal-control-plane.d.ts.map +1 -0
- package/dist/runtime/api-terminal-control-plane.js +95 -0
- package/dist/runtime/api-terminal-control-plane.js.map +1 -0
- package/dist/runtime/backoff.d.ts +17 -0
- package/dist/runtime/backoff.d.ts.map +1 -0
- package/dist/runtime/backoff.js +61 -0
- package/dist/runtime/backoff.js.map +1 -0
- package/dist/runtime/boundary.d.ts +171 -0
- package/dist/runtime/boundary.d.ts.map +1 -0
- package/dist/runtime/boundary.js +1858 -0
- package/dist/runtime/boundary.js.map +1 -0
- package/dist/runtime/capability-gate.d.ts +25 -0
- package/dist/runtime/capability-gate.d.ts.map +1 -0
- package/dist/runtime/capability-gate.js +77 -0
- package/dist/runtime/capability-gate.js.map +1 -0
- package/dist/runtime/contracts.d.ts +25 -0
- package/dist/runtime/contracts.d.ts.map +1 -0
- package/dist/runtime/contracts.js +66 -0
- package/dist/runtime/contracts.js.map +1 -0
- package/dist/runtime/errors.d.ts +20 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +22 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +9 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/node-foreground-session.d.ts +50 -0
- package/dist/runtime/node-foreground-session.d.ts.map +1 -0
- package/dist/runtime/node-foreground-session.js +431 -0
- package/dist/runtime/node-foreground-session.js.map +1 -0
- package/dist/runtime/node-websocket-connector.d.ts +6 -0
- package/dist/runtime/node-websocket-connector.d.ts.map +1 -0
- package/dist/runtime/node-websocket-connector.js +344 -0
- package/dist/runtime/node-websocket-connector.js.map +1 -0
- package/dist/runtime/observer-screen.d.ts +6 -0
- package/dist/runtime/observer-screen.d.ts.map +1 -0
- package/dist/runtime/observer-screen.js +144 -0
- package/dist/runtime/observer-screen.js.map +1 -0
- package/dist/runtime/owner-grant-operations.d.ts +136 -0
- package/dist/runtime/owner-grant-operations.d.ts.map +1 -0
- package/dist/runtime/owner-grant-operations.js +216 -0
- package/dist/runtime/owner-grant-operations.js.map +1 -0
- package/dist/runtime/owner-grants-screen.d.ts +40 -0
- package/dist/runtime/owner-grants-screen.d.ts.map +1 -0
- package/dist/runtime/owner-grants-screen.js +987 -0
- package/dist/runtime/owner-grants-screen.js.map +1 -0
- package/dist/runtime/terminal-transport.d.ts +85 -0
- package/dist/runtime/terminal-transport.d.ts.map +1 -0
- package/dist/runtime/terminal-transport.js +152 -0
- package/dist/runtime/terminal-transport.js.map +1 -0
- package/dist/sync/continuous-sync-supervisor.d.ts +107 -0
- package/dist/sync/continuous-sync-supervisor.d.ts.map +1 -0
- package/dist/sync/continuous-sync-supervisor.js +1117 -0
- package/dist/sync/continuous-sync-supervisor.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +9 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/journal.d.ts +57 -0
- package/dist/sync/journal.d.ts.map +1 -0
- package/dist/sync/journal.js +685 -0
- package/dist/sync/journal.js.map +1 -0
- package/dist/sync/revisions.d.ts +118 -0
- package/dist/sync/revisions.d.ts.map +1 -0
- package/dist/sync/revisions.js +364 -0
- package/dist/sync/revisions.js.map +1 -0
- package/dist/sync/supervisor.d.ts +107 -0
- package/dist/sync/supervisor.d.ts.map +1 -0
- package/dist/sync/supervisor.js +246 -0
- package/dist/sync/supervisor.js.map +1 -0
- package/dist/sync/workspace-sync-client.d.ts +18 -0
- package/dist/sync/workspace-sync-client.d.ts.map +1 -0
- package/dist/sync/workspace-sync-client.js +150 -0
- package/dist/sync/workspace-sync-client.js.map +1 -0
- package/dist/sync/workspace-sync-coordinator.d.ts +76 -0
- package/dist/sync/workspace-sync-coordinator.d.ts.map +1 -0
- package/dist/sync/workspace-sync-coordinator.js +621 -0
- package/dist/sync/workspace-sync-coordinator.js.map +1 -0
- package/dist/sync/workspace-sync-product-service.d.ts +79 -0
- package/dist/sync/workspace-sync-product-service.d.ts.map +1 -0
- package/dist/sync/workspace-sync-product-service.js +398 -0
- package/dist/sync/workspace-sync-product-service.js.map +1 -0
- package/dist/sync/workspace-sync-protocol.d.ts +145 -0
- package/dist/sync/workspace-sync-protocol.d.ts.map +1 -0
- package/dist/sync/workspace-sync-protocol.js +347 -0
- package/dist/sync/workspace-sync-protocol.js.map +1 -0
- package/dist/terminal/appbar.d.ts +40 -0
- package/dist/terminal/appbar.d.ts.map +1 -0
- package/dist/terminal/appbar.js +55 -0
- package/dist/terminal/appbar.js.map +1 -0
- package/dist/terminal/cell-width.d.ts +3 -0
- package/dist/terminal/cell-width.d.ts.map +1 -0
- package/dist/terminal/cell-width.js +53 -0
- package/dist/terminal/cell-width.js.map +1 -0
- package/dist/terminal/codec.d.ts +107 -0
- package/dist/terminal/codec.d.ts.map +1 -0
- package/dist/terminal/codec.js +538 -0
- package/dist/terminal/codec.js.map +1 -0
- package/dist/terminal/foreground.d.ts +135 -0
- package/dist/terminal/foreground.d.ts.map +1 -0
- package/dist/terminal/foreground.js +2097 -0
- package/dist/terminal/foreground.js.map +1 -0
- package/dist/terminal/generated/terminal-wire-v1.d.ts +26 -0
- package/dist/terminal/generated/terminal-wire-v1.d.ts.map +1 -0
- package/dist/terminal/generated/terminal-wire-v1.js +27 -0
- package/dist/terminal/generated/terminal-wire-v1.js.map +1 -0
- package/dist/terminal/mode.d.ts +33 -0
- package/dist/terminal/mode.d.ts.map +1 -0
- package/dist/terminal/mode.js +99 -0
- package/dist/terminal/mode.js.map +1 -0
- package/dist/terminal/passthrough.d.ts +47 -0
- package/dist/terminal/passthrough.d.ts.map +1 -0
- package/dist/terminal/passthrough.js +666 -0
- package/dist/terminal/passthrough.js.map +1 -0
- package/dist/terminal/resume.d.ts +58 -0
- package/dist/terminal/resume.d.ts.map +1 -0
- package/dist/terminal/resume.js +185 -0
- package/dist/terminal/resume.js.map +1 -0
- package/dist/terminal/viewport.d.ts +93 -0
- package/dist/terminal/viewport.d.ts.map +1 -0
- package/dist/terminal/viewport.js +266 -0
- package/dist/terminal/viewport.js.map +1 -0
- package/dist/terminal/workbench.d.ts +32 -0
- package/dist/terminal/workbench.d.ts.map +1 -0
- package/dist/terminal/workbench.js +316 -0
- package/dist/terminal/workbench.js.map +1 -0
- package/dist/terminal/xterm-vte.d.ts +49 -0
- package/dist/terminal/xterm-vte.d.ts.map +1 -0
- package/dist/terminal/xterm-vte.js +602 -0
- package/dist/terminal/xterm-vte.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/workspace/binding-path.d.ts +17 -0
- package/dist/workspace/binding-path.d.ts.map +1 -0
- package/dist/workspace/binding-path.js +91 -0
- package/dist/workspace/binding-path.js.map +1 -0
- package/dist/workspace/binding-store.d.ts +107 -0
- package/dist/workspace/binding-store.d.ts.map +1 -0
- package/dist/workspace/binding-store.js +783 -0
- package/dist/workspace/binding-store.js.map +1 -0
- package/dist/workspace/binding.d.ts +35 -0
- package/dist/workspace/binding.d.ts.map +1 -0
- package/dist/workspace/binding.js +70 -0
- package/dist/workspace/binding.js.map +1 -0
- package/dist/workspace/errors.d.ts +4 -0
- package/dist/workspace/errors.d.ts.map +1 -0
- package/dist/workspace/errors.js +19 -0
- package/dist/workspace/errors.js.map +1 -0
- package/dist/workspace/exclusion.d.ts +21 -0
- package/dist/workspace/exclusion.d.ts.map +1 -0
- package/dist/workspace/exclusion.js +243 -0
- package/dist/workspace/exclusion.js.map +1 -0
- package/dist/workspace/index.d.ts +8 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +8 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/lifecycle.d.ts +56 -0
- package/dist/workspace/lifecycle.d.ts.map +1 -0
- package/dist/workspace/lifecycle.js +94 -0
- package/dist/workspace/lifecycle.js.map +1 -0
- package/dist/workspace/manifest.d.ts +47 -0
- package/dist/workspace/manifest.d.ts.map +1 -0
- package/dist/workspace/manifest.js +243 -0
- package/dist/workspace/manifest.js.map +1 -0
- package/dist/workspace/paths.d.ts +14 -0
- package/dist/workspace/paths.d.ts.map +1 -0
- package/dist/workspace/paths.js +67 -0
- package/dist/workspace/paths.js.map +1 -0
- package/dist/workspace/schema.d.ts +15 -0
- package/dist/workspace/schema.d.ts.map +1 -0
- package/dist/workspace/schema.js +33 -0
- package/dist/workspace/schema.js.map +1 -0
- package/dist/workspace/selection-screen.d.ts +4 -0
- package/dist/workspace/selection-screen.d.ts.map +1 -0
- package/dist/workspace/selection-screen.js +216 -0
- package/dist/workspace/selection-screen.js.map +1 -0
- package/dist/workspace/selection-service.d.ts +16 -0
- package/dist/workspace/selection-service.d.ts.map +1 -0
- package/dist/workspace/selection-service.js +65 -0
- package/dist/workspace/selection-service.js.map +1 -0
- package/node_modules/@xterm/headless/README.md +31 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.js +2 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.js.map +1 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs +46 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs.map +7 -0
- package/node_modules/@xterm/headless/package.json +29 -0
- package/node_modules/@xterm/headless/typings/xterm-headless.d.ts +1385 -0
- package/package.json +75 -0
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { constants as fileConstants, watch } from "node:fs";
|
|
3
|
+
import { chmod, lstat, mkdir, open, readFile, realpath, rename, rmdir, unlink, writeFile, } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { EXIT_CODES, CunaError } from "../core/errors.js";
|
|
6
|
+
import { createWorkspaceManifest } from "../workspace/manifest.js";
|
|
7
|
+
import { assertLexicallyInsideRoot, normalizeWirePath, } from "../workspace/paths.js";
|
|
8
|
+
import { DurableSyncJournal } from "./journal.js";
|
|
9
|
+
import { decodeChangePage } from "./workspace-sync-protocol.js";
|
|
10
|
+
const STATE_SCHEMA = 1;
|
|
11
|
+
const ZERO_DIGEST = "0".repeat(64);
|
|
12
|
+
const DEFAULT_DEBOUNCE_MS = 250;
|
|
13
|
+
const DEFAULT_RECONCILE_MS = 60_000;
|
|
14
|
+
const DEFAULT_REMOTE_POLL_MS = 750;
|
|
15
|
+
const DEFAULT_OPERATION_LIMIT = 10_000;
|
|
16
|
+
const DEFAULT_BYTE_LIMIT = 256 * 1024 * 1024;
|
|
17
|
+
/**
|
|
18
|
+
* One foreground supervisor owns one WorkspaceBinding writer lease. It keeps
|
|
19
|
+
* synchronization on an independent asynchronous control path: terminal I/O
|
|
20
|
+
* never enters this queue, and every scan/poll batch yields before continuing.
|
|
21
|
+
*/
|
|
22
|
+
export class ContinuousWorkspaceSyncSupervisor {
|
|
23
|
+
#input;
|
|
24
|
+
#clock;
|
|
25
|
+
#sleep;
|
|
26
|
+
#manifestBuilder;
|
|
27
|
+
#listeners = new Set();
|
|
28
|
+
#controller = new AbortController();
|
|
29
|
+
#statePath;
|
|
30
|
+
#journalDirectory;
|
|
31
|
+
#state;
|
|
32
|
+
#journal;
|
|
33
|
+
#writerLease;
|
|
34
|
+
#watcher;
|
|
35
|
+
#loop;
|
|
36
|
+
#wake;
|
|
37
|
+
#scanRequested = false;
|
|
38
|
+
#reconcileRequested = false;
|
|
39
|
+
#closed = false;
|
|
40
|
+
constructor(input, state) {
|
|
41
|
+
this.#input = input;
|
|
42
|
+
this.#clock = input.clock ?? Date.now;
|
|
43
|
+
this.#sleep = input.sleep ?? abortableSleep;
|
|
44
|
+
this.#manifestBuilder = input.manifestBuilder ?? createWorkspaceManifest;
|
|
45
|
+
this.#state = state;
|
|
46
|
+
this.#statePath = join(input.stateDirectory, "continuous-sync.state.json");
|
|
47
|
+
this.#journalDirectory = join(input.stateDirectory, "operation-journal");
|
|
48
|
+
}
|
|
49
|
+
static async start(input) {
|
|
50
|
+
validateInput(input);
|
|
51
|
+
const root = await canonicalPlainDirectory(input.canonicalRoot);
|
|
52
|
+
const stateDirectory = await preparePrivateDirectory(input.stateDirectory, root);
|
|
53
|
+
const normalizedInput = Object.freeze({ ...input, canonicalRoot: root, stateDirectory });
|
|
54
|
+
const initialManifest = input.initialManifest ?? await (input.manifestBuilder ?? createWorkspaceManifest)({
|
|
55
|
+
root,
|
|
56
|
+
policy: input.policy,
|
|
57
|
+
capabilities: input.filesystemCapabilities,
|
|
58
|
+
});
|
|
59
|
+
if (initialManifest.policyDigest !== input.policy.digest ||
|
|
60
|
+
initialManifest.manifestRoot !== input.initialManifestRoot) {
|
|
61
|
+
throw syncFailure("initial_manifest_unproven", EXIT_CODES.conflict);
|
|
62
|
+
}
|
|
63
|
+
const statePath = join(stateDirectory, "continuous-sync.state.json");
|
|
64
|
+
const loaded = await loadState(statePath);
|
|
65
|
+
const state = loaded === undefined
|
|
66
|
+
? createInitialState(normalizedInput, initialManifest)
|
|
67
|
+
: admitState(loaded, normalizedInput);
|
|
68
|
+
if (loaded === undefined)
|
|
69
|
+
await atomicWriteState(statePath, state);
|
|
70
|
+
const supervisor = new ContinuousWorkspaceSyncSupervisor(normalizedInput, state);
|
|
71
|
+
try {
|
|
72
|
+
await supervisor.#open();
|
|
73
|
+
return supervisor;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
try {
|
|
77
|
+
await supervisor.stop();
|
|
78
|
+
}
|
|
79
|
+
catch (cleanupError) {
|
|
80
|
+
throw new AggregateError([error, cleanupError], "Continuous synchronization startup cleanup was incomplete.");
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
get snapshot() {
|
|
86
|
+
return Object.freeze({
|
|
87
|
+
state: this.#closed ? "stopped" : this.#state.status,
|
|
88
|
+
generation: this.#state.generation,
|
|
89
|
+
manifestRoot: this.#state.manifest_root,
|
|
90
|
+
dirty: this.#state.dirty,
|
|
91
|
+
pendingLocalOperations: this.#state.pending_local.length,
|
|
92
|
+
pendingRemoteChanges: this.#state.pending_remote === null
|
|
93
|
+
? 0
|
|
94
|
+
: this.#state.pending_remote.items.length - this.#state.pending_remote.nextIndex,
|
|
95
|
+
...(this.#state.reason === null ? {} : { reason: this.#state.reason }),
|
|
96
|
+
observedAt: this.#state.updated_at,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
subscribe(listener) {
|
|
100
|
+
this.#listeners.add(listener);
|
|
101
|
+
try {
|
|
102
|
+
listener(this.snapshot);
|
|
103
|
+
}
|
|
104
|
+
catch { /* Projection observers cannot own sync correctness. */ }
|
|
105
|
+
return () => this.#listeners.delete(listener);
|
|
106
|
+
}
|
|
107
|
+
requestScan() {
|
|
108
|
+
if (this.#closed)
|
|
109
|
+
return;
|
|
110
|
+
this.#scanRequested = true;
|
|
111
|
+
this.#wake?.();
|
|
112
|
+
}
|
|
113
|
+
requestReconciliation(reason = "explicit_reconciliation") {
|
|
114
|
+
if (this.#closed)
|
|
115
|
+
return;
|
|
116
|
+
this.#reconcileRequested = true;
|
|
117
|
+
// The run loop is the sole durable-state writer. Publishing an optimistic
|
|
118
|
+
// transition here would race a concurrent apply/checkpoint write.
|
|
119
|
+
void reason;
|
|
120
|
+
this.#wake?.();
|
|
121
|
+
}
|
|
122
|
+
async stop() {
|
|
123
|
+
if (this.#closed)
|
|
124
|
+
return;
|
|
125
|
+
this.#closed = true;
|
|
126
|
+
this.#controller.abort(syncFailure("cancelled", EXIT_CODES.network));
|
|
127
|
+
this.#wake?.();
|
|
128
|
+
const failures = [];
|
|
129
|
+
try {
|
|
130
|
+
await this.#watcher?.close();
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
failures.push(error);
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await this.#loop;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (!isAbort(error))
|
|
140
|
+
failures.push(error);
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
await this.#journal?.close();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
failures.push(error);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
await this.#writerLease?.close();
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
failures.push(error);
|
|
153
|
+
}
|
|
154
|
+
this.#publish();
|
|
155
|
+
if (failures.length > 0)
|
|
156
|
+
throw new AggregateError(failures, "Continuous synchronization cleanup was incomplete.");
|
|
157
|
+
}
|
|
158
|
+
async waitForStop() {
|
|
159
|
+
await this.#loop;
|
|
160
|
+
}
|
|
161
|
+
async #open() {
|
|
162
|
+
if (this.#input.writerLeaseDirectory !== undefined) {
|
|
163
|
+
this.#writerLease = await DurableSyncJournal.open({
|
|
164
|
+
directory: this.#input.writerLeaseDirectory,
|
|
165
|
+
bindingId: this.#input.bindingId,
|
|
166
|
+
// This journal is only the stable per-binding writer authority; its
|
|
167
|
+
// metadata generation deliberately does not follow content revisions.
|
|
168
|
+
bindingGeneration: 1,
|
|
169
|
+
ownerId: `continuous-sync-authority:${process.pid}:${randomUUID()}`,
|
|
170
|
+
clock: this.#clock,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
this.#journal = await DurableSyncJournal.open({
|
|
175
|
+
directory: this.#journalDirectory,
|
|
176
|
+
bindingId: this.#input.bindingId,
|
|
177
|
+
bindingGeneration: this.#input.bindingGeneration,
|
|
178
|
+
ownerId: `continuous-sync:${process.pid}:${randomUUID()}`,
|
|
179
|
+
clock: this.#clock,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
await this.#writerLease?.close();
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
if (this.#state.pending_remote !== null)
|
|
187
|
+
await this.#resumeRemoteApply();
|
|
188
|
+
await this.#recoverPendingLocal();
|
|
189
|
+
const watchFactory = this.#input.watchFactory ?? createNodeWorkspaceWatcher;
|
|
190
|
+
this.#watcher = await watchFactory({
|
|
191
|
+
root: this.#input.canonicalRoot,
|
|
192
|
+
onEvent: (event) => {
|
|
193
|
+
if (event.kind === "overflow" || event.path === undefined) {
|
|
194
|
+
this.requestReconciliation("watcher_overflow");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const path = normalizeWirePath(event.path.replaceAll("\\", "/"), this.#input.filesystemCapabilities);
|
|
199
|
+
const decision = this.#input.policy.decide(path);
|
|
200
|
+
if (!decision.excluded)
|
|
201
|
+
this.requestScan();
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
this.requestReconciliation("watcher_path_untrusted");
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
onError: () => this.requestReconciliation("watcher_failed"),
|
|
208
|
+
});
|
|
209
|
+
this.#scanRequested = true;
|
|
210
|
+
this.#loop = this.#runLoop();
|
|
211
|
+
}
|
|
212
|
+
async #runLoop() {
|
|
213
|
+
const signal = this.#controller.signal;
|
|
214
|
+
let lastReconciliation = this.#clock();
|
|
215
|
+
while (!signal.aborted) {
|
|
216
|
+
try {
|
|
217
|
+
if (this.#reconcileRequested || this.#clock() - lastReconciliation >= (this.#input.reconciliationIntervalMs ?? DEFAULT_RECONCILE_MS)) {
|
|
218
|
+
this.#reconcileRequested = false;
|
|
219
|
+
await this.#reconcile(signal);
|
|
220
|
+
lastReconciliation = this.#clock();
|
|
221
|
+
}
|
|
222
|
+
if (this.#scanRequested && this.#state.status !== "conflicted" && this.#state.status !== "recovery_required") {
|
|
223
|
+
this.#scanRequested = false;
|
|
224
|
+
await this.#sleep(this.#input.debounceMs ?? DEFAULT_DEBOUNCE_MS, signal);
|
|
225
|
+
await this.#scanAndCommit(signal);
|
|
226
|
+
}
|
|
227
|
+
if (this.#state.status !== "conflicted" && this.#state.status !== "recovery_required" && this.#state.status !== "paused") {
|
|
228
|
+
await this.#consumeRemote(signal);
|
|
229
|
+
}
|
|
230
|
+
await this.#journal?.renew();
|
|
231
|
+
await this.#waitForWake(this.#input.remotePollIntervalMs ?? DEFAULT_REMOTE_POLL_MS, signal);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
if (signal.aborted)
|
|
235
|
+
break;
|
|
236
|
+
const classification = classifyFailure(error);
|
|
237
|
+
await this.#transition({
|
|
238
|
+
status: classification.status,
|
|
239
|
+
dirty: true,
|
|
240
|
+
reason: classification.reason,
|
|
241
|
+
});
|
|
242
|
+
if (classification.status === "recovery_required" || classification.status === "conflicted")
|
|
243
|
+
break;
|
|
244
|
+
await this.#waitForWake(this.#input.remotePollIntervalMs ?? DEFAULT_REMOTE_POLL_MS, signal);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async #scanAndCommit(signal) {
|
|
249
|
+
await this.#transition({ status: "catching_up", reason: null });
|
|
250
|
+
const manifest = await this.#buildManifest();
|
|
251
|
+
if (manifest.manifestRoot === this.#state.manifest_root) {
|
|
252
|
+
if (!this.#state.dirty)
|
|
253
|
+
await this.#transition({ status: "live_unverified", reason: null });
|
|
254
|
+
else
|
|
255
|
+
this.#reconcileRequested = true;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const observedOperations = diffManifest(this.#state.baseline, manifest, this.#state.generation);
|
|
259
|
+
const operations = this.#state.pending_local.length === 0
|
|
260
|
+
? observedOperations
|
|
261
|
+
: samePendingIntent(this.#state.pending_local, observedOperations)
|
|
262
|
+
? this.#state.pending_local
|
|
263
|
+
: (() => { throw syncFailure("pending_local_intent_changed", EXIT_CODES.conflict); })();
|
|
264
|
+
const maximumOperations = this.#input.maximumPendingOperations ?? DEFAULT_OPERATION_LIMIT;
|
|
265
|
+
const maximumBytes = this.#input.maximumPendingBytes ?? DEFAULT_BYTE_LIMIT;
|
|
266
|
+
const bytes = operations.reduce((total, operation) => total + operation.byteLength, 0);
|
|
267
|
+
if (operations.length > maximumOperations || bytes > maximumBytes) {
|
|
268
|
+
await this.#transition({ status: "paused", dirty: true, reason: operations.length > maximumOperations ? "operation_limit" : "byte_limit" });
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (this.#state.pending_local.length === 0) {
|
|
272
|
+
for (const operation of operations) {
|
|
273
|
+
await this.#journal?.append({
|
|
274
|
+
operationId: operation.operationId,
|
|
275
|
+
baseGeneration: operation.baseGeneration,
|
|
276
|
+
digest: operation.fingerprint,
|
|
277
|
+
byteLength: operation.byteLength,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
await this.#replaceState({ pending_local: operations, status: "catching_up", dirty: this.#state.dirty, reason: null });
|
|
281
|
+
}
|
|
282
|
+
for (const operation of operations)
|
|
283
|
+
await transitionIfQueued(this.#journal, operation.operationId);
|
|
284
|
+
let receipt;
|
|
285
|
+
try {
|
|
286
|
+
receipt = await this.#input.authority.commitLocalSnapshot({
|
|
287
|
+
baseGeneration: this.#state.generation,
|
|
288
|
+
manifest,
|
|
289
|
+
signal,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
for (const operation of operations)
|
|
294
|
+
await transitionIfPossible(this.#journal, operation.operationId, "uncertain");
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
if (receipt.generation !== this.#state.generation + 1 ||
|
|
298
|
+
receipt.manifestRoot !== manifest.manifestRoot ||
|
|
299
|
+
!isUuid(receipt.syncId)) {
|
|
300
|
+
throw syncFailure("commit_receipt_mismatch", EXIT_CODES.remote);
|
|
301
|
+
}
|
|
302
|
+
for (const operation of operations) {
|
|
303
|
+
await this.#journal?.transition(operation.operationId, "acknowledged");
|
|
304
|
+
await this.#journal?.transition(operation.operationId, "applied");
|
|
305
|
+
}
|
|
306
|
+
await this.#replaceState({
|
|
307
|
+
sync_id: receipt.syncId,
|
|
308
|
+
generation: receipt.generation,
|
|
309
|
+
manifest_root: receipt.manifestRoot,
|
|
310
|
+
baseline: projectManifest(manifest),
|
|
311
|
+
pending_local: [],
|
|
312
|
+
cursor: null,
|
|
313
|
+
status: "live_unverified",
|
|
314
|
+
dirty: false,
|
|
315
|
+
reason: null,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
async #consumeRemote(signal) {
|
|
319
|
+
const items = [];
|
|
320
|
+
let cursor = this.#state.cursor ?? undefined;
|
|
321
|
+
let terminalCursor = null;
|
|
322
|
+
for (let pageIndex = 0; pageIndex < 256; pageIndex += 1) {
|
|
323
|
+
const page = await this.#input.authority.listChanges({
|
|
324
|
+
syncId: this.#state.sync_id,
|
|
325
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
326
|
+
afterGeneration: this.#state.generation,
|
|
327
|
+
signal,
|
|
328
|
+
});
|
|
329
|
+
items.push(...page.items.filter((item) => item.generation > this.#state.generation));
|
|
330
|
+
terminalCursor = page.next_cursor;
|
|
331
|
+
if (page.next_cursor === null)
|
|
332
|
+
break;
|
|
333
|
+
if (page.next_cursor === cursor)
|
|
334
|
+
throw syncFailure("remote_cursor_stalled", EXIT_CODES.conflict);
|
|
335
|
+
cursor = page.next_cursor;
|
|
336
|
+
if (pageIndex === 255)
|
|
337
|
+
throw syncFailure("remote_page_limit", EXIT_CODES.conflict);
|
|
338
|
+
}
|
|
339
|
+
const unseen = items;
|
|
340
|
+
if (unseen.length === 0) {
|
|
341
|
+
if (!this.#state.dirty && this.#state.status !== "converged") {
|
|
342
|
+
await this.#transition({ status: "live_unverified", reason: null });
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
let offset = 0;
|
|
347
|
+
while (offset < unseen.length) {
|
|
348
|
+
const generation = unseen[offset]?.generation;
|
|
349
|
+
if (generation === undefined || generation !== this.#state.generation + 1) {
|
|
350
|
+
throw syncFailure("remote_sequence_gap", EXIT_CODES.conflict);
|
|
351
|
+
}
|
|
352
|
+
const items = [];
|
|
353
|
+
while (offset < unseen.length && unseen[offset]?.generation === generation) {
|
|
354
|
+
const item = unseen[offset];
|
|
355
|
+
if (item !== undefined)
|
|
356
|
+
items.push(item);
|
|
357
|
+
offset += 1;
|
|
358
|
+
}
|
|
359
|
+
const manifestRoot = commonRemoteField(items, "manifest_root");
|
|
360
|
+
const policyDigest = commonRemoteField(items, "exclusion_policy_digest");
|
|
361
|
+
if (policyDigest !== this.#input.policy.digest)
|
|
362
|
+
throw syncFailure("remote_policy_changed", EXIT_CODES.conflict);
|
|
363
|
+
await this.#replaceState({
|
|
364
|
+
status: "catching_up",
|
|
365
|
+
pending_remote: Object.freeze({
|
|
366
|
+
generation,
|
|
367
|
+
manifestRoot,
|
|
368
|
+
cursor: offset === unseen.length ? terminalCursor : this.#state.cursor,
|
|
369
|
+
items: Object.freeze(items),
|
|
370
|
+
nextIndex: 0,
|
|
371
|
+
}),
|
|
372
|
+
reason: null,
|
|
373
|
+
});
|
|
374
|
+
await this.#resumeRemoteApply();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async #resumeRemoteApply() {
|
|
378
|
+
const pending = this.#state.pending_remote;
|
|
379
|
+
if (pending === null)
|
|
380
|
+
return;
|
|
381
|
+
const signal = this.#controller.signal;
|
|
382
|
+
const current = await this.#buildManifest();
|
|
383
|
+
const currentEntries = new Map(projectManifest(current).map((entry) => [entry.path, entry]));
|
|
384
|
+
const baseline = new Map(this.#state.baseline.map((entry) => [entry.path, entry]));
|
|
385
|
+
const ordered = orderRemoteItems(pending.items);
|
|
386
|
+
for (let index = pending.nextIndex; index < ordered.length; index += 1) {
|
|
387
|
+
const item = ordered[index];
|
|
388
|
+
if (item === undefined)
|
|
389
|
+
continue;
|
|
390
|
+
if (item.operation !== "revision") {
|
|
391
|
+
const path = item.path;
|
|
392
|
+
if (path === null)
|
|
393
|
+
throw syncFailure("remote_change_shape", EXIT_CODES.remote);
|
|
394
|
+
const prior = baseline.get(path);
|
|
395
|
+
const observed = currentEntries.get(path);
|
|
396
|
+
if (!sameProjection(prior, observed)) {
|
|
397
|
+
await this.#retainConflict(item, signal);
|
|
398
|
+
await this.#replaceState({
|
|
399
|
+
pending_remote: Object.freeze({ ...pending, items: Object.freeze(ordered), nextIndex: index + 1 }),
|
|
400
|
+
status: "conflicted",
|
|
401
|
+
dirty: true,
|
|
402
|
+
reason: "same_path_diverged",
|
|
403
|
+
});
|
|
404
|
+
throw syncFailure("same_path_diverged", EXIT_CODES.conflict);
|
|
405
|
+
}
|
|
406
|
+
await this.#applyRemoteItem(item, signal);
|
|
407
|
+
}
|
|
408
|
+
await this.#replaceState({
|
|
409
|
+
pending_remote: Object.freeze({ ...pending, items: Object.freeze(ordered), nextIndex: index + 1 }),
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
const manifest = await this.#buildManifest();
|
|
413
|
+
if (manifest.manifestRoot !== pending.manifestRoot) {
|
|
414
|
+
throw syncFailure("remote_apply_manifest_mismatch", EXIT_CODES.conflict);
|
|
415
|
+
}
|
|
416
|
+
await this.#replaceState({
|
|
417
|
+
generation: pending.generation,
|
|
418
|
+
manifest_root: pending.manifestRoot,
|
|
419
|
+
baseline: projectManifest(manifest),
|
|
420
|
+
cursor: pending.cursor,
|
|
421
|
+
pending_remote: null,
|
|
422
|
+
dirty: false,
|
|
423
|
+
status: "live_unverified",
|
|
424
|
+
reason: null,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
async #applyRemoteItem(item, signal) {
|
|
428
|
+
if (item.path === null)
|
|
429
|
+
return;
|
|
430
|
+
const path = normalizeWirePath(item.path, this.#input.filesystemCapabilities);
|
|
431
|
+
const physical = assertLexicallyInsideRoot(this.#input.canonicalRoot, join(this.#input.canonicalRoot, path));
|
|
432
|
+
if (item.operation === "delete") {
|
|
433
|
+
await safeDelete(this.#input.canonicalRoot, physical);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const entry = item.entry;
|
|
437
|
+
if (entry === null)
|
|
438
|
+
throw syncFailure("remote_change_shape", EXIT_CODES.remote);
|
|
439
|
+
const decision = this.#input.policy.decide(path, entry.kind);
|
|
440
|
+
if (decision.excluded)
|
|
441
|
+
throw syncFailure("remote_excluded_path", EXIT_CODES.policy);
|
|
442
|
+
if (entry.kind === "symlink")
|
|
443
|
+
throw syncFailure("remote_symlink_unsupported", EXIT_CODES.policy);
|
|
444
|
+
if (entry.kind === "directory") {
|
|
445
|
+
await ensureSafeDirectory(this.#input.canonicalRoot, physical);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const chunks = [];
|
|
449
|
+
let total = 0;
|
|
450
|
+
for (const chunk of entry.chunks) {
|
|
451
|
+
const bytes = await this.#input.authority.readChunk({
|
|
452
|
+
syncId: this.#state.sync_id,
|
|
453
|
+
digest: chunk.digest,
|
|
454
|
+
byteLength: chunk.byte_length,
|
|
455
|
+
signal,
|
|
456
|
+
});
|
|
457
|
+
if (bytes.byteLength !== chunk.byte_length || sha256(bytes) !== chunk.digest) {
|
|
458
|
+
throw syncFailure("remote_chunk_mismatch", EXIT_CODES.policy);
|
|
459
|
+
}
|
|
460
|
+
chunks.push(bytes);
|
|
461
|
+
total += bytes.byteLength;
|
|
462
|
+
}
|
|
463
|
+
if (total !== entry.byte_length)
|
|
464
|
+
throw syncFailure("remote_content_length_mismatch", EXIT_CODES.remote);
|
|
465
|
+
await atomicReplaceFile(this.#input.canonicalRoot, physical, chunks, entry.executable);
|
|
466
|
+
}
|
|
467
|
+
async #retainConflict(item, signal) {
|
|
468
|
+
if (item.operation !== "upsert" || item.entry === null || item.path === null || item.entry.kind !== "file")
|
|
469
|
+
return;
|
|
470
|
+
const suffix = createHash("sha256")
|
|
471
|
+
.update(`${this.#input.bindingId}\0${item.generation}\0${item.path}`)
|
|
472
|
+
.digest("hex")
|
|
473
|
+
.slice(0, 12);
|
|
474
|
+
const retained = `${item.path}.cuna-conflict-${item.generation}-${suffix}`;
|
|
475
|
+
const physical = assertLexicallyInsideRoot(this.#input.canonicalRoot, join(this.#input.canonicalRoot, retained));
|
|
476
|
+
const chunks = [];
|
|
477
|
+
for (const chunk of item.entry.chunks) {
|
|
478
|
+
const bytes = await this.#input.authority.readChunk({
|
|
479
|
+
syncId: this.#state.sync_id,
|
|
480
|
+
digest: chunk.digest,
|
|
481
|
+
byteLength: chunk.byte_length,
|
|
482
|
+
signal,
|
|
483
|
+
});
|
|
484
|
+
if (bytes.byteLength !== chunk.byte_length || sha256(bytes) !== chunk.digest) {
|
|
485
|
+
throw syncFailure("remote_chunk_mismatch", EXIT_CODES.policy);
|
|
486
|
+
}
|
|
487
|
+
chunks.push(bytes);
|
|
488
|
+
}
|
|
489
|
+
await atomicReplaceFile(this.#input.canonicalRoot, physical, chunks, item.entry.executable, true);
|
|
490
|
+
}
|
|
491
|
+
async #reconcile(signal) {
|
|
492
|
+
await this.#transition({ status: "reconciling", dirty: true, reason: "manifest_reconciliation" });
|
|
493
|
+
const manifest = await this.#buildManifest();
|
|
494
|
+
const receipt = await this.#input.authority.reconcile({
|
|
495
|
+
generation: this.#state.generation,
|
|
496
|
+
manifestRoot: manifest.manifestRoot,
|
|
497
|
+
signal,
|
|
498
|
+
});
|
|
499
|
+
if (receipt.status === "converged") {
|
|
500
|
+
if (receipt.generation !== this.#state.generation || receipt.manifestRoot !== manifest.manifestRoot) {
|
|
501
|
+
throw syncFailure("reconcile_receipt_mismatch", EXIT_CODES.remote);
|
|
502
|
+
}
|
|
503
|
+
await this.#replaceState({
|
|
504
|
+
manifest_root: manifest.manifestRoot,
|
|
505
|
+
baseline: projectManifest(manifest),
|
|
506
|
+
dirty: false,
|
|
507
|
+
status: "converged",
|
|
508
|
+
reason: null,
|
|
509
|
+
});
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (receipt.generation < this.#state.generation)
|
|
513
|
+
throw syncFailure("remote_generation_rollback", EXIT_CODES.policy);
|
|
514
|
+
if (this.#state.pending_local.length > 0 &&
|
|
515
|
+
receipt.generation === this.#state.generation + 1 &&
|
|
516
|
+
receipt.manifestRoot === manifest.manifestRoot) {
|
|
517
|
+
for (const operation of this.#state.pending_local) {
|
|
518
|
+
await transitionToApplied(this.#journal, operation.operationId);
|
|
519
|
+
}
|
|
520
|
+
await this.#replaceState({
|
|
521
|
+
generation: receipt.generation,
|
|
522
|
+
manifest_root: receipt.manifestRoot,
|
|
523
|
+
baseline: projectManifest(manifest),
|
|
524
|
+
pending_local: [],
|
|
525
|
+
cursor: null,
|
|
526
|
+
dirty: false,
|
|
527
|
+
status: "converged",
|
|
528
|
+
reason: null,
|
|
529
|
+
});
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
await this.#replaceState({ status: "reconciling", dirty: true, reason: "canonical_divergence" });
|
|
533
|
+
}
|
|
534
|
+
async #recoverPendingLocal() {
|
|
535
|
+
if (this.#state.pending_local.length === 0)
|
|
536
|
+
return;
|
|
537
|
+
const latest = latestJournalStates(this.#journal?.records ?? []);
|
|
538
|
+
const uncertain = this.#state.pending_local.some((operation) => {
|
|
539
|
+
const state = latest.get(operation.operationId);
|
|
540
|
+
return state === "sending" || state === "uncertain" || state === "acknowledged";
|
|
541
|
+
});
|
|
542
|
+
if (uncertain) {
|
|
543
|
+
await this.#transition({ status: "reconciling", dirty: true, reason: "uncertain_local_commit" });
|
|
544
|
+
this.#reconcileRequested = true;
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
this.#scanRequested = true;
|
|
548
|
+
}
|
|
549
|
+
async #buildManifest() {
|
|
550
|
+
return this.#manifestBuilder({
|
|
551
|
+
root: this.#input.canonicalRoot,
|
|
552
|
+
policy: this.#input.policy,
|
|
553
|
+
capabilities: this.#input.filesystemCapabilities,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
async #transition(input) {
|
|
557
|
+
await this.#replaceState({
|
|
558
|
+
status: input.status,
|
|
559
|
+
...(input.dirty === undefined ? {} : { dirty: input.dirty }),
|
|
560
|
+
reason: input.reason,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
async #replaceState(patch) {
|
|
564
|
+
const next = Object.freeze({
|
|
565
|
+
...this.#state,
|
|
566
|
+
...patch,
|
|
567
|
+
schema_version: STATE_SCHEMA,
|
|
568
|
+
updated_at: new Date(this.#clock()).toISOString(),
|
|
569
|
+
});
|
|
570
|
+
this.#state = admitState(next, this.#input);
|
|
571
|
+
await atomicWriteState(this.#statePath, this.#state);
|
|
572
|
+
this.#publish();
|
|
573
|
+
}
|
|
574
|
+
#publish() {
|
|
575
|
+
const snapshot = this.snapshot;
|
|
576
|
+
for (const listener of this.#listeners) {
|
|
577
|
+
try {
|
|
578
|
+
listener(snapshot);
|
|
579
|
+
}
|
|
580
|
+
catch { /* Projection observers cannot own sync correctness. */ }
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async #waitForWake(milliseconds, signal) {
|
|
584
|
+
if (this.#scanRequested || this.#reconcileRequested)
|
|
585
|
+
return;
|
|
586
|
+
await new Promise((resolveWake, rejectWake) => {
|
|
587
|
+
let timer;
|
|
588
|
+
const abort = () => finish(signal.reason);
|
|
589
|
+
const wake = () => finish();
|
|
590
|
+
const finish = (error) => {
|
|
591
|
+
clearTimeout(timer);
|
|
592
|
+
signal.removeEventListener("abort", abort);
|
|
593
|
+
if (this.#wake === wake)
|
|
594
|
+
this.#wake = undefined;
|
|
595
|
+
if (error === undefined)
|
|
596
|
+
resolveWake();
|
|
597
|
+
else
|
|
598
|
+
rejectWake(error);
|
|
599
|
+
};
|
|
600
|
+
timer = setTimeout(finish, milliseconds);
|
|
601
|
+
this.#wake = wake;
|
|
602
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function createInitialState(input, manifest) {
|
|
607
|
+
return Object.freeze({
|
|
608
|
+
schema_version: 1,
|
|
609
|
+
binding_id: input.bindingId,
|
|
610
|
+
binding_generation: input.bindingGeneration,
|
|
611
|
+
policy_digest: input.policy.digest,
|
|
612
|
+
sync_id: input.syncId,
|
|
613
|
+
generation: input.initialGeneration,
|
|
614
|
+
manifest_root: input.initialManifestRoot,
|
|
615
|
+
cursor: null,
|
|
616
|
+
dirty: false,
|
|
617
|
+
status: "recovering",
|
|
618
|
+
reason: null,
|
|
619
|
+
baseline: projectManifest(manifest),
|
|
620
|
+
pending_local: Object.freeze([]),
|
|
621
|
+
pending_remote: null,
|
|
622
|
+
updated_at: new Date(input.clock?.() ?? Date.now()).toISOString(),
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
function projectManifest(manifest) {
|
|
626
|
+
return Object.freeze(manifest.entries.map((entry) => Object.freeze({
|
|
627
|
+
path: entry.path,
|
|
628
|
+
kind: entry.kind,
|
|
629
|
+
fingerprint: entryFingerprint(entry),
|
|
630
|
+
byteLength: entry.byteLength,
|
|
631
|
+
})));
|
|
632
|
+
}
|
|
633
|
+
function diffManifest(baselineEntries, manifest, baseGeneration) {
|
|
634
|
+
const baseline = new Map(baselineEntries.map((entry) => [entry.path, entry]));
|
|
635
|
+
const current = new Map(projectManifest(manifest).map((entry) => [entry.path, entry]));
|
|
636
|
+
const paths = [...new Set([...baseline.keys(), ...current.keys()])].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
|
|
637
|
+
return Object.freeze(paths.flatMap((path) => {
|
|
638
|
+
const before = baseline.get(path);
|
|
639
|
+
const after = current.get(path);
|
|
640
|
+
if (sameProjection(before, after))
|
|
641
|
+
return [];
|
|
642
|
+
const kind = before === undefined ? "create" : after === undefined ? "delete" : "update";
|
|
643
|
+
const fingerprint = after?.fingerprint ?? ZERO_DIGEST;
|
|
644
|
+
return [Object.freeze({
|
|
645
|
+
operationId: randomUUID(),
|
|
646
|
+
path,
|
|
647
|
+
kind,
|
|
648
|
+
baseGeneration,
|
|
649
|
+
fingerprint,
|
|
650
|
+
byteLength: after?.byteLength ?? 0,
|
|
651
|
+
})];
|
|
652
|
+
}));
|
|
653
|
+
}
|
|
654
|
+
function entryFingerprint(entry) {
|
|
655
|
+
return createHash("sha256").update(JSON.stringify({
|
|
656
|
+
path: entry.path,
|
|
657
|
+
kind: entry.kind,
|
|
658
|
+
byte_length: entry.kind === "symlink" ? 0 : entry.byteLength,
|
|
659
|
+
executable: entry.executable,
|
|
660
|
+
chunks: entry.chunks?.map((chunk) => ({ digest: chunk.digest, byte_length: chunk.byteLength })) ?? [],
|
|
661
|
+
link_target: entry.linkTarget ?? null,
|
|
662
|
+
})).digest("hex");
|
|
663
|
+
}
|
|
664
|
+
function sameProjection(left, right) {
|
|
665
|
+
return left === undefined ? right === undefined : right !== undefined && left.fingerprint === right.fingerprint && left.kind === right.kind;
|
|
666
|
+
}
|
|
667
|
+
function orderRemoteItems(items) {
|
|
668
|
+
const revisions = items.filter((item) => item.operation === "revision");
|
|
669
|
+
const directoryCreates = items.filter((item) => item.operation === "upsert" && item.entry?.kind === "directory")
|
|
670
|
+
.sort((left, right) => depth(left.path) - depth(right.path));
|
|
671
|
+
const upserts = items.filter((item) => item.operation === "upsert" && item.entry?.kind !== "directory");
|
|
672
|
+
const deletes = items.filter((item) => item.operation === "delete")
|
|
673
|
+
.sort((left, right) => depth(right.path) - depth(left.path));
|
|
674
|
+
return Object.freeze([...revisions, ...directoryCreates, ...upserts, ...deletes]);
|
|
675
|
+
}
|
|
676
|
+
function depth(path) {
|
|
677
|
+
return path?.split("/").length ?? 0;
|
|
678
|
+
}
|
|
679
|
+
function commonRemoteField(items, field) {
|
|
680
|
+
const first = items[0]?.[field];
|
|
681
|
+
if (first === undefined || items.some((item) => item[field] !== first)) {
|
|
682
|
+
throw syncFailure("remote_generation_inconsistent", EXIT_CODES.remote);
|
|
683
|
+
}
|
|
684
|
+
return first;
|
|
685
|
+
}
|
|
686
|
+
async function atomicReplaceFile(root, path, chunks, executable, requireMissing = false) {
|
|
687
|
+
await ensureSafeDirectory(root, dirname(path));
|
|
688
|
+
await assertSafeAncestors(root, dirname(path));
|
|
689
|
+
if (requireMissing && await pathExists(path))
|
|
690
|
+
throw syncFailure("conflict_retention_collision", EXIT_CODES.conflict);
|
|
691
|
+
await assertReplaceTarget(path);
|
|
692
|
+
const temporary = join(dirname(path), `.${basename(path)}.cuna-apply-${randomUUID()}.tmp`);
|
|
693
|
+
const handle = await open(temporary, fileConstants.O_CREAT | fileConstants.O_EXCL | fileConstants.O_WRONLY | noFollowFlag(), 0o600);
|
|
694
|
+
try {
|
|
695
|
+
for (const bytes of chunks)
|
|
696
|
+
await handle.write(bytes);
|
|
697
|
+
await handle.sync();
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
await handle.close().catch(() => undefined);
|
|
701
|
+
await unlink(temporary).catch(() => undefined);
|
|
702
|
+
throw error;
|
|
703
|
+
}
|
|
704
|
+
await handle.close();
|
|
705
|
+
try {
|
|
706
|
+
await chmod(temporary, executable ? 0o700 : 0o600);
|
|
707
|
+
await assertSafeAncestors(root, dirname(path));
|
|
708
|
+
await assertReplaceTarget(path);
|
|
709
|
+
await rename(temporary, path);
|
|
710
|
+
await syncDirectory(dirname(path));
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
await unlink(temporary).catch(() => undefined);
|
|
714
|
+
throw error;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async function safeDelete(root, path) {
|
|
718
|
+
await assertSafeAncestors(root, dirname(path));
|
|
719
|
+
try {
|
|
720
|
+
const metadata = await lstat(path);
|
|
721
|
+
if (metadata.isSymbolicLink() || (!metadata.isFile() && !metadata.isDirectory()) || (metadata.isFile() && metadata.nlink !== 1)) {
|
|
722
|
+
throw syncFailure("delete_target_untrusted", EXIT_CODES.policy);
|
|
723
|
+
}
|
|
724
|
+
if (metadata.isDirectory())
|
|
725
|
+
await rmdir(path);
|
|
726
|
+
else
|
|
727
|
+
await unlink(path);
|
|
728
|
+
await syncDirectory(dirname(path));
|
|
729
|
+
}
|
|
730
|
+
catch (error) {
|
|
731
|
+
if (error.code !== "ENOENT")
|
|
732
|
+
throw error;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
async function ensureSafeDirectory(root, requested) {
|
|
736
|
+
const target = assertLexicallyInsideRoot(root, requested);
|
|
737
|
+
const difference = relative(root, target);
|
|
738
|
+
let current = root;
|
|
739
|
+
for (const component of difference.split(sep).filter(Boolean)) {
|
|
740
|
+
current = join(current, component);
|
|
741
|
+
try {
|
|
742
|
+
const metadata = await lstat(current);
|
|
743
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink())
|
|
744
|
+
throw syncFailure("ancestor_untrusted", EXIT_CODES.policy);
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
if (error.code !== "ENOENT")
|
|
748
|
+
throw error;
|
|
749
|
+
await mkdir(current, { mode: 0o700 });
|
|
750
|
+
const metadata = await lstat(current);
|
|
751
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink())
|
|
752
|
+
throw syncFailure("ancestor_untrusted", EXIT_CODES.policy);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
await assertSafeAncestors(root, target);
|
|
756
|
+
}
|
|
757
|
+
async function assertSafeAncestors(root, directory) {
|
|
758
|
+
const target = assertLexicallyInsideRoot(root, directory);
|
|
759
|
+
const canonical = await realpath(target);
|
|
760
|
+
const difference = relative(root, canonical);
|
|
761
|
+
if (difference === ".." || difference.startsWith(`..${sep}`) || isAbsolute(difference)) {
|
|
762
|
+
throw syncFailure("ancestor_escape", EXIT_CODES.policy);
|
|
763
|
+
}
|
|
764
|
+
let current = root;
|
|
765
|
+
for (const component of relative(root, target).split(sep).filter(Boolean)) {
|
|
766
|
+
current = join(current, component);
|
|
767
|
+
const metadata = await lstat(current);
|
|
768
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink())
|
|
769
|
+
throw syncFailure("ancestor_untrusted", EXIT_CODES.policy);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async function assertReplaceTarget(path) {
|
|
773
|
+
try {
|
|
774
|
+
const metadata = await lstat(path);
|
|
775
|
+
if (metadata.isSymbolicLink() || !metadata.isFile() || metadata.nlink !== 1) {
|
|
776
|
+
throw syncFailure("replace_target_untrusted", EXIT_CODES.policy);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
catch (error) {
|
|
780
|
+
if (error.code !== "ENOENT")
|
|
781
|
+
throw error;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
async function syncDirectory(directory) {
|
|
785
|
+
try {
|
|
786
|
+
const handle = await open(directory, fileConstants.O_RDONLY);
|
|
787
|
+
try {
|
|
788
|
+
await handle.sync();
|
|
789
|
+
}
|
|
790
|
+
finally {
|
|
791
|
+
await handle.close();
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
if (process.platform !== "win32")
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async function canonicalPlainDirectory(path) {
|
|
800
|
+
if (!isAbsolute(path) || path.includes("\0"))
|
|
801
|
+
throw syncFailure("root_untrusted", EXIT_CODES.policy);
|
|
802
|
+
const canonical = await realpath(path);
|
|
803
|
+
const metadata = await lstat(canonical);
|
|
804
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink())
|
|
805
|
+
throw syncFailure("root_untrusted", EXIT_CODES.policy);
|
|
806
|
+
return canonical;
|
|
807
|
+
}
|
|
808
|
+
async function preparePrivateDirectory(path, workspaceRoot) {
|
|
809
|
+
if (!isAbsolute(path) || path.includes("\0"))
|
|
810
|
+
throw syncFailure("state_directory_untrusted", EXIT_CODES.policy);
|
|
811
|
+
const requested = resolve(path);
|
|
812
|
+
if (isSameOrInside(workspaceRoot, requested))
|
|
813
|
+
throw syncFailure("state_inside_workspace", EXIT_CODES.policy);
|
|
814
|
+
await mkdir(requested, { recursive: true, mode: 0o700 });
|
|
815
|
+
const canonical = await realpath(requested);
|
|
816
|
+
const metadata = await lstat(canonical);
|
|
817
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink() || isSameOrInside(workspaceRoot, canonical)) {
|
|
818
|
+
throw syncFailure("state_directory_untrusted", EXIT_CODES.policy);
|
|
819
|
+
}
|
|
820
|
+
await chmod(canonical, 0o700).catch((error) => {
|
|
821
|
+
if (process.platform !== "win32")
|
|
822
|
+
throw error;
|
|
823
|
+
});
|
|
824
|
+
return canonical;
|
|
825
|
+
}
|
|
826
|
+
async function loadState(path) {
|
|
827
|
+
try {
|
|
828
|
+
const metadata = await lstat(path);
|
|
829
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 64 * 1024 * 1024) {
|
|
830
|
+
throw syncFailure("state_file_untrusted", EXIT_CODES.policy);
|
|
831
|
+
}
|
|
832
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
if (error.code === "ENOENT")
|
|
836
|
+
return undefined;
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
async function atomicWriteState(path, state) {
|
|
841
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
842
|
+
try {
|
|
843
|
+
await writeFile(temporary, `${JSON.stringify(state)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
844
|
+
// Windows rejects FlushFileBuffers on a read-only handle. Open the private
|
|
845
|
+
// temporary read/write solely for durability; no bytes are changed here.
|
|
846
|
+
const handle = await open(temporary, fileConstants.O_RDWR | noFollowFlag());
|
|
847
|
+
try {
|
|
848
|
+
await handle.sync();
|
|
849
|
+
}
|
|
850
|
+
finally {
|
|
851
|
+
await handle.close();
|
|
852
|
+
}
|
|
853
|
+
await rename(temporary, path);
|
|
854
|
+
await chmod(path, 0o600).catch((error) => { if (process.platform !== "win32")
|
|
855
|
+
throw error; });
|
|
856
|
+
await syncDirectory(dirname(path));
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
await unlink(temporary).catch(() => undefined);
|
|
860
|
+
throw error;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
function admitState(value, input) {
|
|
864
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
865
|
+
throw syncFailure("state_invalid", EXIT_CODES.conflict);
|
|
866
|
+
const source = value;
|
|
867
|
+
const keys = [
|
|
868
|
+
"baseline", "binding_generation", "binding_id", "cursor", "dirty", "generation",
|
|
869
|
+
"manifest_root", "pending_local", "pending_remote", "policy_digest", "reason",
|
|
870
|
+
"schema_version", "status", "sync_id", "updated_at",
|
|
871
|
+
];
|
|
872
|
+
if (Object.keys(source).sort().join("\0") !== keys.sort().join("\0") || source.schema_version !== STATE_SCHEMA) {
|
|
873
|
+
throw syncFailure("state_schema_incompatible", EXIT_CODES.conflict);
|
|
874
|
+
}
|
|
875
|
+
if (source.binding_id !== input.bindingId ||
|
|
876
|
+
source.binding_generation !== input.bindingGeneration ||
|
|
877
|
+
source.policy_digest !== input.policy.digest)
|
|
878
|
+
throw syncFailure("state_identity_mismatch", EXIT_CODES.policy);
|
|
879
|
+
const statusValues = new Set(["recovering", "reconciling", "catching_up", "live_unverified", "converged", "conflicted", "paused", "recovery_required", "unknown"]);
|
|
880
|
+
if (!isUuid(source.sync_id) ||
|
|
881
|
+
!Number.isSafeInteger(source.generation) || source.generation < 0 ||
|
|
882
|
+
!isDigest(source.manifest_root) ||
|
|
883
|
+
(source.cursor !== null && (typeof source.cursor !== "string" || source.cursor.length < 1 || source.cursor.length > 1_024)) ||
|
|
884
|
+
typeof source.dirty !== "boolean" ||
|
|
885
|
+
typeof source.status !== "string" || !statusValues.has(source.status) ||
|
|
886
|
+
(source.reason !== null && (typeof source.reason !== "string" || source.reason.length < 1 || source.reason.length > 128)) ||
|
|
887
|
+
typeof source.updated_at !== "string" || !Number.isFinite(Date.parse(source.updated_at)) ||
|
|
888
|
+
!Array.isArray(source.baseline) || !Array.isArray(source.pending_local))
|
|
889
|
+
throw syncFailure("state_invalid", EXIT_CODES.conflict);
|
|
890
|
+
const baseline = Object.freeze(source.baseline.map(decodeEntryProjection));
|
|
891
|
+
const pendingLocal = Object.freeze(source.pending_local.map(decodePendingLocal));
|
|
892
|
+
const pendingRemote = source.pending_remote === null ? null : decodePendingRemote(source.pending_remote);
|
|
893
|
+
return Object.freeze({
|
|
894
|
+
schema_version: 1,
|
|
895
|
+
binding_id: input.bindingId,
|
|
896
|
+
binding_generation: input.bindingGeneration,
|
|
897
|
+
policy_digest: input.policy.digest,
|
|
898
|
+
sync_id: source.sync_id,
|
|
899
|
+
generation: source.generation,
|
|
900
|
+
manifest_root: source.manifest_root,
|
|
901
|
+
cursor: source.cursor,
|
|
902
|
+
dirty: source.dirty,
|
|
903
|
+
status: source.status,
|
|
904
|
+
reason: source.reason,
|
|
905
|
+
baseline,
|
|
906
|
+
pending_local: pendingLocal,
|
|
907
|
+
pending_remote: pendingRemote,
|
|
908
|
+
updated_at: source.updated_at,
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
function decodeEntryProjection(value) {
|
|
912
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
913
|
+
throw syncFailure("state_invalid", EXIT_CODES.conflict);
|
|
914
|
+
const source = value;
|
|
915
|
+
if (Object.keys(source).sort().join("\0") !== ["byteLength", "fingerprint", "kind", "path"].join("\0") ||
|
|
916
|
+
typeof source.path !== "string" || !isDigest(source.fingerprint) ||
|
|
917
|
+
!["directory", "file", "symlink"].includes(source.kind) ||
|
|
918
|
+
!Number.isSafeInteger(source.byteLength) || source.byteLength < 0)
|
|
919
|
+
throw syncFailure("state_invalid", EXIT_CODES.conflict);
|
|
920
|
+
return Object.freeze(source);
|
|
921
|
+
}
|
|
922
|
+
function decodePendingLocal(value) {
|
|
923
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
924
|
+
throw syncFailure("state_invalid", EXIT_CODES.conflict);
|
|
925
|
+
const source = value;
|
|
926
|
+
const expected = ["baseGeneration", "byteLength", "fingerprint", "kind", "operationId", "path"];
|
|
927
|
+
if (Object.keys(source).sort().join("\0") !== expected.join("\0") || !isUuid(source.operationId) ||
|
|
928
|
+
typeof source.path !== "string" || !["create", "update", "delete"].includes(source.kind) ||
|
|
929
|
+
!Number.isSafeInteger(source.baseGeneration) || source.baseGeneration < 0 ||
|
|
930
|
+
!Number.isSafeInteger(source.byteLength) || source.byteLength < 0 || !isDigest(source.fingerprint))
|
|
931
|
+
throw syncFailure("state_invalid", EXIT_CODES.conflict);
|
|
932
|
+
return Object.freeze(source);
|
|
933
|
+
}
|
|
934
|
+
function decodePendingRemote(value) {
|
|
935
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
936
|
+
throw syncFailure("state_invalid", EXIT_CODES.conflict);
|
|
937
|
+
const source = value;
|
|
938
|
+
const expected = ["cursor", "generation", "items", "manifestRoot", "nextIndex"];
|
|
939
|
+
if (Object.keys(source).sort().join("\0") !== expected.join("\0") ||
|
|
940
|
+
!Number.isSafeInteger(source.generation) || source.generation < 1 || !isDigest(source.manifestRoot) ||
|
|
941
|
+
(source.cursor !== null && (typeof source.cursor !== "string" || source.cursor.length < 1 || source.cursor.length > 1_024)) ||
|
|
942
|
+
!Array.isArray(source.items) || !Number.isSafeInteger(source.nextIndex) || source.nextIndex < 0 ||
|
|
943
|
+
source.nextIndex > source.items.length)
|
|
944
|
+
throw syncFailure("state_invalid", EXIT_CODES.conflict);
|
|
945
|
+
const decodedItems = decodeChangePage({
|
|
946
|
+
selected_protocol: 1,
|
|
947
|
+
items: source.items,
|
|
948
|
+
next_cursor: null,
|
|
949
|
+
}).items;
|
|
950
|
+
return Object.freeze({
|
|
951
|
+
generation: source.generation,
|
|
952
|
+
manifestRoot: source.manifestRoot,
|
|
953
|
+
cursor: source.cursor,
|
|
954
|
+
items: decodedItems,
|
|
955
|
+
nextIndex: source.nextIndex,
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
async function createNodeWorkspaceWatcher(input) {
|
|
959
|
+
let watcher;
|
|
960
|
+
try {
|
|
961
|
+
watcher = watch(input.root, { recursive: true, persistent: false }, (_event, filename) => {
|
|
962
|
+
if (filename === null)
|
|
963
|
+
input.onEvent({ kind: "overflow" });
|
|
964
|
+
else
|
|
965
|
+
input.onEvent({ kind: "change", path: filename.toString() });
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
catch (error) {
|
|
969
|
+
throw syncFailure("watcher_unavailable", EXIT_CODES.unsupported, error);
|
|
970
|
+
}
|
|
971
|
+
watcher.on("error", input.onError);
|
|
972
|
+
return Object.freeze({ close: () => watcher.close() });
|
|
973
|
+
}
|
|
974
|
+
function validateInput(input) {
|
|
975
|
+
if (!isUuid(input.bindingId) || !isUuid(input.syncId) ||
|
|
976
|
+
!Number.isSafeInteger(input.bindingGeneration) || input.bindingGeneration < 1 ||
|
|
977
|
+
!Number.isSafeInteger(input.initialGeneration) || input.initialGeneration < 1 ||
|
|
978
|
+
!isDigest(input.initialManifestRoot) || !isDigest(input.policy.digest))
|
|
979
|
+
throw syncFailure("input_invalid", EXIT_CODES.usage);
|
|
980
|
+
for (const value of [
|
|
981
|
+
input.debounceMs ?? DEFAULT_DEBOUNCE_MS,
|
|
982
|
+
input.reconciliationIntervalMs ?? DEFAULT_RECONCILE_MS,
|
|
983
|
+
input.remotePollIntervalMs ?? DEFAULT_REMOTE_POLL_MS,
|
|
984
|
+
input.maximumPendingOperations ?? DEFAULT_OPERATION_LIMIT,
|
|
985
|
+
input.maximumPendingBytes ?? DEFAULT_BYTE_LIMIT,
|
|
986
|
+
]) {
|
|
987
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
988
|
+
throw syncFailure("limit_invalid", EXIT_CODES.usage);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function latestJournalStates(records) {
|
|
992
|
+
const result = new Map();
|
|
993
|
+
for (const record of records)
|
|
994
|
+
result.set(record.operationId, record.state);
|
|
995
|
+
return result;
|
|
996
|
+
}
|
|
997
|
+
function samePendingIntent(durable, observed) {
|
|
998
|
+
if (durable.length !== observed.length)
|
|
999
|
+
return false;
|
|
1000
|
+
return durable.every((operation, index) => {
|
|
1001
|
+
const candidate = observed[index];
|
|
1002
|
+
return candidate !== undefined &&
|
|
1003
|
+
operation.path === candidate.path &&
|
|
1004
|
+
operation.kind === candidate.kind &&
|
|
1005
|
+
operation.baseGeneration === candidate.baseGeneration &&
|
|
1006
|
+
operation.fingerprint === candidate.fingerprint &&
|
|
1007
|
+
operation.byteLength === candidate.byteLength;
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
async function transitionIfQueued(journal, operationId) {
|
|
1011
|
+
const latest = [...(journal?.records ?? [])].reverse().find((record) => record.operationId === operationId);
|
|
1012
|
+
if (latest?.state === "queued")
|
|
1013
|
+
await journal?.transition(operationId, "sending");
|
|
1014
|
+
else if (latest?.state !== "sending" && latest?.state !== "uncertain") {
|
|
1015
|
+
throw syncFailure("journal_operation_state_invalid", EXIT_CODES.conflict);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
async function transitionToApplied(journal, operationId) {
|
|
1019
|
+
let latest = [...(journal?.records ?? [])].reverse().find((record) => record.operationId === operationId)?.state;
|
|
1020
|
+
if (latest === "queued") {
|
|
1021
|
+
await journal?.transition(operationId, "sending");
|
|
1022
|
+
latest = "sending";
|
|
1023
|
+
}
|
|
1024
|
+
if (latest === "sending") {
|
|
1025
|
+
await journal?.transition(operationId, "uncertain");
|
|
1026
|
+
latest = "uncertain";
|
|
1027
|
+
}
|
|
1028
|
+
if (latest === "uncertain") {
|
|
1029
|
+
await journal?.transition(operationId, "acknowledged");
|
|
1030
|
+
latest = "acknowledged";
|
|
1031
|
+
}
|
|
1032
|
+
if (latest === "acknowledged")
|
|
1033
|
+
await journal?.transition(operationId, "applied");
|
|
1034
|
+
}
|
|
1035
|
+
async function transitionIfPossible(journal, operationId, state) {
|
|
1036
|
+
try {
|
|
1037
|
+
await journal?.transition(operationId, state);
|
|
1038
|
+
}
|
|
1039
|
+
catch { /* preserve the original ambiguous failure */ }
|
|
1040
|
+
}
|
|
1041
|
+
function classifyFailure(error) {
|
|
1042
|
+
if (error instanceof CunaError) {
|
|
1043
|
+
const reason = typeof error.details?.reason === "string" ? error.details.reason : error.code;
|
|
1044
|
+
if (error.code === "cuna.workspace_sync.contract_mismatch" ||
|
|
1045
|
+
error.code === "cuna.workspace.path_invalid" ||
|
|
1046
|
+
error.code === "cuna.workspace.path_escape")
|
|
1047
|
+
return { status: "recovery_required", reason };
|
|
1048
|
+
if (error.exitCode === EXIT_CODES.conflict)
|
|
1049
|
+
return { status: "conflicted", reason };
|
|
1050
|
+
if (error.exitCode === EXIT_CODES.policy || error.exitCode === EXIT_CODES.usage || error.exitCode === EXIT_CODES.unsupported) {
|
|
1051
|
+
return { status: "recovery_required", reason };
|
|
1052
|
+
}
|
|
1053
|
+
return { status: "paused", reason };
|
|
1054
|
+
}
|
|
1055
|
+
const code = error.code;
|
|
1056
|
+
if (code === "ENOSPC" || code === "EDQUOT")
|
|
1057
|
+
return { status: "paused", reason: "disk_exhausted" };
|
|
1058
|
+
return { status: "paused", reason: "dependency_unavailable" };
|
|
1059
|
+
}
|
|
1060
|
+
function isAbort(error) {
|
|
1061
|
+
return error instanceof CunaError && error.details?.reason === "cancelled";
|
|
1062
|
+
}
|
|
1063
|
+
function isSameOrInside(root, candidate) {
|
|
1064
|
+
const difference = relative(resolve(root), resolve(candidate));
|
|
1065
|
+
return difference === "" || (!difference.startsWith(`..${sep}`) && difference !== ".." && !isAbsolute(difference));
|
|
1066
|
+
}
|
|
1067
|
+
async function pathExists(path) {
|
|
1068
|
+
try {
|
|
1069
|
+
await lstat(path);
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
if (error.code === "ENOENT")
|
|
1074
|
+
return false;
|
|
1075
|
+
throw error;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function noFollowFlag() {
|
|
1079
|
+
return typeof fileConstants.O_NOFOLLOW === "number" ? fileConstants.O_NOFOLLOW : 0;
|
|
1080
|
+
}
|
|
1081
|
+
function isUuid(value) {
|
|
1082
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(value);
|
|
1083
|
+
}
|
|
1084
|
+
function isDigest(value) {
|
|
1085
|
+
return typeof value === "string" && /^[0-9a-f]{64}$/u.test(value);
|
|
1086
|
+
}
|
|
1087
|
+
function sha256(value) {
|
|
1088
|
+
return createHash("sha256").update(value).digest("hex");
|
|
1089
|
+
}
|
|
1090
|
+
function syncFailure(reason, exitCode, cause) {
|
|
1091
|
+
return new CunaError({
|
|
1092
|
+
code: "cuna.workspace_sync.continuous_failed",
|
|
1093
|
+
message: "Continuous workspace synchronization could not preserve its safety contract.",
|
|
1094
|
+
exitCode: exitCode,
|
|
1095
|
+
details: { reason },
|
|
1096
|
+
...(cause === undefined ? {} : { cause }),
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
async function abortableSleep(milliseconds, signal) {
|
|
1100
|
+
if (signal.aborted)
|
|
1101
|
+
throw signal.reason;
|
|
1102
|
+
await new Promise((resolveSleep, rejectSleep) => {
|
|
1103
|
+
let timer;
|
|
1104
|
+
const abort = () => finish(signal.reason);
|
|
1105
|
+
const finish = (error) => {
|
|
1106
|
+
clearTimeout(timer);
|
|
1107
|
+
signal.removeEventListener("abort", abort);
|
|
1108
|
+
if (error === undefined)
|
|
1109
|
+
resolveSleep();
|
|
1110
|
+
else
|
|
1111
|
+
rejectSleep(error);
|
|
1112
|
+
};
|
|
1113
|
+
timer = setTimeout(finish, milliseconds);
|
|
1114
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
//# sourceMappingURL=continuous-sync-supervisor.js.map
|