@runeditrun/rer 0.0.0-stage → 0.1.1
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 +21 -0
- package/README.md +157 -2
- package/bin/rer.mjs +3 -0
- package/cli/README.md +243 -0
- package/cli/adapter.mjs +10 -0
- package/cli/ai-local.mjs +65 -0
- package/cli/archive-path.mjs +52 -0
- package/cli/archive-vite.mjs +55 -0
- package/cli/archive.mjs +8 -0
- package/cli/auth-copy.mjs +80 -0
- package/cli/bounded.mjs +70 -0
- package/cli/cache-doctor.mjs +63 -0
- package/cli/container-transfer.mjs +35 -0
- package/cli/container.mjs +62 -0
- package/cli/contract.mjs +25 -0
- package/cli/dependencies.mjs +45 -0
- package/cli/dependency-approval.mjs +33 -0
- package/cli/dependency-doctor.mjs +34 -0
- package/cli/dependency-input.mjs +43 -0
- package/cli/deploy-config.mjs +118 -0
- package/cli/deploy.mjs +37 -0
- package/cli/dev-identity.mjs +80 -0
- package/cli/errors.mjs +14 -0
- package/cli/export.mjs +42 -0
- package/cli/font-cache.mjs +29 -0
- package/cli/hosted-output.mjs +110 -0
- package/cli/independent.mjs +29 -0
- package/cli/inspect.mjs +174 -0
- package/cli/jsonc.mjs +111 -0
- package/cli/lifecycle.mjs +148 -0
- package/cli/local.mjs +100 -0
- package/cli/main.mjs +57 -0
- package/cli/managed-server.mjs +143 -0
- package/cli/node-runtime-cache.mjs +68 -0
- package/cli/node-runtime-discovery.mjs +56 -0
- package/cli/node-runtime-download.mjs +59 -0
- package/cli/node-runtime.mjs +50 -0
- package/cli/output.mjs +22 -0
- package/cli/parse.mjs +77 -0
- package/cli/port.mjs +52 -0
- package/cli/portability-worker.mjs +56 -0
- package/cli/portability.mjs +66 -0
- package/cli/preview-cache.mjs +99 -0
- package/cli/project.mjs +48 -0
- package/cli/release.mjs +24 -0
- package/cli/runtime-front-door.mjs +46 -0
- package/cli/scheduled.mjs +81 -0
- package/cli/server-activity.mjs +12 -0
- package/cli/server-proxy.mjs +93 -0
- package/cli/server-registry.mjs +182 -0
- package/cli/server-resources.mjs +22 -0
- package/cli/settings.mjs +15 -0
- package/cli/setup-answers.mjs +29 -0
- package/cli/setup-input.mjs +113 -0
- package/cli/shared.mjs +7 -0
- package/cli/transfers.mjs +33 -0
- package/cli/version.mjs +36 -0
- package/composition/git-source.mjs +111 -0
- package/edition-authoring/files.mjs +81 -0
- package/edition-authoring/manifest.mjs +65 -0
- package/global/README.md +158 -0
- package/global/checkout.mjs +72 -0
- package/global/cli/command.mjs +13 -0
- package/global/cli/install.mjs +75 -0
- package/global/cli/layout.mjs +45 -0
- package/global/cli/status.mjs +54 -0
- package/global/cli/uninstall.mjs +36 -0
- package/global/delegate.mjs +13 -0
- package/global/launch/capacity.mjs +72 -0
- package/global/launch/dev.mjs +117 -0
- package/global/launch/facts.mjs +94 -0
- package/global/launch/failure.mjs +5 -0
- package/global/launch/local-defaults.mjs +4 -0
- package/global/launch/prepare.mjs +63 -0
- package/global/launch/render.mjs +44 -0
- package/global/launch/state.mjs +106 -0
- package/global/launch/steps.mjs +90 -0
- package/global/launch/stop.mjs +89 -0
- package/global/launch/worker.mjs +62 -0
- package/global/list.mjs +59 -0
- package/global/main.mjs +53 -0
- package/global/package-info.mjs +38 -0
- package/global/registry.mjs +24 -0
- package/global/rer.mjs +29 -0
- package/global/update/archive.mjs +89 -0
- package/global/update/command.mjs +90 -0
- package/global/update/latest.mjs +6 -0
- package/global/update/notice.mjs +58 -0
- package/global/update/registry.mjs +47 -0
- package/global/version.mjs +29 -0
- package/install.sh +144 -0
- package/modules/ai/LICENSE +21 -0
- package/modules/ai/README.md +90 -0
- package/modules/ai/client.mjs +71 -0
- package/modules/ai/config.mjs +39 -0
- package/modules/ai/dev/index.d.mts +6 -0
- package/modules/ai/dev/index.mjs +64 -0
- package/modules/ai/dev/outbox.mjs +33 -0
- package/modules/ai/errors.mjs +28 -0
- package/modules/ai/fake/index.d.mts +3 -0
- package/modules/ai/fake/index.mjs +82 -0
- package/modules/ai/index.d.mts +10 -0
- package/modules/ai/index.mjs +3 -0
- package/modules/ai/package.json +6 -0
- package/modules/container/README.md +156 -0
- package/modules/container/adapters/d1.mjs +34 -0
- package/modules/container/adapters/egress.mjs +40 -0
- package/modules/container/adapters/jobs.mjs +47 -0
- package/modules/container/adapters/objects.mjs +84 -0
- package/modules/container/adapters/retained.mjs +14 -0
- package/modules/container/adapters/sqlite-error.mjs +7 -0
- package/modules/container/adapters/sqlite-worker.mjs +26 -0
- package/modules/container/assets.mjs +14 -0
- package/modules/container/bindings.mjs +1 -0
- package/modules/container/boundary.mjs +38 -0
- package/modules/container/build.mjs +65 -0
- package/modules/container/deploy-input.mjs +17 -0
- package/modules/container/entrypoint.sh +5 -0
- package/modules/container/host.mjs +43 -0
- package/modules/container/ingress.mjs +23 -0
- package/modules/container/lock.mjs +11 -0
- package/modules/container/migrations.mjs +16 -0
- package/modules/container/node-engine.mjs +39 -0
- package/modules/container/portability-schema.mjs +14 -0
- package/modules/container/remote/deploy.py +76 -0
- package/modules/container/remote/files.py +275 -0
- package/modules/container/remote/resources.py +157 -0
- package/modules/container/runner.mjs +10 -0
- package/modules/container/ssh-plan.mjs +44 -0
- package/modules/container/ssh.mjs +63 -0
- package/modules/container/transfer.mjs +27 -0
- package/modules/container/worker-engine.mjs +32 -0
- package/modules/dependencies/README.md +211 -0
- package/modules/dependencies/approval.mjs +51 -0
- package/modules/dependencies/auth.mjs +40 -0
- package/modules/dependencies/authority.mjs +14 -0
- package/modules/dependencies/crypto.mjs +13 -0
- package/modules/dependencies/index.d.mts +23 -0
- package/modules/dependencies/index.mjs +113 -0
- package/modules/dependencies/logout.mjs +42 -0
- package/modules/dependencies/native.mjs +9 -0
- package/modules/dependencies/protocol.mjs +23 -0
- package/modules/dependencies/schema/approval-attempt.sql +3 -0
- package/modules/dependencies/schema/dependencies.sql +15 -0
- package/modules/dependencies/store.mjs +19 -0
- package/modules/deploy-kit/README.md +308 -0
- package/modules/deploy-kit/artifact.mjs +47 -0
- package/modules/deploy-kit/build-modules.mjs +21 -0
- package/modules/deploy-kit/check.mjs +13 -0
- package/modules/deploy-kit/cli.mjs +50 -0
- package/modules/deploy-kit/container-config.mjs +8 -0
- package/modules/deploy-kit/default-stage.mjs +14 -0
- package/modules/deploy-kit/deploy.mjs +80 -0
- package/modules/deploy-kit/health-request.mjs +86 -0
- package/modules/deploy-kit/health.mjs +99 -0
- package/modules/deploy-kit/index.mjs +12 -0
- package/modules/deploy-kit/local-only.mjs +10 -0
- package/modules/deploy-kit/migrations.mjs +48 -0
- package/modules/deploy-kit/owner-cli.mjs +63 -0
- package/modules/deploy-kit/owner.mjs +297 -0
- package/modules/deploy-kit/prepare.mjs +62 -0
- package/modules/deploy-kit/provider.mjs +99 -0
- package/modules/deploy-kit/queue-consumer.mjs +10 -0
- package/modules/deploy-kit/receipt.mjs +16 -0
- package/modules/deploy-kit/render.mjs +47 -0
- package/modules/deploy-kit/resources.mjs +106 -0
- package/modules/deploy-kit/service-grants.mjs +25 -0
- package/modules/deploy-kit/sql-safety.mjs +94 -0
- package/modules/deploy-kit/sql-tokens.mjs +49 -0
- package/modules/deploy-kit/teardown.mjs +100 -0
- package/modules/deploy-kit/templates.mjs +31 -0
- package/modules/deploy-kit/validation.mjs +58 -0
- package/modules/deploy-kit/worker.mjs +105 -0
- package/modules/erasure/CONTRACT.md +90 -0
- package/modules/erasure/README.md +221 -0
- package/modules/erasure/audit.mjs +23 -0
- package/modules/erasure/blobs.mjs +45 -0
- package/modules/erasure/cascade.mjs +91 -0
- package/modules/erasure/completion.mjs +62 -0
- package/modules/erasure/declaration.mjs +51 -0
- package/modules/erasure/fence.mjs +19 -0
- package/modules/erasure/index.d.mts +62 -0
- package/modules/erasure/index.mjs +85 -0
- package/modules/erasure/order.mjs +56 -0
- package/modules/erasure/plan.mjs +121 -0
- package/modules/erasure/rate.mjs +57 -0
- package/modules/erasure/receipt.mjs +32 -0
- package/modules/erasure/root.mjs +21 -0
- package/modules/erasure/schema/erasure-rates.sql +5 -0
- package/modules/erasure/schema/erasure.sql +27 -0
- package/modules/erasure/scope.mjs +29 -0
- package/modules/erasure/sql.mjs +23 -0
- package/modules/erasure/verification.mjs +69 -0
- package/modules/integrations/core/CONTRACT.md +50 -0
- package/modules/integrations/core/README.md +27 -0
- package/modules/integrations/core/crypto.ts +19 -0
- package/modules/integrations/core/database.ts +21 -0
- package/modules/integrations/core/delivery.ts +97 -0
- package/modules/integrations/core/destination.ts +13 -0
- package/modules/integrations/core/index.ts +7 -0
- package/modules/integrations/core/node-transport.ts +72 -0
- package/modules/integrations/core/public-address.ts +57 -0
- package/modules/integrations/core/repository.ts +74 -0
- package/modules/integrations/core/schema.sql +36 -0
- package/modules/integrations/core/sqlite.ts +20 -0
- package/modules/integrations/core/timestamped-body-signature.ts +38 -0
- package/modules/integrations/core/tokens.ts +71 -0
- package/modules/integrations/core/types.ts +52 -0
- package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
- package/modules/integrations/core/webhook.ts +73 -0
- package/modules/job-queue/README.md +173 -0
- package/modules/job-queue/cli.mjs +64 -0
- package/modules/job-queue/command.mjs +64 -0
- package/modules/job-queue/config.mjs +48 -0
- package/modules/job-queue/identity.mjs +48 -0
- package/modules/job-queue/index.mjs +135 -0
- package/modules/job-queue/membership.mjs +30 -0
- package/modules/job-queue/module.json +10 -0
- package/modules/job-queue/policy.mjs +29 -0
- package/modules/job-queue/processes.mjs +80 -0
- package/modules/job-queue/resources.mjs +37 -0
- package/modules/job-queue/storage.mjs +64 -0
- package/modules/job-queue/supervisor.mjs +30 -0
- package/modules/job-queue/waiting.mjs +16 -0
- package/modules/mail/LICENSE +21 -0
- package/modules/mail/README.md +336 -0
- package/modules/mail/doctor.mjs +81 -0
- package/modules/mail/index.mjs +8 -0
- package/modules/mail/live-proof/README.md +113 -0
- package/modules/mail/live-proof/run.mjs +55 -0
- package/modules/mail/package.json +19 -0
- package/modules/mail/pnpm-lock.yaml +537 -0
- package/modules/mail/src/adapters/cloudflare/README.md +84 -0
- package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
- package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
- package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
- package/modules/mail/src/adapters/messaging/README.md +92 -0
- package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
- package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
- package/modules/mail/src/adapters/messaging/index.mjs +61 -0
- package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
- package/modules/mail/src/adapters/messaging/request.mjs +52 -0
- package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
- package/modules/mail/src/adapters/resend/README.md +120 -0
- package/modules/mail/src/adapters/resend/index.mjs +22 -0
- package/modules/mail/src/adapters/resend/response.mjs +54 -0
- package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
- package/modules/mail/src/adapters/resend/size.mjs +99 -0
- package/modules/mail/src/adapters/sendgrid/README.md +75 -0
- package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
- package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
- package/modules/mail/src/core/errors.mjs +9 -0
- package/modules/mail/src/core/transport.mjs +33 -0
- package/modules/mail/src/core/validation.mjs +172 -0
- package/modules/mail/src/development/index.d.mts +37 -0
- package/modules/mail/src/development/index.mjs +35 -0
- package/modules/mail/src/development/outbox.mjs +78 -0
- package/modules/mail/src/development/schema.sql +9 -0
- package/modules/mail/src/development/viewer-html.mjs +42 -0
- package/modules/mail/src/development/viewer.mjs +65 -0
- package/modules/mail/src/doctor/README.md +107 -0
- package/modules/mail/src/doctor/common.mjs +64 -0
- package/modules/mail/src/doctor/dns.mjs +102 -0
- package/modules/mail/src/doctor/index.mjs +28 -0
- package/modules/mail/src/doctor/probe.mjs +66 -0
- package/modules/mail/src/doctor/receive.mjs +81 -0
- package/modules/mail/src/doctor/records.mjs +45 -0
- package/modules/mail/src/events/README.md +268 -0
- package/modules/mail/src/events/d1-schema.sql +25 -0
- package/modules/mail/src/events/d1-storage.mjs +76 -0
- package/modules/mail/src/events/index.mjs +3 -0
- package/modules/mail/src/events/intake.mjs +201 -0
- package/modules/mail/src/events/storage.mjs +52 -0
- package/modules/mail/src/events/verify.mjs +92 -0
- package/modules/mail/src/inbound/README.md +137 -0
- package/modules/mail/src/inbound/bytes.mjs +91 -0
- package/modules/mail/src/inbound/headers.mjs +143 -0
- package/modules/mail/src/inbound/index.mjs +29 -0
- package/modules/mail/src/inbound/mime.mjs +36 -0
- package/modules/mail/src/inbound/multipart.mjs +27 -0
- package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
- package/modules/mail/src/inbound/resend.mjs +54 -0
- package/modules/mail/src/inbound/sendgrid.mjs +60 -0
- package/modules/mail/src/sender.mjs +33 -0
- package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
- package/modules/mail/src/sendgrid-email/index.mjs +86 -0
- package/modules/mail/src/unsubscribe/index.mjs +142 -0
- package/modules/operator-auth/README.md +599 -0
- package/modules/operator-auth/accounts/adapter.mjs +41 -0
- package/modules/operator-auth/accounts/admin.mjs +159 -0
- package/modules/operator-auth/accounts/credentials.mjs +27 -0
- package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
- package/modules/operator-auth/accounts/index.d.mts +9 -0
- package/modules/operator-auth/accounts/index.mjs +9 -0
- package/modules/operator-auth/accounts/native.d.mts +38 -0
- package/modules/operator-auth/accounts/native.mjs +171 -0
- package/modules/operator-auth/accounts/oa.mjs +90 -0
- package/modules/operator-auth/accounts/provider-control.mjs +50 -0
- package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
- package/modules/operator-auth/accounts/roles.mjs +26 -0
- package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
- package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
- package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
- package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
- package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
- package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
- package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
- package/modules/operator-auth/accounts/settings-page.mjs +4 -0
- package/modules/operator-auth/accounts/store.mjs +246 -0
- package/modules/operator-auth/branding.mjs +66 -0
- package/modules/operator-auth/config.mjs +35 -0
- package/modules/operator-auth/conformance/README.md +81 -0
- package/modules/operator-auth/conformance/index.d.mts +43 -0
- package/modules/operator-auth/conformance/index.mjs +300 -0
- package/modules/operator-auth/conformance/presentation.mjs +57 -0
- package/modules/operator-auth/crypto.mjs +17 -0
- package/modules/operator-auth/dev/badge.mjs +62 -0
- package/modules/operator-auth/dev/index.d.mts +14 -0
- package/modules/operator-auth/dev/index.mjs +137 -0
- package/modules/operator-auth/dev/personas.mjs +17 -0
- package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
- package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
- package/modules/operator-auth/dev-admission.d.mts +4 -0
- package/modules/operator-auth/dev-admission.mjs +10 -0
- package/modules/operator-auth/dev-gate.mjs +86 -0
- package/modules/operator-auth/docs/README.md +78 -0
- package/modules/operator-auth/docs/native-accounts.md +313 -0
- package/modules/operator-auth/docs/tanstack-start.md +218 -0
- package/modules/operator-auth/docs/vite-spa.md +144 -0
- package/modules/operator-auth/errors.mjs +9 -0
- package/modules/operator-auth/http.mjs +42 -0
- package/modules/operator-auth/identity/admission.mjs +105 -0
- package/modules/operator-auth/identity/after-confirm.mjs +18 -0
- package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
- package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
- package/modules/operator-auth/identity/email-link/index.mjs +128 -0
- package/modules/operator-auth/identity/email-link/message.mjs +16 -0
- package/modules/operator-auth/identity/index.d.mts +7 -0
- package/modules/operator-auth/identity/index.mjs +5 -0
- package/modules/operator-auth/identity/oidc/README.md +171 -0
- package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
- package/modules/operator-auth/identity/oidc/config.mjs +102 -0
- package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
- package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
- package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
- package/modules/operator-auth/identity/oidc/index.mjs +287 -0
- package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
- package/modules/operator-auth/identity/oidc/network.mjs +93 -0
- package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
- package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
- package/modules/operator-auth/identity/oidc/store.mjs +68 -0
- package/modules/operator-auth/identity/principal.mjs +28 -0
- package/modules/operator-auth/index.d.mts +223 -0
- package/modules/operator-auth/index.mjs +11 -0
- package/modules/operator-auth/input.mjs +55 -0
- package/modules/operator-auth/local-cli.mjs +16 -0
- package/modules/operator-auth/page.mjs +34 -0
- package/modules/operator-auth/policy.mjs +20 -0
- package/modules/operator-auth/routes.mjs +91 -0
- package/modules/operator-auth/service.mjs +152 -0
- package/modules/operator-auth/signin/index.mjs +67 -0
- package/modules/owner-claim/README.md +137 -0
- package/modules/owner-claim/idp.mjs +40 -0
- package/modules/owner-claim/index.d.mts +27 -0
- package/modules/owner-claim/index.mjs +39 -0
- package/modules/owner-claim/page.mjs +11 -0
- package/modules/owner-claim/schema/idp.sql +4 -0
- package/modules/owner-claim/schema/owner-claim.sql +18 -0
- package/modules/owner-claim/transaction.mjs +87 -0
- package/modules/owner-claim/verify.mjs +80 -0
- package/modules/portability/README.md +444 -0
- package/modules/portability/bridge.mjs +102 -0
- package/modules/portability/catalogue.mjs +118 -0
- package/modules/portability/cli-options.mjs +38 -0
- package/modules/portability/cli.mjs +56 -0
- package/modules/portability/copies.mjs +68 -0
- package/modules/portability/dev-data.mjs +32 -0
- package/modules/portability/diff.mjs +33 -0
- package/modules/portability/export.mjs +129 -0
- package/modules/portability/files.mjs +65 -0
- package/modules/portability/format.mjs +122 -0
- package/modules/portability/generated.mjs +60 -0
- package/modules/portability/hash.mjs +79 -0
- package/modules/portability/http.mjs +42 -0
- package/modules/portability/import.mjs +185 -0
- package/modules/portability/index.d.mts +58 -0
- package/modules/portability/index.mjs +22 -0
- package/modules/portability/journal.mjs +49 -0
- package/modules/portability/legacy-files.mjs +43 -0
- package/modules/portability/legacy-safety.mjs +27 -0
- package/modules/portability/lines.mjs +36 -0
- package/modules/portability/migration-history.mjs +18 -0
- package/modules/portability/migration-statements.mjs +23 -0
- package/modules/portability/payloads.mjs +52 -0
- package/modules/portability/provider-data.mjs +25 -0
- package/modules/portability/restore-safety.mjs +30 -0
- package/modules/portability/schema/portability.sql +25 -0
- package/modules/portability/storage.mjs +49 -0
- package/modules/release/README.md +212 -0
- package/modules/release/catalogue.mjs +79 -0
- package/modules/release/export.mjs +51 -0
- package/modules/release/produce.mjs +136 -0
- package/modules/release/public-child-transport-state.cjs +29 -0
- package/modules/release/public-child.mjs +71 -0
- package/modules/release/public-git.mjs +21 -0
- package/modules/release/publication-scan.mjs +22 -0
- package/modules/release/publish-handoff.mjs +37 -0
- package/modules/release/source.mjs +71 -0
- package/modules/release/test-summary.mjs +50 -0
- package/onboarding/README.md +163 -0
- package/onboarding/activity.mjs +31 -0
- package/onboarding/adapter-auth.mjs +88 -0
- package/onboarding/adapter-hosting.mjs +35 -0
- package/onboarding/adapter-labels.mjs +13 -0
- package/onboarding/adapter-settings.mjs +129 -0
- package/onboarding/adapter.mjs +238 -0
- package/onboarding/agent-handoff.mjs +9 -0
- package/onboarding/app-processes.mjs +21 -0
- package/onboarding/archive-output.d.mts +3 -0
- package/onboarding/archive-output.mjs +54 -0
- package/onboarding/command-custody.mjs +24 -0
- package/onboarding/command-recovery.mjs +8 -0
- package/onboarding/commands.mjs +166 -0
- package/onboarding/dependency-approval.mjs +36 -0
- package/onboarding/dev-identity.mjs +23 -0
- package/onboarding/email.mjs +24 -0
- package/onboarding/failures.mjs +42 -0
- package/onboarding/input-limits.mjs +7 -0
- package/onboarding/installer-inputs.mjs +24 -0
- package/onboarding/launcher.mjs +34 -0
- package/onboarding/local/build-files.mjs +64 -0
- package/onboarding/local/build.mjs +49 -0
- package/onboarding/local/environment.mjs +30 -0
- package/onboarding/local/files.mjs +35 -0
- package/onboarding/local/operation.mjs +166 -0
- package/onboarding/local/owner.mjs +34 -0
- package/onboarding/local/process-table.mjs +114 -0
- package/onboarding/local/recovery.mjs +79 -0
- package/onboarding/local/state.mjs +31 -0
- package/onboarding/metadata.mjs +7 -0
- package/onboarding/operator-install.mjs +45 -0
- package/onboarding/operator-installer.mjs +39 -0
- package/onboarding/operator-prerequisites.mjs +27 -0
- package/onboarding/package.json +6 -0
- package/onboarding/prefill.mjs +16 -0
- package/onboarding/private-files.mjs +97 -0
- package/onboarding/private.mjs +35 -0
- package/onboarding/process-cleanup.mjs +134 -0
- package/onboarding/process-diagnostics.mjs +91 -0
- package/onboarding/process-scope.mjs +118 -0
- package/onboarding/process-table.mjs +118 -0
- package/onboarding/process-tree.mjs +36 -0
- package/onboarding/production-modules.mjs +22 -0
- package/onboarding/production-options.mjs +40 -0
- package/onboarding/production-plan.mjs +8 -0
- package/onboarding/production-runtime.mjs +30 -0
- package/onboarding/project-vite.mjs +14 -0
- package/onboarding/providers.mjs +289 -0
- package/onboarding/queue.mjs +53 -0
- package/onboarding/queued-entry.mjs +34 -0
- package/onboarding/readiness.mjs +59 -0
- package/onboarding/redaction-jwt.mjs +32 -0
- package/onboarding/redaction.mjs +298 -0
- package/onboarding/schema/SETUP.schema.json +514 -0
- package/onboarding/schema/dependencies.mjs +35 -0
- package/onboarding/schema/json-schema.mjs +202 -0
- package/onboarding/schema/setup-validation.mjs +325 -0
- package/onboarding/secrets.mjs +21 -0
- package/onboarding/server.mjs +188 -0
- package/onboarding/setup-manifest.mjs +116 -0
- package/onboarding/state.mjs +46 -0
- package/onboarding/theme-files.mjs +37 -0
- package/onboarding/theme.mjs +118 -0
- package/onboarding/ui/activity.js +98 -0
- package/onboarding/ui/address-client.mjs +33 -0
- package/onboarding/ui/app.js +377 -0
- package/onboarding/ui/index.html +1 -0
- package/onboarding/ui/model-core.mjs +127 -0
- package/onboarding/ui/model.mjs +5 -0
- package/onboarding/ui/style.css +63 -0
- package/onboarding/ui/theme.css +37 -0
- package/onboarding/ui/workspace-art.js +30 -0
- package/onboarding/wrangler-auth.mjs +43 -0
- package/operations/README.md +246 -0
- package/operations/operations.mjs +168 -0
- package/operations/project-operations.mjs +157 -0
- package/package.json +11 -4
- package/scripts/publish-check/README.md +72 -0
- package/scripts/publish-check/checksum_maps.py +52 -0
- package/scripts/publish-check/cli.py +34 -0
- package/scripts/publish-check/command_arguments.py +51 -0
- package/scripts/publish-check/command_shell_quotes.py +41 -0
- package/scripts/publish-check/command_source_quotes.py +120 -0
- package/scripts/publish-check/content_identities.py +250 -0
- package/scripts/publish-check/copy_identities.py +41 -0
- package/scripts/publish-check/der.py +61 -0
- package/scripts/publish-check/destination.py +117 -0
- package/scripts/publish-check/env_values.py +74 -0
- package/scripts/publish-check/font_licence.py +23 -0
- package/scripts/publish-check/git_history.py +115 -0
- package/scripts/publish-check/github_refs.py +217 -0
- package/scripts/publish-check/gitleaks.py +72 -0
- package/scripts/publish-check/gitleaks.toml +40 -0
- package/scripts/publish-check/history_scan.py +176 -0
- package/scripts/publish-check/hit_location.py +18 -0
- package/scripts/publish-check/hygiene_config.py +42 -0
- package/scripts/publish-check/hygiene_text.py +11 -0
- package/scripts/publish-check/identity-recipe.mjs +54 -0
- package/scripts/publish-check/json_identity.py +79 -0
- package/scripts/publish-check/license-exceptions.json +11 -0
- package/scripts/publish-check/licenses.py +49 -0
- package/scripts/publish-check/policy.py +18 -0
- package/scripts/publish-check/project_identity.py +89 -0
- package/scripts/publish-check/provenance.py +91 -0
- package/scripts/publish-check/provenance_pins.py +33 -0
- package/scripts/publish-check/public_base.py +63 -0
- package/scripts/publish-check/public_hygiene.py +80 -0
- package/scripts/publish-check/publication-config.json +12 -0
- package/scripts/publish-check/publication_config.py +34 -0
- package/scripts/publish-check/report.py +52 -0
- package/scripts/publish-check/repository_commands.py +75 -0
- package/scripts/publish-check/repository_intent.py +52 -0
- package/scripts/publish-check/reviewed.py +41 -0
- package/scripts/publish-check/source_hashes.mjs +6 -0
- package/scripts/publish-check/tree_scan.py +88 -0
- package/scripts/publish-check/trust_bundle.py +87 -0
- package/scripts/publish-check.mjs +58 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Portable Web Crypto verifier. This file has no application or database imports.
|
|
2
|
+
export class OwnerClaimError extends Error {
|
|
3
|
+
constructor(code = 'invalid_claim', status = 403) { super(code); this.name = 'OwnerClaimError'; this.code = code; this.status = status; }
|
|
4
|
+
}
|
|
5
|
+
const fail = () => { throw new OwnerClaimError(); };
|
|
6
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
const exactKeys = (value, keys) => object(value) && Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
8
|
+
function coordinate(value) {
|
|
9
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(value)) return false;
|
|
10
|
+
return btoa(atob(value.replaceAll('-', '+').replaceAll('_', '/'))).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '') === value;
|
|
11
|
+
}
|
|
12
|
+
function origin(value) {
|
|
13
|
+
try { const u = new URL(value); return u.origin === value && !u.username && !u.password && (u.protocol === 'https:' || (u.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(u.hostname))); } catch { return false; }
|
|
14
|
+
}
|
|
15
|
+
export function configureClaim(input) {
|
|
16
|
+
if (!input || !origin(input.issuer) || !origin(input.appOrigin)
|
|
17
|
+
|| typeof input.installationId !== 'string' || !input.installationId.trim() || input.installationId.length > 256
|
|
18
|
+
|| typeof input.expectedEmail !== 'string' || input.expectedEmail !== input.expectedEmail.trim().toLowerCase()
|
|
19
|
+
|| !/^[^\s@<>\x00-\x1f]+@[^\s@<>\x00-\x1f]+\.[^\s@<>\x00-\x1f]+$/.test(input.expectedEmail) || input.expectedEmail.length > 254
|
|
20
|
+
|| !object(input.publicKeys) || !Object.keys(input.publicKeys).length) throw new OwnerClaimError('invalid_configuration', 503);
|
|
21
|
+
const publicKeys = Object.create(null);
|
|
22
|
+
for (const [kid, key] of Object.entries(input.publicKeys)) {
|
|
23
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(kid) || !object(key)
|
|
24
|
+
|| Object.keys(key).some(name => !['kty', 'crv', 'x', 'y', 'kid', 'alg', 'use', 'key_ops', 'ext'].includes(name))
|
|
25
|
+
|| key.kty !== 'EC' || key.crv !== 'P-256' || !coordinate(key.x) || !coordinate(key.y)
|
|
26
|
+
|| (Object.hasOwn(key, 'kid') && key.kid !== kid) || (Object.hasOwn(key, 'alg') && key.alg !== 'ES256')
|
|
27
|
+
|| (Object.hasOwn(key, 'use') && key.use !== 'sig') || (Object.hasOwn(key, 'ext') && typeof key.ext !== 'boolean')
|
|
28
|
+
|| (Object.hasOwn(key, 'key_ops') && (!Array.isArray(key.key_ops) || key.key_ops.length !== 1 || key.key_ops[0] !== 'verify'))) throw new OwnerClaimError('invalid_configuration', 503);
|
|
29
|
+
publicKeys[kid] = Object.freeze({ ...key, ...(key.key_ops ? { key_ops: Object.freeze([...key.key_ops]) } : {}) });
|
|
30
|
+
}
|
|
31
|
+
return Object.freeze({ issuer: input.issuer, installationId: input.installationId, appOrigin: input.appOrigin, expectedEmail: input.expectedEmail, publicKeys: Object.freeze(publicKeys) });
|
|
32
|
+
}
|
|
33
|
+
function decode(segment) {
|
|
34
|
+
if (!/^[A-Za-z0-9_-]+$/.test(segment)) fail();
|
|
35
|
+
try {
|
|
36
|
+
const raw = atob(segment.replaceAll('-', '+').replaceAll('_', '/'));
|
|
37
|
+
const bytes = Uint8Array.from(raw, c => c.charCodeAt(0));
|
|
38
|
+
if (btoa(raw).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '') !== segment) fail();
|
|
39
|
+
return bytes;
|
|
40
|
+
} catch { fail(); }
|
|
41
|
+
}
|
|
42
|
+
function json(segment) {
|
|
43
|
+
try {
|
|
44
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(decode(segment));
|
|
45
|
+
const value = JSON.parse(text);
|
|
46
|
+
// Reject duplicate decoded member names, including escaped aliases.
|
|
47
|
+
const keys = new Set();
|
|
48
|
+
for (const match of text.matchAll(/"(?:\\[\s\S]|[^"\\])*"\s*:/g)) {
|
|
49
|
+
const key = JSON.parse(match[0].replace(/\s*:$/, ''));
|
|
50
|
+
if (keys.has(key)) fail();
|
|
51
|
+
keys.add(key);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
} catch { fail(); }
|
|
55
|
+
}
|
|
56
|
+
export async function verifyOwnerClaim(token, input) {
|
|
57
|
+
const config = configureClaim(input);
|
|
58
|
+
const now = input.now ?? Math.floor(Date.now() / 1000);
|
|
59
|
+
if (!Number.isSafeInteger(now) || now < 0) throw new OwnerClaimError('invalid_configuration', 503);
|
|
60
|
+
if (typeof token !== 'string' || token.length > 8192) fail();
|
|
61
|
+
const parts = token.split('.');
|
|
62
|
+
if (parts.length !== 3) fail();
|
|
63
|
+
const header = json(parts[0]);
|
|
64
|
+
if (!exactKeys(header, ['alg', 'typ', 'kid']) || header.alg !== 'ES256' || header.typ !== 'rer-owner-claim+jwt'
|
|
65
|
+
|| typeof header.kid !== 'string' || !Object.hasOwn(config.publicKeys, header.kid)) fail();
|
|
66
|
+
const signature = decode(parts[2]);
|
|
67
|
+
if (signature.length !== 64) fail();
|
|
68
|
+
try {
|
|
69
|
+
const key = await crypto.subtle.importKey('jwk', config.publicKeys[header.kid], { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
|
|
70
|
+
if (!await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, signature, new TextEncoder().encode(`${parts[0]}.${parts[1]}`))) fail();
|
|
71
|
+
} catch { fail(); }
|
|
72
|
+
const claim = json(parts[1]);
|
|
73
|
+
if (!exactKeys(claim, ['v', 'iss', 'aud', 'installationId', 'email', 'jti', 'iat', 'exp'])
|
|
74
|
+
|| claim.v !== 1 || claim.iss !== config.issuer || claim.aud !== config.appOrigin || claim.installationId !== config.installationId
|
|
75
|
+
|| claim.email !== config.expectedEmail || typeof claim.jti !== 'string'
|
|
76
|
+
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(claim.jti)
|
|
77
|
+
|| !Number.isSafeInteger(claim.iat) || !Number.isSafeInteger(claim.exp) || claim.iat < 0
|
|
78
|
+
|| claim.iat - now > 30 || claim.exp <= now || claim.exp <= claim.iat || claim.exp - claim.iat > 300) fail();
|
|
79
|
+
return Object.freeze(claim);
|
|
80
|
+
}
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# Portable installation data
|
|
2
|
+
|
|
3
|
+
This dependency-free source module exports and restores one installation's D1
|
|
4
|
+
tables, R2 objects and optional named byte payloads. It is generalised from the
|
|
5
|
+
Penny and Orbit seeds, with atomic graph restoration from Lamp. Products retain
|
|
6
|
+
their data policy, erasure planning, authorization and interface.
|
|
7
|
+
|
|
8
|
+
The library works in Workers and Node with D1/R2 bindings. It makes no network
|
|
9
|
+
request and dispatches no product effect. Only the CLI contacts the product's
|
|
10
|
+
HTTP handler. Copying the module does not establish a product's complete data
|
|
11
|
+
ownership guarantee: include every binding, review transformations and extra
|
|
12
|
+
payload consumers, and prove the integrated round trip.
|
|
13
|
+
|
|
14
|
+
## Policy
|
|
15
|
+
|
|
16
|
+
`await createPortability({db, buckets, policy})` validates policy and the current
|
|
17
|
+
schema at construction. Apply the bookkeeping migration first. Policy fields:
|
|
18
|
+
|
|
19
|
+
| Field | Meaning |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `product` | Required archive product identity. |
|
|
22
|
+
| `excludeTables` | Additional exact names or prefixes ending in `%`. |
|
|
23
|
+
| `includeTables` | Explicit identity tables to include despite default exclusions. |
|
|
24
|
+
| `derivedTables` | Virtual/FTS tables to regenerate; their shadow tables are excluded. |
|
|
25
|
+
| `secretColumns` | Per-table nullable base columns omitted at export and written as NULL. Primary keys and generated columns cannot be secret. |
|
|
26
|
+
| `exportRow(table,row)` | Pure, synchronous, idempotent transformation of encoded base cells. Restore writes its result verbatim; SQLite recomputes generated cells. |
|
|
27
|
+
| `replaceableTables` | Bootstrap or migration-seeded rows replaced in the atomic restore batch. Populated excluded dependents reachable through modifying FK actions refuse replacement. |
|
|
28
|
+
| `requiredConfiguration` | Setting names returned after restore for the owner to re-enter. |
|
|
29
|
+
| `validateStaged({db,tables,read,manifest})` | Read-only product validation before any object write; false or a thrown error refuses restore. `tables` names the staging tables for bounded SQL queries; `read` is a convenience for small tables. |
|
|
30
|
+
| `acceptedSchemas` | Map from an earlier full schema hash to `{upgradeRow(table,row)}`. Upgrade functions are pure and synchronous. No older schema is accepted by default. |
|
|
31
|
+
| `buckets` | Binding names mapped to `{prefix?,objectReferences?:{table,keyColumn}}`. Prefixes isolate an object namespace. References require exactly the distinct referenced keys. |
|
|
32
|
+
| `extraPayloads` | Named `{produce,consume}` pairs. Producers return bytes or a byte stream. Consumers receive a verified byte stream and descriptor before D1 commit. |
|
|
33
|
+
| `partBytes` | Maximum part size, default 16 MiB, configurable from 1 KiB to 16 MiB. |
|
|
34
|
+
|
|
35
|
+
Default exclusions are `sqlite_%`, `_cf_%`, `d1_migrations`,
|
|
36
|
+
`rer_portability_%`, `rer_mail_development_outbox`, `oa_%`, `oc_%`,
|
|
37
|
+
`rer_dependency_keys`, `rer_dependency_pending` and `rer_dependency_codes`.
|
|
38
|
+
When a policy includes operator-auth tables, local dev identity still never
|
|
39
|
+
travels, and no policy option changes that: export drops `oa_sessions` rows of
|
|
40
|
+
kind `dev`, every credential recorded in `oa_dev_credentials` (and that table's
|
|
41
|
+
rows), and marks dev personas (`<as>@dev.localhost` operators) inactive with a
|
|
42
|
+
new authority version; import refuses with 409 (`dev_data`, exit 4 in the CLI)
|
|
43
|
+
an archive holding a `dev` session or `oa_dev_credentials` rows. Only tables with
|
|
44
|
+
operator-auth's own columns are touched.
|
|
45
|
+
A policy may preserve people, their external identity bindings and the original
|
|
46
|
+
owner claim. Such a destination keeps that claim and recovers access through
|
|
47
|
+
ordinary native sign-in; setup does not grant another owner. Other tables are
|
|
48
|
+
discovered automatically. Exclusion rules follow SQLite's ASCII identifier case matching.
|
|
49
|
+
Unlisted virtual tables, hidden virtual-table columns and foreign keys into
|
|
50
|
+
excluded tables fail by name. The product must explicitly regenerate derived
|
|
51
|
+
data. A schema upgrade may change columns but cannot silently omit tables.
|
|
52
|
+
|
|
53
|
+
Provider link keys and transient provider sessions never travel. Export marks
|
|
54
|
+
retained dependency links as needing re-link and turns provider sign-in and
|
|
55
|
+
first-setup enablement off. When `oa_auth_settings` is included, each export
|
|
56
|
+
advances its authority generation; importing then exporting therefore changes
|
|
57
|
+
that generation and the content hash, while retained domain and identity rows
|
|
58
|
+
remain equal. Re-linking keeps the installation sector and person bindings;
|
|
59
|
+
restored provider sign-in remains off until the owner explicitly enables it.
|
|
60
|
+
|
|
61
|
+
SQLite VIRTUAL and STORED generated columns retain their visible schema metadata
|
|
62
|
+
and original table SQL. Only writable base cells appear in archived rows or
|
|
63
|
+
restore inserts; the destination's migrated schema computes generated values and
|
|
64
|
+
maintains its indexes. Snapshot and staging tables retain native generated
|
|
65
|
+
expressions, column affinities and collations, while leaving final constraints
|
|
66
|
+
to the atomic destination restore. Normalization updates base cells, so generated
|
|
67
|
+
attachment references and `validateStaged` reads see recomputed values. Nullable
|
|
68
|
+
secret base cells become NULL before generated snapshots are normalized. A
|
|
69
|
+
generated column cannot itself be redacted: exclude the table or change the
|
|
70
|
+
product schema/policy if its computed value must be secret. Ordinary-column
|
|
71
|
+
metadata and row encoding are unchanged. Ordinary-table archives using the
|
|
72
|
+
authenticated format continue to import into the same schema; see the version
|
|
73
|
+
1 refusal below.
|
|
74
|
+
|
|
75
|
+
An archived INTEGER-affinity base cell outside JavaScript's safe integer range fails before
|
|
76
|
+
precision is lost. Cells are null, strings, finite numbers or `{blob:base64}`.
|
|
77
|
+
Column affinity and NOT NULL are checked before staging. R2 metadata moves with
|
|
78
|
+
the bytes. Large objects span parts and restore through multipart upload with at
|
|
79
|
+
most one 5 MiB upload buffer. Extra payload consumers own their own transaction,
|
|
80
|
+
idempotence and recovery; D1 cannot roll back Durable Object or external state.
|
|
81
|
+
|
|
82
|
+
## Archive and durable jobs
|
|
83
|
+
|
|
84
|
+
The UTF-8 JSON-lines archive has a manifest, indexed checksummed parts and a
|
|
85
|
+
closing record. `readArchive(stream)` uses the shared bounded, fatal UTF-8 line
|
|
86
|
+
reader. The default maximum line size is 24 MiB; callers can set a smaller bound.
|
|
87
|
+
No runtime or CLI materializes the whole archive. Readers enforce a 16 MiB
|
|
88
|
+
canonical UTF-8 part limit; `readArchive(stream,{partBytes})` can lower it.
|
|
89
|
+
Import uploads enforce the policy's part limit before any staging write.
|
|
90
|
+
A part has at most 1,000 rows;
|
|
91
|
+
R2 and payload parts contain base64 byte chunks.
|
|
92
|
+
|
|
93
|
+
The manifest includes product, export time, persistent random installation
|
|
94
|
+
identity, schema identity, table SQL/columns/counts/FK dependencies, object and
|
|
95
|
+
payload sizes/hashes/metadata, exclusions, required configuration, part count and
|
|
96
|
+
the source erasure epoch/journal. The closing `contentHash` hashes canonical JSON
|
|
97
|
+
of the schema, normalized rows ordered by archived primary key (original row order without a key),
|
|
98
|
+
object metadata/hashes, payload hashes and required configuration. Keyless row
|
|
99
|
+
parts also preserve the original rowid, including gaps, for restoration and
|
|
100
|
+
erasure matching. Export time,
|
|
101
|
+
source identity, job ids and boundaries do not affect equivalence.
|
|
102
|
+
|
|
103
|
+
New exports use archive version 2. The closing `archiveHash` is SHA-256 of
|
|
104
|
+
canonical JSON `{manifest,parts}`, where `manifest` is the complete manifest
|
|
105
|
+
and `parts` is the ordered array of verified part SHA-256 strings. This binds
|
|
106
|
+
source identity, erasure epoch/journal, export time, all table and policy-visible
|
|
107
|
+
metadata, and every part field (including original rowids). It is required on
|
|
108
|
+
finalize and checked before any R2 write, extra payload consumer or D1 restore
|
|
109
|
+
commit. Local `rer import` verifies the whole input before even applying
|
|
110
|
+
destination migrations. `contentHash` remains the content-equivalence digest;
|
|
111
|
+
`archiveHash` changes when export time, source metadata or part boundaries change.
|
|
112
|
+
|
|
113
|
+
Version 1 archives lack this binding and are refused with
|
|
114
|
+
`legacy_archive_unverifiable` (HTTP 409; local `rer import` exit 4), before
|
|
115
|
+
restoration. Upgrade the original installation and export a new archive.
|
|
116
|
+
Changing an old archive's version or recomputing its part checksums cannot
|
|
117
|
+
authenticate it. If the original installation is unavailable, use the explicit
|
|
118
|
+
[backup-only v1 recovery](#backup-only-v1-recovery) below. Normal import still
|
|
119
|
+
requires version 2 integrity.
|
|
120
|
+
|
|
121
|
+
## Archives across later additive migrations
|
|
122
|
+
|
|
123
|
+
An archive restores into an installation whose schema differs only by migrations
|
|
124
|
+
applied after the archive was made, when those migrations are additive: new
|
|
125
|
+
tables, new columns that are nullable or have a default, and indexes. The
|
|
126
|
+
archive's tables restore with their archived columns, later columns take their
|
|
127
|
+
defaults, and later tables stay empty (they must be empty, like every restored
|
|
128
|
+
table). This applies to normal v2 import and to backup-only v1 recovery.
|
|
129
|
+
|
|
130
|
+
The Base CLI runners (local Workers and container) pass `createPortability` a
|
|
131
|
+
`migrations` replayer (`migration-history.mjs`, Node only) built from the
|
|
132
|
+
destination's applied migration ledger and migration files. Import replays them
|
|
133
|
+
into a scratch database to find the archive's place in that history:
|
|
134
|
+
|
|
135
|
+
- Archives record the applied migration names in `manifest.migrations` (since
|
|
136
|
+
additive restore). Every recorded migration must be applied here, and
|
|
137
|
+
replaying exactly those must give the archive's schema identity.
|
|
138
|
+
- Older v2 and all v1 archives do not record them; the latest prefix of the
|
|
139
|
+
applied migrations that gives the archive's schema identity is used. Migrations
|
|
140
|
+
that change no archived table (indexes, excluded tables) cannot be told apart
|
|
141
|
+
there and are reported as later.
|
|
142
|
+
|
|
143
|
+
Restored rows never pass through the later migrations, so each of them may
|
|
144
|
+
contain only `CREATE TABLE` (not `AS SELECT`), `CREATE INDEX` and
|
|
145
|
+
`ALTER TABLE ... ADD COLUMN` (SQLite allows only constant defaults there) on the
|
|
146
|
+
restorable tables. A later migration with any other statement that names a
|
|
147
|
+
restorable table, such as an `INSERT`, `UPDATE` or `DELETE` that purges or
|
|
148
|
+
backfills rows or a trigger, is refused with `schema_mismatch`; statements that
|
|
149
|
+
touch only excluded tables are allowed. For archives without a migration record,
|
|
150
|
+
every migration after the earliest prefix that gives the archive's schema is
|
|
151
|
+
checked, since the archive may predate any of them.
|
|
152
|
+
|
|
153
|
+
The same check runs when the archive's schema identity equals the destination's:
|
|
154
|
+
a later migration that only purges or backfills rows leaves the schema unchanged,
|
|
155
|
+
yet the archive's rows never passed through it. Such an archive is refused with
|
|
156
|
+
`schema_mismatch:migration_data:<migration>` (local `rer import` exit 4, naming
|
|
157
|
+
the migration) before any staging or import job; one whose later migrations only
|
|
158
|
+
add indexes or touch excluded tables restores as usual. Without a replayer, an
|
|
159
|
+
archive whose recorded migrations the destination's ledger has outgrown is
|
|
160
|
+
refused with `schema_mismatch:migrations`, since those migrations cannot be read;
|
|
161
|
+
an archive with no migration record cannot be placed there and restores as before.
|
|
162
|
+
|
|
163
|
+
Then each archived table must still exist with the same leading columns, types,
|
|
164
|
+
keys and constraints; its definition may differ only by added column definitions,
|
|
165
|
+
as `ALTER TABLE ADD COLUMN` writes them. Anything else, including a dropped,
|
|
166
|
+
renamed or retyped table or column and a migration the archive has but this
|
|
167
|
+
installation lacks, is refused with `schema_mismatch` (local `rer import` exit 4)
|
|
168
|
+
before any staging or audit write. Without a replayer (the HTTP handler, direct
|
|
169
|
+
bindings), only identical schemas and `acceptedSchemas` restore.
|
|
170
|
+
|
|
171
|
+
The import status and CLI receipt carry `bridged`: `migrations` (the later
|
|
172
|
+
migrations bridged), `tables` (later tables left empty), `columns` (later columns
|
|
173
|
+
per table that took defaults), `fromSchema` and `toSchema`. The import job keeps it
|
|
174
|
+
and v1 recovery adds it to its audit record. `archiveHash`, `contentHash`,
|
|
175
|
+
the erasure fence and v1's own checks are unchanged.
|
|
176
|
+
|
|
177
|
+
## Backup-only v1 recovery
|
|
178
|
+
|
|
179
|
+
When your only backup is a version 1 archive and the original installation is
|
|
180
|
+
gone, the Base local Workers CLI provides an explicitly acknowledged recovery.
|
|
181
|
+
Use a separate, empty installation with the same product schema, or one that
|
|
182
|
+
differs only by [later additive migrations](#archives-across-later-additive-migrations),
|
|
183
|
+
and the same portability policy. Keep its writers stopped. Do not run setup or claim an owner first:
|
|
184
|
+
recovery also refuses populated identity, excluded and normally replaceable
|
|
185
|
+
tables, and any objects in the declared buckets. It does not merge or overwrite
|
|
186
|
+
existing installation data. Remote and container recovery are not supported.
|
|
187
|
+
|
|
188
|
+
1. Keep the original v1 backup untouched. Obtain a fresh runnable checkout of
|
|
189
|
+
your product with this CLI, install its dependencies with `rer install`, and
|
|
190
|
+
copy the backup to `./exports/backup-v1.rer`. On macOS/Linux, make that copy
|
|
191
|
+
owner-only with `chmod 600 exports/backup-v1.rer`. Input files must be regular,
|
|
192
|
+
private, single-link files; do not use a symlink.
|
|
193
|
+
2. Read the verification limits below, then run from that empty checkout:
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
rer import --input exports/backup-v1.rer --legacy-v1 --yes \
|
|
197
|
+
--acknowledge-unverified-metadata --output exports/recovered-v2.rer
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`--yes` confirms restoring data. The separate
|
|
201
|
+
`--acknowledge-unverified-metadata` accepts the missing v1 metadata binding;
|
|
202
|
+
`--yes` alone cannot authorize legacy recovery. `--output` must name a new
|
|
203
|
+
safe archive path, inside `exports/` or an absolute path outside the checkout.
|
|
204
|
+
The command verifies the input before destination migrations, restores it,
|
|
205
|
+
and finishes by writing and verifying a fresh owner-only v2 export. An
|
|
206
|
+
existing output is refused. A v2 input cannot use this recovery mode.
|
|
207
|
+
3. Keep `recovered-v2.rer` with your backups. Ordinary
|
|
208
|
+
`rer import --input exports/recovered-v2.rer --yes` accepts it in another
|
|
209
|
+
empty installation. Re-enter the reported required configuration and recover
|
|
210
|
+
owner access according to your product's identity policy; provider links
|
|
211
|
+
still need explicit re-linking. Check the recovered content before normal use.
|
|
212
|
+
|
|
213
|
+
Recovery verifies all checks v1 carried: schema checksum, each ordered part's
|
|
214
|
+
checksum, part/table counts, row order/types, binary sizes and hashes, the
|
|
215
|
+
closing content hash and completeness. It validates the carried erasure journal
|
|
216
|
+
and refuses rows matching its tombstones or any destination erasure fence.
|
|
217
|
+
Product validation, foreign keys and normal restore safeguards still apply.
|
|
218
|
+
Any mismatch refuses recovery; this mode does not repair damaged archives.
|
|
219
|
+
|
|
220
|
+
V1 cannot authenticate the complete manifest: source identity, export time,
|
|
221
|
+
erasure epoch/journal, rowids and other metadata lack the v2 archive binding.
|
|
222
|
+
Checksums are consistency checks, not proof of authorship. A missing or altered
|
|
223
|
+
erasure record cannot be reconstructed, and deletions after the backup are
|
|
224
|
+
unknown. The new v2 export binds the recovered data and metadata from recovery
|
|
225
|
+
onward; it cannot prove the original v1 metadata was authentic.
|
|
226
|
+
|
|
227
|
+
The installation records `legacy_v1_unverified_restore`, the explicit metadata
|
|
228
|
+
acknowledgement, claimed source identity and export time, output path and restored content
|
|
229
|
+
hash in `rer_portability_recovery_audit`, atomically with the restore. This
|
|
230
|
+
additive, local bookkeeping table survives transfer cleanup and is excluded
|
|
231
|
+
from data exports. No historical migration is rewritten. If the final v2
|
|
232
|
+
export fails (for example, disk full), the restore and audit remain: preserve
|
|
233
|
+
the installation, resolve the output problem and run ordinary `rer export
|
|
234
|
+
--output exports/recovered-v2.rer`. The recovery command reports failure until
|
|
235
|
+
its v2 output succeeds; do not reset the recovered installation to retry.
|
|
236
|
+
|
|
237
|
+
`exports.create(actor?)` copies all tables in one D1 batch. Product writes may
|
|
238
|
+
continue after that snapshot. R2 listing records size and etag; changes or missing
|
|
239
|
+
objects fail subsequent reads. D1 and R2 do not share a cross-binding transaction.
|
|
240
|
+
Transfer bookkeeping lives in sidecar tables, so product tables up to D1's
|
|
241
|
+
100-column limit retain the full column and binding capacity. A keyless table
|
|
242
|
+
with 100 columns or generated columns restores its rowids with two updates in
|
|
243
|
+
the same atomic batch.
|
|
244
|
+
Schemas shadowing all three SQLite rowid aliases refuse export at construction.
|
|
245
|
+
Encoded projections that exceed D1's SQL statement limit also refuse construction
|
|
246
|
+
by table name. Product-only staging keeps restore queries small even with long
|
|
247
|
+
column names.
|
|
248
|
+
Advance normalizes the durable snapshot, sorts archived keys and checks attachment
|
|
249
|
+
references again before completing the archive.
|
|
250
|
+
`exports.advance(id,{expectedPart,maxParts})` commits durable bounded parts;
|
|
251
|
+
`expectedPart` makes a lost-acknowledgement retry a no-op. Status reports progress;
|
|
252
|
+
download streams completed jobs. Abort removes snapshots and stored parts.
|
|
253
|
+
|
|
254
|
+
`imports.create(manifest,actor?)` creates staging under one durable import lock.
|
|
255
|
+
Upload parts strictly in order. Same-index/same-hash retries are no-ops; different
|
|
256
|
+
content returns 409. Finalize requires the closing `archiveHash` and verifies all
|
|
257
|
+
parts, semantic contents, destination
|
|
258
|
+
emptiness, erasure and product invariants. R2 ownership is recorded after the
|
|
259
|
+
empty-namespace check. Verified object writes precede one atomic D1 batch, with
|
|
260
|
+
deferred FKs, trigger suppression/recreation, replaceable rows, staged inserts,
|
|
261
|
+
journal adoption and completion. FK cycles and self-references restore together.
|
|
262
|
+
An unsuccessful commit removes only owned objects. A lost commit acknowledgement
|
|
263
|
+
is resolved by reading durable completion before any cleanup.
|
|
264
|
+
|
|
265
|
+
An empty destination must be reserved for restore: pause destination writers and
|
|
266
|
+
serialize finalize/abort calls. Product writes to source tables can continue.
|
|
267
|
+
R2 has no compare-and-swap transaction covering bucket emptiness and writes.
|
|
268
|
+
An interrupted applying job can be finalized again; ownership survives restart.
|
|
269
|
+
Keep job status until the client acknowledges completion. Abort completed exports
|
|
270
|
+
and unfinished imports when no longer needed; completed import copies can be
|
|
271
|
+
invalidated by the product's erasure batch.
|
|
272
|
+
|
|
273
|
+
## HTTP and CLI
|
|
274
|
+
|
|
275
|
+
`await createPortabilityHandler({db,buckets,policy,appOrigin,basePath,authorize})`
|
|
276
|
+
returns an async dispatcher, or null for foreign paths. Default base path is
|
|
277
|
+
`/api/portability`. The product's `authorize(request,{operation,jobId})` returns
|
|
278
|
+
an actor id or null. Jobs belong to their creating actor. Unsafe methods require
|
|
279
|
+
the exact configured Origin. Responses have `Cache-Control: no-store` and errors
|
|
280
|
+
are `{error:code}`. Request bodies are bounded; `maxBodyBytes` can lower the bound.
|
|
281
|
+
|
|
282
|
+
| Method / relative path | Action |
|
|
283
|
+
| --- | --- |
|
|
284
|
+
| `POST exports` | Create export. |
|
|
285
|
+
| `GET exports/:id` | Status. |
|
|
286
|
+
| `POST exports/:id/advance` | Advance with optional expectedPart/maxParts. |
|
|
287
|
+
| `GET exports/:id/download` | Stream archive. |
|
|
288
|
+
| `POST exports/:id/abort` | Clean up export. |
|
|
289
|
+
| `POST imports` | Create with `{confirmed:true,manifest}`. |
|
|
290
|
+
| `PUT imports/:id/parts/:index` | Upload part. |
|
|
291
|
+
| `POST imports/:id/finalize` | Apply with `{archiveHash,contentHash?,laterJournal?}`. |
|
|
292
|
+
| `GET imports/:id` | Status. |
|
|
293
|
+
| `POST imports/:id/abort` | Clean up unfinished import. |
|
|
294
|
+
|
|
295
|
+
```sh
|
|
296
|
+
node vendor/base/modules/portability/cli.mjs export --origin https://app.example.com --session-file /absolute/session.cookie --out /absolute/archive.jsonl
|
|
297
|
+
node vendor/base/modules/portability/cli.mjs import --origin https://app.example.com --session-file /absolute/session.cookie --from /absolute/archive.jsonl
|
|
298
|
+
node vendor/base/modules/portability/cli.mjs status --origin https://app.example.com --session-file /absolute/session.cookie --resume JOB_ID
|
|
299
|
+
node vendor/base/modules/portability/cli.mjs abort --origin https://app.example.com --session-file /absolute/session.cookie --resume JOB_ID
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Only HTTPS or HTTP on literal localhost, 127.0.0.1 or [::1] is accepted. Origins
|
|
303
|
+
cannot contain URL credentials, path, query or fragment. The small regular session
|
|
304
|
+
file contains one cookie; its value is never displayed or copied. Paths must be
|
|
305
|
+
absolute. `--base-path` selects another mounted path. Export refuses an existing
|
|
306
|
+
output, writes a 0600 `.partial`, verifies every line, syncs, then renames it to
|
|
307
|
+
the final name, leaving no second link, so `rer import` accepts the file. New
|
|
308
|
+
directories are 0700. A 0600 `.transfer.json` stores the job id and origin.
|
|
309
|
+
Use the printed resume command with the same job id. A resumed download
|
|
310
|
+
starts at the manifest and re-verifies the stream before promotion. Imports
|
|
311
|
+
re-read and verify the file, retrying earlier acknowledged parts harmlessly.
|
|
312
|
+
|
|
313
|
+
## Direct bindings
|
|
314
|
+
|
|
315
|
+
The adopting seed supplies Wrangler; the module adds no dependency:
|
|
316
|
+
|
|
317
|
+
```js
|
|
318
|
+
import { getPlatformProxy } from 'wrangler';
|
|
319
|
+
import { createPortability, readArchive } from './vendor/base/modules/portability/index.mjs';
|
|
320
|
+
import { portabilityPolicy } from './src/portability-policy.mjs';
|
|
321
|
+
const proxy = await getPlatformProxy({ remoteBindings: false });
|
|
322
|
+
// For reviewed remote use, mark the D1/R2 bindings remote in Wrangler config
|
|
323
|
+
// and enable remoteBindings. Credential provisioning stays with the seed.
|
|
324
|
+
try {
|
|
325
|
+
const service = await createPortability({ db: proxy.env.DB, buckets: {FILES:proxy.env.FILES}, policy: portabilityPolicy });
|
|
326
|
+
const id = await service.exports.create();
|
|
327
|
+
while ((await service.exports.advance(id,{maxParts:16})).phase === 'building') {}
|
|
328
|
+
for await (const record of readArchive(await service.exports.download(id))) {
|
|
329
|
+
// Write each record to an owner-only file, or upload it to a fresh target.
|
|
330
|
+
}
|
|
331
|
+
} finally { await proxy.dispose(); }
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Erasure fence
|
|
335
|
+
|
|
336
|
+
Include `await service.erasureStatements(table,primaryKey)` in the product's own
|
|
337
|
+
deletion batch. Keys are objects of primary-key columns; a keyless table uses
|
|
338
|
+
`{rowid:integer}`. Tombstones are opaque hashes bound to source identity, table
|
|
339
|
+
and canonical key. Journals grow and epochs prevent older source restoration.
|
|
340
|
+
For rows deliberately kept with all personal values replaced, include
|
|
341
|
+
`await service.redactionStatements()` in that same transaction instead of
|
|
342
|
+
permanently tombstoning their retained primary keys. It fences the source's
|
|
343
|
+
checkpoint using the private portability state row and advances the epoch.
|
|
344
|
+
A destination or trusted later journal with that checkpoint refuses the whole
|
|
345
|
+
older archive; a post-redaction archive remains restorable with anonymous rows.
|
|
346
|
+
This stricter checkpoint is necessary when preserved identities still appear in
|
|
347
|
+
new archives. It does not change offline transfer's need for a trusted later journal.
|
|
348
|
+
|
|
349
|
+
Archives, separately supplied later journals and destination journals all fence
|
|
350
|
+
restore. Commit adopts source identity so subsequent erasures keep matching.
|
|
351
|
+
|
|
352
|
+
Include `await service.invalidateTransfersStatements()` in that same batch to
|
|
353
|
+
drop every transfer snapshot, staging copy and stored part. This does not plan
|
|
354
|
+
product erasure or remove product objects; the product handles attachments and
|
|
355
|
+
any extra state. Do not retain a previously downloaded archive as erasure proof;
|
|
356
|
+
supply the owner's later journal when restoring old offline data.
|
|
357
|
+
|
|
358
|
+
## Measured capacity
|
|
359
|
+
|
|
360
|
+
One local Miniflare D1/R2 run exported, restored and exported 20,000 rows across
|
|
361
|
+
five tables in 171.169 seconds, with 625 parts at a configured 4 KiB bound and a
|
|
362
|
+
1,979,471-byte archive. Restore used one atomic D1 commit. The verification run
|
|
363
|
+
reports the measurements again. These measurements are test evidence, not a production
|
|
364
|
+
capacity claim. Real D1 CPU, query, statement and storage limits, object mutation,
|
|
365
|
+
destination writers and extra-payload recovery need product-level verification.
|
|
366
|
+
|
|
367
|
+
## Adopting
|
|
368
|
+
|
|
369
|
+
Copy exactly these runtime, declaration and schema files byte for byte under the
|
|
370
|
+
seed's `vendor/base/` directory. The fixture adoption test parses this list and
|
|
371
|
+
executes the copied Worker and CLI. Tests and development fixtures are not copied.
|
|
372
|
+
|
|
373
|
+
```text
|
|
374
|
+
modules/portability/index.mjs
|
|
375
|
+
modules/portability/index.d.mts
|
|
376
|
+
modules/portability/lines.mjs
|
|
377
|
+
modules/portability/hash.mjs
|
|
378
|
+
modules/portability/format.mjs
|
|
379
|
+
modules/portability/catalogue.mjs
|
|
380
|
+
modules/portability/storage.mjs
|
|
381
|
+
modules/portability/copies.mjs
|
|
382
|
+
modules/portability/dev-data.mjs
|
|
383
|
+
modules/portability/provider-data.mjs
|
|
384
|
+
modules/portability/generated.mjs
|
|
385
|
+
modules/portability/restore-safety.mjs
|
|
386
|
+
modules/portability/bridge.mjs
|
|
387
|
+
modules/portability/migration-statements.mjs
|
|
388
|
+
modules/portability/legacy-safety.mjs
|
|
389
|
+
modules/portability/export.mjs
|
|
390
|
+
modules/portability/import.mjs
|
|
391
|
+
modules/portability/payloads.mjs
|
|
392
|
+
modules/portability/journal.mjs
|
|
393
|
+
modules/portability/http.mjs
|
|
394
|
+
modules/portability/cli.mjs
|
|
395
|
+
modules/portability/cli-options.mjs
|
|
396
|
+
modules/portability/schema/portability.sql
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Apply `schema/portability.sql` as the seed's next numbered migration. Preserve
|
|
400
|
+
all existing migrations and their ledger. Wire package.json:
|
|
401
|
+
|
|
402
|
+
```json
|
|
403
|
+
{"scripts":{"export":"node vendor/base/modules/portability/cli.mjs export","import":"node vendor/base/modules/portability/cli.mjs import"}}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Before copying, capture the source checkout's actual origin with
|
|
407
|
+
`git remote get-url origin` and its full revision with `git rev-parse HEAD`.
|
|
408
|
+
Record this component in `edition.json`, replacing `ORIGIN` and `PIN` with those
|
|
409
|
+
values. Keep the real source provenance in the edition's component and source
|
|
410
|
+
lock rather than embedding a repository address in the copied module:
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{"id":"runeditrun/portability","role":"module","source":{"kind":"local","paths":["vendor/base/modules/portability"],"origin":{"repository":"ORIGIN","revision":"PIN","paths":["modules/portability"]}}}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Follow the seed's edition-authoring source-lock workflow, then prove the copy:
|
|
417
|
+
|
|
418
|
+
```sh
|
|
419
|
+
node scripts/edition-authoring/cli.mjs lock
|
|
420
|
+
node scripts/edition-authoring/cli.mjs check
|
|
421
|
+
node scripts/edition-authoring/cli.mjs record-base vendor/base/modules/portability modules/portability PIN
|
|
422
|
+
node scripts/edition-authoring/cli.mjs verify-base
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Add `verify-base` and an integrated export/import/export test to the seed's verify.
|
|
426
|
+
Keep the seed's real binding names and authorization in its Worker:
|
|
427
|
+
|
|
428
|
+
```js
|
|
429
|
+
import { createPortabilityHandler } from './vendor/base/modules/portability/index.mjs';
|
|
430
|
+
const handler = await createPortabilityHandler({
|
|
431
|
+
db:env.DB, buckets:{FILES:env.FILES}, appOrigin:env.APP_ORIGIN,
|
|
432
|
+
policy:{product:'my-product',buckets:{FILES:{}},secretColumns:{connections:['token']},
|
|
433
|
+
requiredConfiguration:['PROVIDER_KEY'],
|
|
434
|
+
exportRow(table,row){if(table==='connections')row.enabled=0;return row;}},
|
|
435
|
+
authorize:async request=>(await ownerSession(request))?.actorId??null,
|
|
436
|
+
});
|
|
437
|
+
const response = await handler(request); // null means another route owns it
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Policy, erasure planning, destination maintenance and UI stay in the product.
|
|
441
|
+
Base's default-seed and composed foundation CLI adapters adopt direct local D1
|
|
442
|
+
bindings with explicit identity/configuration exclusions. Their CLI journeys
|
|
443
|
+
exercise archive/reset/restore/archive equivalence. Remote transfer and deployed
|
|
444
|
+
product acceptance remain outside this local adoption.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { all, quote, insist } from './storage.mjs';
|
|
2
|
+
import { canonicalJson } from './hash.mjs';
|
|
3
|
+
import { tokens, identifier, folded } from './generated.mjs';
|
|
4
|
+
import { changesData } from './migration-statements.mjs';
|
|
5
|
+
|
|
6
|
+
// Applied product migrations in the ledger's own order: the deploy ledger when
|
|
7
|
+
// present (it covers any older Wrangler history), else Wrangler's.
|
|
8
|
+
export async function appliedMigrations(db) {
|
|
9
|
+
const ledgers=(await all(db,"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('_rer_deploy_migrations','d1_migrations')")).map(x=>x.name);
|
|
10
|
+
if(ledgers.includes('_rer_deploy_migrations'))return (await all(db,"SELECT name FROM _rer_deploy_migrations WHERE state='applied' ORDER BY name")).map(x=>x.name);
|
|
11
|
+
return ledgers.includes('d1_migrations')?(await all(db,'SELECT name FROM d1_migrations ORDER BY id')).map(x=>x.name):undefined;
|
|
12
|
+
}
|
|
13
|
+
// Where an archive sits in the destination's applied migrations: those its
|
|
14
|
+
// rows passed through (earlier) and those applied after it (unapplied).
|
|
15
|
+
// migrations() is the runner's replay of the destination's applied migrations
|
|
16
|
+
// (migration-history.mjs). The archive's schema must be the replay of the
|
|
17
|
+
// migrations it records or, for archives that predate that record, of an
|
|
18
|
+
// earlier prefix. Restored rows never pass through a later migration, so one
|
|
19
|
+
// that writes or rewrites restorable data is refused, whether or not it changes
|
|
20
|
+
// the schema.
|
|
21
|
+
async function placement(db,manifest,cat,replay,applied) {
|
|
22
|
+
insist(canonicalJson(replay.names)===canonicalJson(applied) && await replay.schemaAfter(applied)===cat.identity,'schema_mismatch');
|
|
23
|
+
let earlier,unapplied;
|
|
24
|
+
if(manifest.migrations) {
|
|
25
|
+
earlier=applied.filter(name=>manifest.migrations.includes(name));
|
|
26
|
+
insist(await replay.schemaAfter(earlier)===manifest.schema,'schema_mismatch');
|
|
27
|
+
unapplied=applied.filter(name=>!earlier.includes(name));
|
|
28
|
+
}else{
|
|
29
|
+
// Unrecorded: the latest matching prefix. Migrations that change no archived
|
|
30
|
+
// table (indexes, excluded tables, data-only changes) cannot be told apart
|
|
31
|
+
// and count as later. The source may have stopped at any matching prefix,
|
|
32
|
+
// so every migration after the earliest one may be missing from its rows.
|
|
33
|
+
const matching=[];
|
|
34
|
+
for(let k=0;k<=applied.length;k++)if(await replay.schemaAfter(applied.slice(0,k))===manifest.schema)matching.push(k);
|
|
35
|
+
insist(matching.length,'schema_mismatch');
|
|
36
|
+
earlier=applied.slice(0,matching.at(-1));
|
|
37
|
+
unapplied=applied.slice(matching[0]);
|
|
38
|
+
}
|
|
39
|
+
// Only rows the archive carries can skip a later migration's change. A table
|
|
40
|
+
// the archive does not hold is restored empty, so triggers and backfills that
|
|
41
|
+
// name only such tables (a newly added module's own) change no restored row.
|
|
42
|
+
const restorable=cat.tables.map(t=>t.name).filter(name=>manifest.tables.some(t=>t.name===name));
|
|
43
|
+
for(const name of unapplied)insist(!replay.statements(name).some(sql=>changesData(sql,restorable)),'schema_mismatch:migration_data:'+name);
|
|
44
|
+
return {earlier};
|
|
45
|
+
}
|
|
46
|
+
// An archive with the destination's schema identity can still predate later
|
|
47
|
+
// migrations that changed only data (a purge or a backfill) or added an index.
|
|
48
|
+
// Those that change restorable data are refused like any other. Without a
|
|
49
|
+
// replayer the statements cannot be read, so an archive whose recorded
|
|
50
|
+
// migrations the destination's ledger has outgrown is refused.
|
|
51
|
+
export async function sameSchemaMigrations(db,manifest,cat,migrations) {
|
|
52
|
+
const applied=await appliedMigrations(db);
|
|
53
|
+
if(!applied)return;
|
|
54
|
+
if(!migrations){insist(!manifest.migrations || applied.every(name=>manifest.migrations.includes(name)),'schema_mismatch:migrations');return;}
|
|
55
|
+
await placement(db,manifest,cat,await migrations(),applied);
|
|
56
|
+
}
|
|
57
|
+
// An archive made before later migrations restores only when those migrations
|
|
58
|
+
// are additive: new tables, new columns that take a default, or indexes (which
|
|
59
|
+
// archives never carry). Anything else stays a schema_mismatch refusal.
|
|
60
|
+
export async function additiveBridge(db,manifest,cat,migrations) {
|
|
61
|
+
insist(migrations,'schema_mismatch');
|
|
62
|
+
const replay=await migrations(),applied=await appliedMigrations(db)??[];
|
|
63
|
+
insist(!manifest.migrations || manifest.migrations.every(name=>applied.includes(name)),'schema_mismatch:migrations');
|
|
64
|
+
const {earlier}=await placement(db,manifest,cat,replay,applied);
|
|
65
|
+
const columns={};
|
|
66
|
+
for(const table of manifest.tables) {
|
|
67
|
+
const target=cat.tables.find(t=>t.name===table.name),label=':'+table.name;
|
|
68
|
+
insist(target,'schema_mismatch:table'+label);
|
|
69
|
+
const kept=target.columns.slice(0,table.columns.length),added=target.columns.slice(table.columns.length);
|
|
70
|
+
insist(canonicalJson(kept)===canonicalJson(table.columns) && table.rowidAlias===target.rowidAlias && table.dependencies.every(d=>target.dependencies.includes(d)),'schema_mismatch:columns'+label);
|
|
71
|
+
if(!added.length){insist(table.sql===target.sql,'schema_mismatch:definition'+label);continue;}
|
|
72
|
+
const declared=await all(db,`PRAGMA table_xinfo(${quote(table.name)})`);
|
|
73
|
+
insist(added.every(c=>!c.pk && !c.generated && (!c.notnull || declared.find(d=>d.name===c.name)?.dflt_value!=null)),'schema_mismatch:column_default'+label);
|
|
74
|
+
insist(appended(table.sql,target.sql,added.map(c=>c.name)),'schema_mismatch:definition'+label);
|
|
75
|
+
columns[table.name]=added.map(c=>c.name);
|
|
76
|
+
}
|
|
77
|
+
return {fromSchema:manifest.schema,toSchema:cat.identity,migrations:applied.filter(name=>!earlier.includes(name)),
|
|
78
|
+
tables:cat.tables.filter(t=>!manifest.tables.some(x=>x.name===t.name)).map(t=>t.name),columns};
|
|
79
|
+
}
|
|
80
|
+
// The destination definition is the archived one with only the added column
|
|
81
|
+
// definitions inserted, as ALTER TABLE ADD COLUMN writes it: no other column,
|
|
82
|
+
// constraint or table option may differ.
|
|
83
|
+
function appended(before,after,names) {
|
|
84
|
+
const a=tokens(before).map(t=>t.text),b=tokens(after),text=b.map(t=>t.text);
|
|
85
|
+
let prefix=0,suffix=0;
|
|
86
|
+
while(prefix<a.length && a[prefix]===text[prefix])prefix++;
|
|
87
|
+
while(suffix<a.length && a[a.length-1-suffix]===text[text.length-1-suffix])suffix++;
|
|
88
|
+
for(let k=Math.max(0,a.length-suffix);k<=prefix;k++) {
|
|
89
|
+
const run=b.slice(k,k+text.length-a.length);
|
|
90
|
+
if(run[0]?.text===',' && definitions(run.slice(1),names))return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function definitions(run,names) {
|
|
95
|
+
const parts=[[]];let depth=0;
|
|
96
|
+
for(const token of run) {
|
|
97
|
+
if(token.text==='(')depth++;
|
|
98
|
+
if(token.text===')' && --depth<0)return false;
|
|
99
|
+
if(token.text===',' && !depth)parts.push([]);else parts.at(-1).push(token);
|
|
100
|
+
}
|
|
101
|
+
return !depth && parts.length===names.length && parts.every((part,i)=>part.length && folded(identifier(part[0]))===folded(names[i]));
|
|
102
|
+
}
|