@runeditrun/rer 0.0.0-stage → 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 +21 -0
- package/README.md +154 -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 +177 -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 +155 -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 +129 -0
- package/global/launch/facts.mjs +93 -0
- package/global/launch/failure.mjs +5 -0
- package/global/launch/local-defaults.mjs +4 -0
- package/global/launch/prepare.mjs +61 -0
- package/global/launch/render.mjs +44 -0
- package/global/launch/state.mjs +97 -0
- package/global/launch/steps.mjs +81 -0
- package/global/launch/worker.mjs +43 -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,43 @@
|
|
|
1
|
+
import { lstat } from 'node:fs/promises';
|
|
2
|
+
import { policy } from './catalogue.mjs';
|
|
3
|
+
import { createPortability } from './index.mjs';
|
|
4
|
+
import { createImport, uploadPart, finalizeImport, abortImport } from './import.mjs';
|
|
5
|
+
import { readLegacyArchive, ArchiveVerifier } from './format.mjs';
|
|
6
|
+
import { fileRecords, exportArchive } from './files.mjs';
|
|
7
|
+
import { ensureLegacyAudit, legacyEmptyTables, requireLegacyEmpty } from './legacy-safety.mjs';
|
|
8
|
+
import { insist, PortabilityError } from './storage.mjs';
|
|
9
|
+
|
|
10
|
+
export async function recoverLegacyArchive({db,buckets,policy:input,migrations},path,output,transient,acknowledged) {
|
|
11
|
+
insist(acknowledged===true,'legacy_acknowledgement_required',409);
|
|
12
|
+
insist(typeof output==='string' && output!==path,'legacy_output_required',409);
|
|
13
|
+
insist(!(await lstat(output).catch(error=>{if(error.code==='ENOENT')return null;throw error;})),'legacy_output_exists',409);
|
|
14
|
+
// Whole-file verification precedes even staging/audit schema writes.
|
|
15
|
+
for await(const record of fileRecords(path,readLegacyArchive)){}
|
|
16
|
+
const context={db,buckets,config:policy(input),migrations},emptyTables=await legacyEmptyTables(db);
|
|
17
|
+
await requireLegacyEmpty(context,emptyTables);
|
|
18
|
+
let id,manifest,closing,integrity,status,complete=false;
|
|
19
|
+
try {
|
|
20
|
+
for await(const record of fileRecords(path,readLegacyArchive)) {
|
|
21
|
+
if(record.kind==='manifest') {
|
|
22
|
+
// This is a local staging envelope, never an assertion of v1 provenance.
|
|
23
|
+
// Normal import/finalize retain their v2 checks and erasure fence.
|
|
24
|
+
manifest={...record,version:2};integrity=new ArchiveVerifier(manifest);
|
|
25
|
+
id=await createImport(context,manifest,'legacy-v1',{
|
|
26
|
+
event:'legacy_v1_unverified_restore',acknowledgedUnverifiedMetadata:true,
|
|
27
|
+
sourceIdentity:record.sourceIdentity,exportedAt:record.exportedAt,
|
|
28
|
+
recoveryOutput:output,emptyTables,
|
|
29
|
+
});
|
|
30
|
+
// A refused schema writes nothing, not even the audit table.
|
|
31
|
+
await ensureLegacyAudit(db);
|
|
32
|
+
}else if(record.kind==='part') {integrity.add(record);await uploadPart(context,id,record.index,record);}
|
|
33
|
+
else closing=record;
|
|
34
|
+
}
|
|
35
|
+
status=await finalizeImport(context,id,{contentHash:closing.contentHash,archiveHash:integrity.digest()});complete=true;
|
|
36
|
+
}finally{if(id&&!complete)await abortImport(context,id);}
|
|
37
|
+
// A fresh export binds the recovered installation from this point forward.
|
|
38
|
+
// A failure here must be reported; the durable restore audit remains intact.
|
|
39
|
+
try {
|
|
40
|
+
const recovered=await exportArchive(await createPortability({db,buckets,policy:input}),output,transient);
|
|
41
|
+
return {...recovered,legacyRecovery:true,unverifiedMetadata:true,restoredContentHash:closing.contentHash,...(status.bridged?{bridged:status.bridged}:{})};
|
|
42
|
+
}catch{throw new PortabilityError('legacy_restored_export_failed',500);}
|
|
43
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { all, statement, guard, quote, insist } from './storage.mjs';
|
|
2
|
+
import { canonicalJson } from './hash.mjs';
|
|
3
|
+
|
|
4
|
+
// Recovery requires a new installation, including excluded identity tables and
|
|
5
|
+
// normally replaceable product rows. Only SQLite/migration/transfer bookkeeping
|
|
6
|
+
// may already exist. Do not use the product's export exclusions for this check.
|
|
7
|
+
export async function legacyEmptyTables(db) {
|
|
8
|
+
const tables=await all(db,"SELECT name FROM sqlite_master WHERE type='table' AND sql IS NOT NULL ORDER BY name");
|
|
9
|
+
return tables.map(t=>t.name).filter(name=>!(/^(sqlite_|_cf_|rer_portability_)/i.test(name) || ['d1_migrations','_rer_deploy_migrations','_rer_portability_migrations'].includes(name.toLowerCase())));
|
|
10
|
+
}
|
|
11
|
+
export async function requireLegacyEmpty({db,buckets},tables) {
|
|
12
|
+
for(const table of tables)insist(!(await statement(db,`SELECT 1 FROM ${quote(table)} LIMIT 1`).first()),'legacy_target_not_empty:'+table,409);
|
|
13
|
+
for(const [name,bucket] of Object.entries(buckets))insist(!(await bucket.list({limit:1})).objects.length,'legacy_bucket_not_empty:'+name,409);
|
|
14
|
+
}
|
|
15
|
+
export function legacyEmptyGuards(db,row) {
|
|
16
|
+
return (row.metadata.legacyRecovery?.emptyTables??[]).map(table=>guard(db,`NOT EXISTS(SELECT 1 FROM ${quote(table)})`));
|
|
17
|
+
}
|
|
18
|
+
export async function ensureLegacyAudit(db) {
|
|
19
|
+
// Additive sidecar, installed only by recovery; existing product migrations
|
|
20
|
+
// and transfer cleanup cannot remove the completed recovery audit.
|
|
21
|
+
await db.prepare('CREATE TABLE IF NOT EXISTS rer_portability_recovery_audit (id TEXT PRIMARY KEY, recorded_at TEXT NOT NULL, details TEXT NOT NULL)').run();
|
|
22
|
+
}
|
|
23
|
+
export function legacyAuditStatements(db,row) {
|
|
24
|
+
if(!row.metadata.legacyRecovery)return [];
|
|
25
|
+
const {emptyTables,...details}=row.metadata.legacyRecovery;
|
|
26
|
+
return [statement(db,'INSERT INTO rer_portability_recovery_audit VALUES(?,?,?)',row.id,new Date().toISOString(),canonicalJson({...details,contentHash:row.metadata.contentHash,...(row.metadata.bridge?{bridged:row.metadata.bridge}:{})}))];
|
|
27
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Read one bounded UTF-8 line at a time; never materialize the complete archive. */
|
|
2
|
+
export async function* archiveLines(source, maxLineBytes) {
|
|
3
|
+
if (!Number.isSafeInteger(maxLineBytes) || maxLineBytes < 1) throw new Error('Invalid archive line limit.');
|
|
4
|
+
const reader = source.getReader();
|
|
5
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
6
|
+
let buffer = new Uint8Array(Math.min(maxLineBytes, 65_536)), bytes = 0;
|
|
7
|
+
function append(fragment) {
|
|
8
|
+
const required = bytes + fragment.byteLength;
|
|
9
|
+
if (required > maxLineBytes) throw new Error('An archive line exceeds the supported size.');
|
|
10
|
+
if (required > buffer.length) {
|
|
11
|
+
const grown = new Uint8Array(Math.min(maxLineBytes, Math.max(required, buffer.length * 2)));
|
|
12
|
+
grown.set(buffer.subarray(0, bytes)); buffer = grown;
|
|
13
|
+
}
|
|
14
|
+
buffer.set(fragment, bytes); bytes = required;
|
|
15
|
+
}
|
|
16
|
+
function line() {
|
|
17
|
+
const value = decoder.decode(buffer.subarray(0, bytes)); bytes = 0;
|
|
18
|
+
return value.endsWith('\r') ? value.slice(0, -1) : value;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
for (;;) {
|
|
22
|
+
const next = await reader.read();
|
|
23
|
+
if (next.done) break;
|
|
24
|
+
let offset = 0;
|
|
25
|
+
for (let index = 0; index < next.value.length; index++) {
|
|
26
|
+
if (next.value[index] !== 10) continue;
|
|
27
|
+
append(next.value.subarray(offset, index));
|
|
28
|
+
yield line(); offset = index + 1;
|
|
29
|
+
}
|
|
30
|
+
append(next.value.subarray(offset));
|
|
31
|
+
}
|
|
32
|
+
if (bytes) yield line();
|
|
33
|
+
} finally {
|
|
34
|
+
await reader.cancel(); reader.releaseLock();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { policy, schemaIdentity } from './catalogue.mjs';
|
|
3
|
+
|
|
4
|
+
// Replays a chosen set of the destination's migrations, in its applied order,
|
|
5
|
+
// into a scratch database and returns the resulting archive schema identity.
|
|
6
|
+
// Node runners only: restore uses it to place an older archive within the
|
|
7
|
+
// destination's history (see bridge.mjs). plan: [{name,statements}].
|
|
8
|
+
export function migrationReplay(plan,input) {
|
|
9
|
+
const config=policy(input);
|
|
10
|
+
return {names:plan.map(migration=>migration.name),statements:name=>plan.find(m=>m.name===name).statements,async schemaAfter(names) {
|
|
11
|
+
const scratch=new DatabaseSync(':memory:');
|
|
12
|
+
const db={prepare:sql=>{const query=scratch.prepare(sql);let values=[];return {bind(...args){values=args;return this;},async all(){return {results:query.all(...values)};}};}};
|
|
13
|
+
try {
|
|
14
|
+
for(const migration of plan.filter(m=>names.includes(m.name)))for(const sql of migration.statements)scratch.exec(sql);
|
|
15
|
+
return await schemaIdentity(db,config);
|
|
16
|
+
}finally{scratch.close();}
|
|
17
|
+
}};
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { tokens, identifier, folded } from './generated.mjs';
|
|
2
|
+
|
|
3
|
+
// Whether one migration statement can change rows a restore carries. Only
|
|
4
|
+
// CREATE TABLE (not AS SELECT), CREATE INDEX and ALTER TABLE ... ADD COLUMN
|
|
5
|
+
// are additive: SQLite refuses ADD COLUMN with a non-constant default, so a
|
|
6
|
+
// restored row takes the same value the migration gave existing rows. Any
|
|
7
|
+
// other statement (INSERT, UPDATE, DELETE, triggers, table rebuilds) changes
|
|
8
|
+
// data unless it names none of the restorable tables, as when it only touches
|
|
9
|
+
// operator-auth or other excluded tables.
|
|
10
|
+
export function changesData(sql,restorable) {
|
|
11
|
+
const list=tokens(sql),words=list.map(t=>t.quoted?undefined:folded(t.text));
|
|
12
|
+
const kind=words.slice(0,4).filter(w=>!['temp','temporary','unique'].includes(w)).slice(0,2).join(' ');
|
|
13
|
+
if(kind==='create table')return topLevelWords(list,words).includes('as');
|
|
14
|
+
if(kind==='create index')return false;
|
|
15
|
+
if(kind==='alter table' && words.find((w,i)=>i>2 && ['add','rename','drop'].includes(w))==='add')return false;
|
|
16
|
+
const names=new Set(restorable.map(folded));
|
|
17
|
+
return list.some(t=>t.quoted ? !t.text.startsWith("'") && names.has(folded(identifier(t))) : t.text.split('.').some(part=>names.has(folded(part))));
|
|
18
|
+
}
|
|
19
|
+
function topLevelWords(list,words) {
|
|
20
|
+
let depth=0;const result=[];
|
|
21
|
+
list.forEach((t,i)=>{if(t.text==='(')depth++;else if(t.text===')')depth--;else if(!depth)result.push(words[i]);});
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { stream, run, all, insist } from './storage.mjs';
|
|
2
|
+
import { base64, unbase64, StreamHash } from './hash.mjs';
|
|
3
|
+
export async function* byteChunks(source,limit) {
|
|
4
|
+
const reader=(source instanceof ReadableStream?source:new Response(source).body).getReader();
|
|
5
|
+
let pending=new Uint8Array(limit),used=0;
|
|
6
|
+
try {
|
|
7
|
+
for(;;) {
|
|
8
|
+
const {value,done}=await reader.read();if(done)break;
|
|
9
|
+
let offset=0;
|
|
10
|
+
while(offset<value.length) {
|
|
11
|
+
const n=Math.min(limit-used,value.length-offset);pending.set(value.subarray(offset,offset+n),used);offset+=n;used+=n;
|
|
12
|
+
if(used===limit){yield pending;pending=new Uint8Array(limit);used=0;}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if(used)yield pending.subarray(0,used);
|
|
16
|
+
} finally {await reader.cancel();reader.releaseLock();}
|
|
17
|
+
}
|
|
18
|
+
export async function capturePayload(db,id,number,source) {
|
|
19
|
+
const hash=new StreamHash();let size=0,segment=0;
|
|
20
|
+
for await(const bytes of byteChunks(source,16000)) {
|
|
21
|
+
hash.update(bytes);size+=bytes.length;
|
|
22
|
+
await run(db,'INSERT INTO rer_portability_segments VALUES(?,?,?,?)',id,-1-number,segment++,base64(bytes));
|
|
23
|
+
}
|
|
24
|
+
return {size,sha256:hash.digest()};
|
|
25
|
+
}
|
|
26
|
+
export async function payloadRange(db,id,number,offset,length) {
|
|
27
|
+
const rows=await all(db,'SELECT segment,data FROM rer_portability_segments WHERE job=? AND part=? AND segment>=? AND segment<=? ORDER BY segment',id,-1-number,Math.floor(offset/16000),Math.floor((offset+length-1)/16000));
|
|
28
|
+
const result=new Uint8Array(length);let used=0;
|
|
29
|
+
for(const row of rows) {
|
|
30
|
+
const bytes=unbase64(row.data),start=Math.max(0,offset-row.segment*16000),n=Math.min(bytes.length-start,length-used);
|
|
31
|
+
result.set(bytes.subarray(start,start+n),used);used+=n;
|
|
32
|
+
}
|
|
33
|
+
insist(used===length,'payload_changed');return result;
|
|
34
|
+
}
|
|
35
|
+
export async function writeObject(bucket,descriptor,source) {
|
|
36
|
+
const metadata={httpMetadata:descriptor.httpMetadata?{...descriptor.httpMetadata}:undefined,customMetadata:descriptor.customMetadata};
|
|
37
|
+
if(metadata.httpMetadata?.cacheExpiry)metadata.httpMetadata.cacheExpiry=new Date(metadata.httpMetadata.cacheExpiry);
|
|
38
|
+
const hash=new StreamHash();let size=0;
|
|
39
|
+
if(descriptor.size<=5*1024*1024) {
|
|
40
|
+
const bytes=new Uint8Array(descriptor.size);
|
|
41
|
+
for await(const chunk of byteChunks(source,65536)) {insist(size+chunk.length<=bytes.length,'binary_size');bytes.set(chunk,size);hash.update(chunk);size+=chunk.length;}
|
|
42
|
+
insist(size===descriptor.size && hash.digest()===descriptor.sha256,'binary_checksum');
|
|
43
|
+
await bucket.put(descriptor.key,bytes,metadata);return;
|
|
44
|
+
}
|
|
45
|
+
const upload=await bucket.createMultipartUpload(descriptor.key,metadata),parts=[];
|
|
46
|
+
try {
|
|
47
|
+
for await(const chunk of byteChunks(source,5*1024*1024)) {
|
|
48
|
+
hash.update(chunk);size+=chunk.length;parts.push(await upload.uploadPart(parts.length+1,chunk));
|
|
49
|
+
}
|
|
50
|
+
insist(size===descriptor.size && hash.digest()===descriptor.sha256,'binary_checksum');await upload.complete(parts);
|
|
51
|
+
} catch(e) {await upload.abort();throw e;}
|
|
52
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { all, quote, insist } from './storage.mjs';
|
|
2
|
+
import { sidecar, copyRowid } from './copies.mjs';
|
|
3
|
+
const TRANSIENT=['oa_setup_links','rer_dependency_keys','rer_dependency_pending','rer_dependency_codes','oa_provider_sessions','oa_provider_session_pins','oa_oidc_transactions','oa_step_up_challenges','oa_identity_provider_health'];
|
|
4
|
+
// Mandatory secret/session filtering even when a product explicitly includes a table.
|
|
5
|
+
export async function dropProviderData(db,tables,copies) {
|
|
6
|
+
const commands=[];
|
|
7
|
+
for(const table of tables) {
|
|
8
|
+
const copy=copies[table.name];if(!copy)continue;
|
|
9
|
+
const condition=TRANSIENT.includes(table.name)?'1':table.name==='oa_sessions' && table.columns.some(c=>c.name==='kind')?"kind='oidc'":null;
|
|
10
|
+
if(condition) {
|
|
11
|
+
commands.push(db.prepare(`DELETE FROM ${quote(sidecar(copy))} WHERE rer_portability_copy_rowid IN (SELECT ${copyRowid(table)} FROM ${quote(copy)} WHERE ${condition})`));
|
|
12
|
+
commands.push(db.prepare(`DELETE FROM ${quote(copy)} WHERE ${condition}`));
|
|
13
|
+
}
|
|
14
|
+
if(table.name==='rer_dependency_link')commands.push(db.prepare(`UPDATE ${quote(copy)} SET needs_relink=1`));
|
|
15
|
+
if(table.name==='oa_identity_providers')commands.push(db.prepare(`UPDATE ${quote(copy)} SET status='unlinked',jwks=NULL,pending_issuer=NULL,pending_client_id=NULL,pending_preset=NULL,pending_at=NULL`));
|
|
16
|
+
if(table.name==='oa_auth_settings')commands.push(db.prepare(`UPDATE ${quote(copy)} SET idp_sign_in=0,first_setup=0,generation=generation+1`));
|
|
17
|
+
}
|
|
18
|
+
if(commands.length)await db.batch(commands);
|
|
19
|
+
}
|
|
20
|
+
export async function refuseProviderData(db,copies,tables) {
|
|
21
|
+
for(const name of TRANSIENT) if(copies[name])insist(!(await all(db,`SELECT 1 FROM ${quote(copies[name])} LIMIT 1`)).length,'provider_transient_data',409);
|
|
22
|
+
if(copies.oa_sessions && tables.find(t=>t.name==='oa_sessions')?.columns.some(c=>c.name==='kind'))insist(!(await all(db,`SELECT 1 FROM ${quote(copies.oa_sessions)} WHERE kind='oidc' LIMIT 1`)).length,'provider_session_data',409);
|
|
23
|
+
if(copies.rer_dependency_link)insist(!(await all(db,`SELECT 1 FROM ${quote(copies.rer_dependency_link)} WHERE needs_relink!=1 LIMIT 1`)).length,'provider_relink_required',409);
|
|
24
|
+
if(copies.oa_auth_settings)insist(!(await all(db,`SELECT 1 FROM ${quote(copies.oa_auth_settings)} WHERE idp_sign_in!=0 OR first_setup!=0 LIMIT 1`)).length,'provider_sign_in_must_be_off',409);
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { all,statement,guard,quote,insist } from './storage.mjs';
|
|
2
|
+
const identifier=value=>value.replace(/[A-Z]/g,c=>c.toLowerCase());
|
|
3
|
+
|
|
4
|
+
export async function excludedDependentGuards(db,targets,replaceable) {
|
|
5
|
+
if(!replaceable.length)return [];
|
|
6
|
+
const tables=await all(db,"SELECT name,sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL ORDER BY name"),references=[];
|
|
7
|
+
// REFERENCES is required in every FK declaration. Tables without that keyword have no
|
|
8
|
+
// FKs, including D1's protected metadata tables which refuse PRAGMA inspection.
|
|
9
|
+
for(const table of tables.filter(t=>/\bREFERENCES\b/i.test(t.sql)))for(const fk of await all(db,`PRAGMA foreign_key_list(${quote(table.name)})`)) {
|
|
10
|
+
const parent=tables.find(t=>identifier(t.name)===identifier(fk.table))?.name??fk.table;
|
|
11
|
+
if([fk.on_delete,fk.on_update].some(action=>['CASCADE','SET NULL','SET DEFAULT'].includes(action)))references.push({child:table.name,parent});
|
|
12
|
+
}
|
|
13
|
+
const affected=new Set(replaceable),protectedTables=new Map();
|
|
14
|
+
let changed=true;
|
|
15
|
+
while(changed) {
|
|
16
|
+
changed=false;
|
|
17
|
+
for(const {child,parent} of references)if(affected.has(parent)) {
|
|
18
|
+
if(!targets.some(t=>t.name===child))protectedTables.set(child,parent);
|
|
19
|
+
if(!affected.has(child)){affected.add(child);changed=true;}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const guards=[];
|
|
23
|
+
for(const [child,parent] of protectedTables) {
|
|
24
|
+
// Conservatively refuse populated excluded dependents, including transitive actions.
|
|
25
|
+
// Repeat this condition inside the atomic commit to catch rows added after validation.
|
|
26
|
+
insist(!(await statement(db,`SELECT 1 FROM ${quote(child)} LIMIT 1`).first()),'excluded_destination_dependents:'+child+':'+parent,409);
|
|
27
|
+
guards.push(guard(db,`NOT EXISTS(SELECT 1 FROM ${quote(child)})`));
|
|
28
|
+
}
|
|
29
|
+
return guards;
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
CREATE TABLE rer_portability_state (
|
|
2
|
+
id INTEGER PRIMARY KEY CHECK(id=1), source_identity TEXT NOT NULL, epoch INTEGER NOT NULL DEFAULT 0,
|
|
3
|
+
import_job TEXT, applying INTEGER NOT NULL DEFAULT 0
|
|
4
|
+
);
|
|
5
|
+
CREATE TABLE rer_portability_jobs (
|
|
6
|
+
id TEXT PRIMARY KEY, kind TEXT NOT NULL, actor TEXT NOT NULL, phase TEXT NOT NULL,
|
|
7
|
+
completed INTEGER NOT NULL DEFAULT 0, metadata TEXT NOT NULL
|
|
8
|
+
);
|
|
9
|
+
CREATE TABLE rer_portability_parts (
|
|
10
|
+
job TEXT NOT NULL REFERENCES rer_portability_jobs(id) ON DELETE CASCADE,
|
|
11
|
+
idx INTEGER NOT NULL, sha256 TEXT NOT NULL, PRIMARY KEY(job,idx)
|
|
12
|
+
);
|
|
13
|
+
CREATE TABLE rer_portability_segments (
|
|
14
|
+
job TEXT NOT NULL REFERENCES rer_portability_jobs(id) ON DELETE CASCADE,
|
|
15
|
+
part INTEGER NOT NULL, segment INTEGER NOT NULL, data TEXT NOT NULL, PRIMARY KEY(job,part,segment)
|
|
16
|
+
);
|
|
17
|
+
CREATE TABLE rer_portability_owned (
|
|
18
|
+
job TEXT NOT NULL REFERENCES rer_portability_jobs(id) ON DELETE CASCADE,
|
|
19
|
+
bucket TEXT NOT NULL, key TEXT NOT NULL, PRIMARY KEY(job,bucket,key)
|
|
20
|
+
);
|
|
21
|
+
CREATE TABLE rer_portability_journal (
|
|
22
|
+
source TEXT NOT NULL, tombstone TEXT NOT NULL, PRIMARY KEY(source,tombstone)
|
|
23
|
+
);
|
|
24
|
+
CREATE TABLE rer_portability_epochs (source TEXT PRIMARY KEY, epoch INTEGER NOT NULL);
|
|
25
|
+
CREATE TABLE rer_portability_guard (value INTEGER NOT NULL CHECK(value=1));
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { canonicalJson } from './hash.mjs';
|
|
2
|
+
export class PortabilityError extends Error {
|
|
3
|
+
constructor(code, status=400) { super(code); this.code=code; this.status=status; }
|
|
4
|
+
}
|
|
5
|
+
export function insist(condition, code, status=400) { if (!condition) throw new PortabilityError(code,status); }
|
|
6
|
+
export const quote = name => '"'+name.replaceAll('"','""')+'"';
|
|
7
|
+
export const statement = (db,sql,...values) => values.length ? db.prepare(sql).bind(...values) : db.prepare(sql);
|
|
8
|
+
export const all = async (db,sql,...values) => (await statement(db,sql,...values).all()).results;
|
|
9
|
+
export const run = (db,sql,...values) => statement(db,sql,...values).run();
|
|
10
|
+
export function guard(db,condition,...values) {
|
|
11
|
+
return statement(db,`INSERT INTO rer_portability_guard(value) SELECT CASE WHEN (${condition}) THEN 1 ELSE 0 END`,...values);
|
|
12
|
+
}
|
|
13
|
+
export async function state(db) {
|
|
14
|
+
await run(db,'INSERT OR IGNORE INTO rer_portability_state(id,source_identity) VALUES(1,?)',crypto.randomUUID());
|
|
15
|
+
return statement(db,'SELECT * FROM rer_portability_state WHERE id=1').first();
|
|
16
|
+
}
|
|
17
|
+
export async function job(db,id,kind) {
|
|
18
|
+
const row=await statement(db,'SELECT * FROM rer_portability_jobs WHERE id=?',id).first();
|
|
19
|
+
insist(row && (!kind || row.kind===kind),'job_not_found',404);
|
|
20
|
+
return {...row, metadata:JSON.parse(row.metadata)};
|
|
21
|
+
}
|
|
22
|
+
export function save(db,row) {
|
|
23
|
+
return statement(db,'UPDATE rer_portability_jobs SET phase=?,completed=?,metadata=? WHERE id=?',row.phase,row.completed,canonicalJson(row.metadata),row.id);
|
|
24
|
+
}
|
|
25
|
+
export function partStatements(db,id,part) {
|
|
26
|
+
const json=canonicalJson(part), result=[statement(db,'INSERT INTO rer_portability_parts VALUES(?,?,?)',id,part.index,part.sha256)];
|
|
27
|
+
for(let i=0,segment=0;i<json.length;segment++) {
|
|
28
|
+
let end=Math.min(i+32000,json.length);
|
|
29
|
+
const last=json.charCodeAt(end-1),next=json.charCodeAt(end);
|
|
30
|
+
if(last>=0xd800 && last<=0xdbff && next>=0xdc00 && next<=0xdfff) end--;
|
|
31
|
+
result.push(statement(db,'INSERT INTO rer_portability_segments VALUES(?,?,?,?)',id,part.index,segment,json.slice(i,end)));i=end;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
export async function readPart(db,id,index) {
|
|
36
|
+
const rows=await all(db,'SELECT data FROM rer_portability_segments WHERE job=? AND part=? ORDER BY segment',id,index);
|
|
37
|
+
insist(rows.length,'part_missing');
|
|
38
|
+
return JSON.parse(rows.map(x=>x.data).join(''));
|
|
39
|
+
}
|
|
40
|
+
export async function* parts(db,id,count) { for(let i=0;i<count;i++) yield await readPart(db,id,i); }
|
|
41
|
+
export function stream(generator) {
|
|
42
|
+
const iterator=generator[Symbol.asyncIterator]();
|
|
43
|
+
return new ReadableStream({async pull(controller){try{const next=await iterator.next();if(next.done)controller.close();else controller.enqueue(next.value);}catch(e){controller.error(e);}},async cancel(){await iterator.return?.();}});
|
|
44
|
+
}
|
|
45
|
+
export function transferTables(row) { return Object.values(row.metadata.copies??{}).flatMap(name=>[name,name+'_meta']); }
|
|
46
|
+
export async function cleanCopies(db,row) {
|
|
47
|
+
await db.batch([...transferTables(row).map(name=>db.prepare(`DROP TABLE IF EXISTS ${quote(name)}`)),
|
|
48
|
+
statement(db,'DELETE FROM rer_portability_parts WHERE job=?',row.id),statement(db,'DELETE FROM rer_portability_segments WHERE job=?',row.id)]);
|
|
49
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# `rer release`
|
|
2
|
+
|
|
3
|
+
Before a Base release, run `pnpm verify:scale` for the full import-kit datasets. It
|
|
4
|
+
is not part of `pnpm verify`: run it on a machine with at least 16 GB of memory and
|
|
5
|
+
nothing else heavy running, because its D1 datasets are large.
|
|
6
|
+
|
|
7
|
+
Set `PUBLIC_PROJECT` to the edition's project identity and `PUBLIC_REPOSITORY`
|
|
8
|
+
to its public GitHub destination URL. From Base, run:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pnpm rer release --repo /path/to/seed --ref main \
|
|
12
|
+
--project "$PUBLIC_PROJECT" --repository "$PUBLIC_REPOSITORY" \
|
|
13
|
+
--output /path/to/new-release-directory
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The `rer` bin and `node bin/rer.mjs` expose the same command. Node 22+, Python 3,
|
|
17
|
+
Git, pnpm and tar are required. Use a full, clean seed clone with its declared
|
|
18
|
+
package-manager version and browser prerequisites available. The producer reads
|
|
19
|
+
committed `seed.json`, `package.json`, `RELEASE.json`, `LICENSE`, `CONTRACT.md` and
|
|
20
|
+
`AGENTS.md`. Version declarations must agree. `--project` declares the publishing
|
|
21
|
+
identity; `--repository` is the credential-free public destination URL. Neither is inferred
|
|
22
|
+
from repository naming or old site listings.
|
|
23
|
+
|
|
24
|
+
When either `edition.json` or `rer-project.json` exists, both must be ordinary
|
|
25
|
+
committed JSON objects. Before installing dependencies, running verification,
|
|
26
|
+
creating output or tagging, the producer requires `edition.json.id` and
|
|
27
|
+
`rer-project.json.projectId` to agree with `--project`. Separately,
|
|
28
|
+
`rer-project.json.sourceRepository` must agree with `--repository`. If
|
|
29
|
+
`edition.json.custody.repository` is present it must agree with `--repository`
|
|
30
|
+
too; the custody declaration is optional. The project identity may differ from
|
|
31
|
+
the repository identity. Repository fields are credential-free HTTPS GitHub
|
|
32
|
+
URLs; identity comparison ignores case and a terminal `.git` in URLs. Missing,
|
|
33
|
+
malformed, partial or inconsistent identities fail closed. The selected
|
|
34
|
+
committed tree supplies the declarations.
|
|
35
|
+
|
|
36
|
+
The platform reads these same HTTPS GitHub URLs, plus bare an owner and repository name separated by a slash for releases that carry it; ordinary release editions still require the URL here.
|
|
37
|
+
|
|
38
|
+
Every publication scan receives the explicit project and destination: source
|
|
39
|
+
history, staged final annotations, parentless exports and dry runs. If GitHub's
|
|
40
|
+
API returns a complete Not Found response with HTTP status 404 for that exact
|
|
41
|
+
agreed repository destination, references to it are `pending-public-destination`
|
|
42
|
+
with severity `INFO` in the report's `info` array. Other missing repositories and
|
|
43
|
+
existing private repositories, including the agreed target, still fail. A distinct project identity receives ordinary
|
|
44
|
+
reference checks. Empty, malformed or unresolved API responses remain
|
|
45
|
+
INCOMPLETE. This rule does not exempt other defects in the same file or message
|
|
46
|
+
and does not authorize publication. Legacy sources with neither declaration
|
|
47
|
+
accept independent project and repository inputs, retain strict reference checks
|
|
48
|
+
and cannot receive this INFO classification.
|
|
49
|
+
|
|
50
|
+
It installs frozen dependencies and runs `pnpm run verify` in a disposable clone
|
|
51
|
+
of the exact selected commit, then scans the original repository's complete
|
|
52
|
+
local-ref history with publish-check. A verify that mutates tracked source fails.
|
|
53
|
+
When declared in the committed package, it runs `pnpm run verify:release` after
|
|
54
|
+
default verification succeeds. Keep capacity, maximum-size memory and load cases
|
|
55
|
+
in that release-only script. Both dry runs and releases require it to pass;
|
|
56
|
+
`gates.verify.release` records its result. Routine CI and default verification
|
|
57
|
+
do not invoke it.
|
|
58
|
+
Child logs are suppressed to avoid credential disclosure; the receipt retains
|
|
59
|
+
exit status and numeric final summaries from Node's spec/TAP reporters and
|
|
60
|
+
Playwright's text reporters. Counts add across suites: `tests`, `passed`,
|
|
61
|
+
`failed`, `skipped` and `flaky`; nonzero `cancelled`, `todo`, `interrupted` and
|
|
62
|
+
`notRun` counts are retained when reported. Playwright's total includes each
|
|
63
|
+
outcome once, with flaky tests counted separately from passed tests. Node's
|
|
64
|
+
reported total is used directly. Unrecognized output is suppressed and contributes
|
|
65
|
+
no counts; the child exit status still determines the verification gate. Source
|
|
66
|
+
metadata and Git blobs are never loaded as producer code.
|
|
67
|
+
|
|
68
|
+
With `--tag`, passing gates produce `public-source.tar`, containing the checked tree's
|
|
69
|
+
exact ordinary files without Git history, and a hash-pinned site bundle:
|
|
70
|
+
`site/<id>/<version>/release.json`, published documents and `site/registry.json`.
|
|
71
|
+
Export attributes cannot silently omit or substitute bytes. The site metadata
|
|
72
|
+
comes from committed seed and contract data; no screenshots, install guarantees
|
|
73
|
+
or verified maturity claims are invented. Copy the reviewed bundle into the site
|
|
74
|
+
release registry through its normal review process; the one-entry generated
|
|
75
|
+
registry is input for that integration, not a replacement for the site's registry.
|
|
76
|
+
|
|
77
|
+
By default, success writes only the receipt, publication report and site preview;
|
|
78
|
+
its provenance says `publication: dry-run`. It creates no tag or source archive.
|
|
79
|
+
Pass **`--tag`** to create an annotated local `v<version>` tag at the verified
|
|
80
|
+
commit and the exact source export. Existing tags are never changed. The command
|
|
81
|
+
**never pushes,
|
|
82
|
+
changes repository visibility, dispatches Actions, deploys or edits a site**.
|
|
83
|
+
Publication and deployment retain their separate owner authorization.
|
|
84
|
+
|
|
85
|
+
`--dry-run` performs the real install, verify and history scan and suppresses
|
|
86
|
+
tag creation even with `--tag`. `--tag --dry-run` also prepares the source archive
|
|
87
|
+
for inspection. Existing tags do not prevent previews.
|
|
88
|
+
If gates fail, it exits 1, omits the source export, records BLOCKED in
|
|
89
|
+
`release-receipt.json` and still prepares a site preview whose provenance says
|
|
90
|
+
`publication: blocked`, with the actual gate results. That preview is for local
|
|
91
|
+
review and must not enter the public registry. Blocked `--tag` releases without
|
|
92
|
+
`--dry-run` produce only the receipt and redacted publication report. A bare
|
|
93
|
+
blocked release still writes the blocked preview. Invalid inputs exit 2. Outputs
|
|
94
|
+
must be new directories outside the source and may not traverse symlinks.
|
|
95
|
+
|
|
96
|
+
The first producer supports MIT seed listings and ordinary-file source trees.
|
|
97
|
+
Service integration bundles remain outside this command. Run producer tests
|
|
98
|
+
with `pnpm verify:release`; fixtures never replace seed verification in a pilot.
|
|
99
|
+
|
|
100
|
+
For a first public release whose private development history must stay private,
|
|
101
|
+
add `--clean-history --env-file /path/to/credentials.env`. The default history
|
|
102
|
+
gate is unchanged. This explicit mode constructs an ordinary-file parentless Git
|
|
103
|
+
repository, verifies its tree equals the selected source tree, and runs the full
|
|
104
|
+
history scanner against that actual public export, including environment hashes.
|
|
105
|
+
Passing tagged releases include `public-git/` with `main` and an annotated release
|
|
106
|
+
tag. The private source tag points to the verified private commit; the export tag
|
|
107
|
+
and site provenance point to the parentless public commit. The receipt records
|
|
108
|
+
both identities and `publicationHistory: parentless-export`. Never push private
|
|
109
|
+
source history to the public repository. Neither mode publishes anything.
|
|
110
|
+
|
|
111
|
+
Public export commit and annotated-tag messages contain the version and optional
|
|
112
|
+
`RELEASE.json.publicReleaseNotes` only. Private source commit identities stay in
|
|
113
|
+
the private release receipt. The clean export's final annotated tag exists
|
|
114
|
+
before publication inspection, and the checked repository is copied unchanged
|
|
115
|
+
into `public-git/`. Ordinary releases inspect their source history and stage the
|
|
116
|
+
exact final annotation in a disposable checkout for inspection before creating
|
|
117
|
+
the source tag. Release notes pass the same content rules as the public tree.
|
|
118
|
+
|
|
119
|
+
## Releasing a seed that is already public
|
|
120
|
+
|
|
121
|
+
Use `--onto` to create one public child of the destination's current branch from
|
|
122
|
+
an independently developed private source tree:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
pnpm rer release --repo /path/to/private-seed --ref main \
|
|
126
|
+
--project "$PUBLIC_PROJECT" --repository "$PUBLIC_REPOSITORY" \
|
|
127
|
+
--onto "$PUBLIC_REPOSITORY#main" --tag v1.2.3 --dry-run \
|
|
128
|
+
--env-file ~/.env --output /path/to/new-release-directory
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The committed version must match the tag. The credential-free `--onto` URL must
|
|
132
|
+
match `--repository` and the committed destination declarations; its explicit
|
|
133
|
+
branch must exist in a confirmed PUBLIC repository. Pending, private, unresolved
|
|
134
|
+
or ambiguous destinations and an existing public version tag are refused.
|
|
135
|
+
`--onto` and `--clean-history` are mutually exclusive.
|
|
136
|
+
|
|
137
|
+
The producer pins the advertised public tip, installs frozen dependencies and
|
|
138
|
+
runs the exact selected private commit's real verification. It builds an
|
|
139
|
+
isolated public repository containing only public ancestry and the selected
|
|
140
|
+
source blobs. The new commit has exactly the pinned public tip as its sole
|
|
141
|
+
parent, and its tree must equal the private source tree, including executable
|
|
142
|
+
modes and raw blob bytes. Attributes cannot change or omit source content.
|
|
143
|
+
Private source commits and refs are never imported. Public commit and annotated
|
|
144
|
+
tag messages contain only `vVERSION` and optional trimmed
|
|
145
|
+
`RELEASE.json.publicReleaseNotes`.
|
|
146
|
+
|
|
147
|
+
The actual final annotation is staged before the real publication inspection,
|
|
148
|
+
which receives `--public-base` for the declared destination. Only PASS with
|
|
149
|
+
complete history inspection and that exact pinned base can produce artifacts.
|
|
150
|
+
The destination's visibility, branch tip and absent version tag are checked
|
|
151
|
+
again after scanning and immediately before final handoff. A changed, removed,
|
|
152
|
+
unresolved or ambiguous head blocks the artifact; start again against the new
|
|
153
|
+
public tip. The producer never pushes anything.
|
|
154
|
+
|
|
155
|
+
A passing tagged run writes `public-git/`, `public-source.tar`, the site bundle,
|
|
156
|
+
`publish-check.json`, `release-receipt.json` and `PUBLISH.md`. The receipt uses
|
|
157
|
+
`publicationHistory: public-child`, names `publicParent`, and records the public
|
|
158
|
+
commit, exact tree and annotated tag object. Site provenance names the public
|
|
159
|
+
child. `--tag vVERSION --dry-run` prepares and scans the same public tag and
|
|
160
|
+
handoff without changing any private source tag. Without `--dry-run`, success
|
|
161
|
+
also annotates the selected private source commit as in existing modes. An
|
|
162
|
+
untagged run remains a preview without public Git/archive/handoff artifacts.
|
|
163
|
+
|
|
164
|
+
Review the report and receipt before publishing. The generated `PUBLISH.md`
|
|
165
|
+
contains this complete shell sequence with concrete artifact-specific values;
|
|
166
|
+
the angle-bracket values below mark those substitutions. Run the generated
|
|
167
|
+
commands as written, from any directory, without changing their values:
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
set -eu
|
|
171
|
+
artifact='<ARTIFACT_DIRECTORY>'
|
|
172
|
+
cd -- "$artifact/public-git"
|
|
173
|
+
release_output=$(node -e 'const fs=require("node:fs"),c=require("node:crypto"); process.stdout.write(c.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$artifact/release-receipt.json") || exit 1
|
|
174
|
+
test "$release_output" = '<RELEASE_RECEIPT_SHA256>'
|
|
175
|
+
release_output=$(node -e 'const fs=require("node:fs"),c=require("node:crypto"); process.stdout.write(c.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$artifact/publish-check.json") || exit 1
|
|
176
|
+
test "$release_output" = '<PUBLISH_CHECK_SHA256>'
|
|
177
|
+
release_output=$(node -e 'const fs=require("node:fs"),c=require("node:crypto"); process.stdout.write(c.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$artifact/public-git/.git/config") || exit 1
|
|
178
|
+
test "$release_output" = '<PUBLIC_GIT_CONFIG_SHA256>'
|
|
179
|
+
# Discard inherited Git overrides while keeping authentication in the environment.
|
|
180
|
+
release_git() {
|
|
181
|
+
node -e 'const cp=require("node:child_process"); const env=Object.fromEntries(Object.entries(process.env).filter(([k])=>!k.startsWith("GIT_") && k!=="GH_DEBUG" && k!=="DEBUG")); Object.assign(env,{GIT_CONFIG_NOSYSTEM:"1",GIT_CONFIG_GLOBAL:"/dev/null",GIT_NO_REPLACE_OBJECTS:"1",GIT_GRAFT_FILE:"/dev/null",GIT_TERMINAL_PROMPT:"0"}); const result=cp.spawnSync("git",["--no-replace-objects","-c","core.hooksPath=/dev/null","-c","core.useReplaceRefs=false","-c","push.followTags=false","-c","credential.helper=","-c","credential.helper=!gh auth git-credential",...process.argv.slice(1)],{env,stdio:"inherit"}); process.exit(result.status??1);' -- "$@"
|
|
182
|
+
}
|
|
183
|
+
release_output=$(release_git rev-parse --show-toplevel) || exit 1
|
|
184
|
+
test "$release_output" = "$artifact/public-git"
|
|
185
|
+
release_output=$(release_git rev-parse HEAD) || exit 1
|
|
186
|
+
test "$release_output" = '<PUBLIC_COMMIT>'
|
|
187
|
+
release_output=$(release_git rev-parse 'HEAD^{tree}') || exit 1
|
|
188
|
+
test "$release_output" = '<PUBLIC_TREE>'
|
|
189
|
+
release_output=$(release_git rev-list --parents -n 1 HEAD) || exit 1
|
|
190
|
+
test "$release_output" = '<PUBLIC_COMMIT> <PINNED_PARENT>'
|
|
191
|
+
release_output=$(release_git cat-file -t 'refs/tags/<VERSION_TAG>') || exit 1
|
|
192
|
+
test "$release_output" = tag
|
|
193
|
+
release_output=$(release_git rev-parse 'refs/tags/<VERSION_TAG>') || exit 1
|
|
194
|
+
test "$release_output" = '<ANNOTATED_TAG_OBJECT>'
|
|
195
|
+
release_output=$(release_git rev-parse 'refs/tags/<VERSION_TAG>^{commit}') || exit 1
|
|
196
|
+
test "$release_output" = '<PUBLIC_COMMIT>'
|
|
197
|
+
release_output=$(release_git ls-remote --refs -- '<DESTINATION_URL>' 'refs/heads/<DESTINATION_BRANCH>' 'refs/tags/<VERSION_TAG>') || exit 1
|
|
198
|
+
test "$release_output" = '<PINNED_PARENT> refs/heads/<DESTINATION_BRANCH>'
|
|
199
|
+
release_git push --atomic --no-follow-tags -- '<DESTINATION_URL>' '<PUBLIC_COMMIT>:refs/heads/<DESTINATION_BRANCH>' '<ANNOTATED_TAG_OBJECT>:refs/tags/<VERSION_TAG>'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The receipt, report and Git config hashes bind the handoff to the reviewed
|
|
203
|
+
artifact. The local checks require the exact commit, tree, sole parent,
|
|
204
|
+
annotated tag object and peeled commit. The remote check requires exactly the
|
|
205
|
+
pinned branch parent and no version tag. The final command makes one ordinary
|
|
206
|
+
atomic push of that commit and tag; it never forces, rewrites history or pushes
|
|
207
|
+
private refs. A failed guard requires a fresh artifact. Keep the private source
|
|
208
|
+
tag in the private repository.
|
|
209
|
+
|
|
210
|
+
Generated source commits and annotated tags identify Run Edit Run with the
|
|
211
|
+
non-contact address `releases@runeditrun.invalid`. Provenance remains bound to
|
|
212
|
+
the source commit and tree; the Git author address is not a delivery destination.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
6
|
+
export const json = value => JSON.stringify(value, null, 2) + '\n';
|
|
7
|
+
|
|
8
|
+
function clauses(text) {
|
|
9
|
+
const result = [];
|
|
10
|
+
let current = null;
|
|
11
|
+
const finish = () => {
|
|
12
|
+
if (!current) return;
|
|
13
|
+
const body = current.body.join(' ').replace(/\s+/gu, ' ').trim();
|
|
14
|
+
if (body) result.push({ id: current.id, title: current.title, text: body });
|
|
15
|
+
current = null;
|
|
16
|
+
};
|
|
17
|
+
const clauseLine = /^- \*\*([A-Z][A-Z0-9-]*-\d{3})\s+[—–-]\s+(.+?)\.?\*\*[ \t]*(.*)$/;
|
|
18
|
+
for (const line of text.split(/\r\n|\n|\r/)) {
|
|
19
|
+
const match = clauseLine.exec(line);
|
|
20
|
+
if (match) {
|
|
21
|
+
finish();
|
|
22
|
+
current = { id: match[1], title: match[2], body: [match[3]] };
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (!current) continue;
|
|
26
|
+
if (!line.trim() || /^[\t ]/.test(line)) {
|
|
27
|
+
current.body.push(line);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
finish();
|
|
31
|
+
}
|
|
32
|
+
finish();
|
|
33
|
+
if (!result.length) throw Error('CONTRACT.md must contain published clauses');
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function catalogue(source, project, repository, gates, dryRun) {
|
|
38
|
+
const s = source.seed;
|
|
39
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(s.id)) throw Error('Invalid seed listing ID');
|
|
40
|
+
for (const key of ['name', 'category', 'description', 'maturity'])
|
|
41
|
+
if (typeof s[key] !== 'string' || !s[key]) throw Error('Incomplete seed listing metadata');
|
|
42
|
+
const contract = clauses(source.bytes('CONTRACT.md').toString());
|
|
43
|
+
const licence = source.bytes('LICENSE').toString();
|
|
44
|
+
if (!licence.includes('Permission is hereby granted, free of charge')) throw Error('Declare a supported listing licence');
|
|
45
|
+
const docs = ['seed.json', 'CONTRACT.md', 'AGENTS.md', 'LICENSE', 'RELEASE.json',
|
|
46
|
+
...['README.md', 'BASELINE.md', 'OPERATIONS.md', 'ACCEPTANCE.md'].filter(path => source.entries.has(path))];
|
|
47
|
+
const artifacts = docs.map(path => ({ path: `documents/${path}`, target: `agents/seeds/${s.id}/${path}`, sha256: sha256(source.bytes(path)) }));
|
|
48
|
+
const listing = { id: s.id, name: s.name, licence: 'MIT', repository, category: s.category,
|
|
49
|
+
categoryLabel: s.categoryLabel ?? s.category, version: s.version, oneLiner: s.description,
|
|
50
|
+
summary: s.description, maturity: s.maturity, replaces: s.replaces ?? [], clauses: contract,
|
|
51
|
+
clauseCount: contract.length, nonGoals: s.nonGoals ?? [], externals: (s.externals ?? []).map(e => ({
|
|
52
|
+
name: e.name ?? e.id, reason: e.reason ?? e.why, required: e.required, data: e.data ?? [], adapters: e.adapters ?? [],
|
|
53
|
+
})), extensionPoints: Array.isArray(s.extensionPoints) ? s.extensionPoints : Object.values(s.extensionPoints ?? {}).flat(),
|
|
54
|
+
shots: [], landing: null, hasReadme: docs.includes('README.md'), hasBaseline: docs.includes('BASELINE.md'), manifestFile: 'seed.json' };
|
|
55
|
+
for (const key of ['scope', 'spine', 'composition', 'operatingCost', 'limits', 'accessibility']) if (s[key] !== undefined) listing[key] = s[key];
|
|
56
|
+
return { schemaVersion: 1, project, release: s.version, provenance: {
|
|
57
|
+
kind: dryRun ? 'release-preview' : 'publisher-release', repository, revision: source.commit, tree: source.tree,
|
|
58
|
+
publication: dryRun ? (gates.verify.status === 'PASS' && gates.publishCheck.status === 'PASS' ? 'dry-run' : 'blocked') : 'local-release',
|
|
59
|
+
tag: source.tag, gates, note: dryRun
|
|
60
|
+
? (source.publicTagStaged ? 'Local review preview; public export tag staged. No private source tag created or push performed.'
|
|
61
|
+
: 'Local review preview; no tag created or pushed and no visibility change.')
|
|
62
|
+
: 'Local tag and export; no push, deployment or visibility change.',
|
|
63
|
+
}, listing, artifacts };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function writeCatalogue(source, output, manifest) {
|
|
67
|
+
const directory = resolve(output, 'site', source.seed.id, source.seed.version);
|
|
68
|
+
for (const artifact of manifest.artifacts) {
|
|
69
|
+
const path = resolve(directory, artifact.path);
|
|
70
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
71
|
+
writeFileSync(path, source.bytes(artifact.path.slice('documents/'.length)), { flag: 'wx' });
|
|
72
|
+
}
|
|
73
|
+
const bytes = json(manifest);
|
|
74
|
+
const releasePath = `${source.seed.id}/${source.seed.version}/release.json`;
|
|
75
|
+
writeFileSync(resolve(directory, 'release.json'), bytes, { flag: 'wx' });
|
|
76
|
+
writeFileSync(resolve(output, 'site/registry.json'), json({ schemaVersion: 1, snapshot: source.timestamp,
|
|
77
|
+
releases: [{ path: releasePath, sha256: sha256(bytes) }] }), { flag: 'wx' });
|
|
78
|
+
return { path: `site/${releasePath}`, sha256: sha256(bytes) };
|
|
79
|
+
}
|