@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.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/apps/storefront/astro.config.mjs +3 -3
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +8 -3
- package/apps/storefront/env.d.ts +54 -5
- package/apps/storefront/integrations/materialize-guard.mjs +12 -14
- package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
- package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
- package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
- package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
- package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
- package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
- package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
- package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
- package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
- package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
- package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
- package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
- package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
- package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
- package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
- package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
- package/apps/storefront/public/shared/commerce-chrome.css +20 -2
- package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
- package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
- package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
- package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
- package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
- package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
- package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
- package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
- package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
- package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
- package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
- package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
- package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
- package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
- package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
- package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
- package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
- package/apps/storefront/src/components/Img.astro +1 -1
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
- package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
- package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
- package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
- package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
- package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
- package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
- package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
- package/apps/storefront/src/components/membership/TierCards.astro +2 -2
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
- package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
- package/apps/storefront/src/config/devTenantSeed.ts +9 -9
- package/apps/storefront/src/config/resolver.ts +18 -10
- package/apps/storefront/src/layouts/Layout.astro +100 -20
- package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
- package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
- package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
- package/apps/storefront/src/lib/admin/envelope.ts +1 -2
- package/apps/storefront/src/lib/apps/adminService.ts +10 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
- package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
- package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
- package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
- package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
- package/apps/storefront/src/lib/apps/registryService.ts +264 -10
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
- package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
- package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
- package/apps/storefront/src/lib/blog/collection.ts +114 -17
- package/apps/storefront/src/lib/blog/markdown.ts +1 -1
- package/apps/storefront/src/lib/blog/provider.ts +76 -9
- package/apps/storefront/src/lib/blog/types.ts +9 -9
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
- package/apps/storefront/src/lib/chrome/model.ts +48 -253
- package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
- package/apps/storefront/src/lib/chrome/variant.ts +39 -0
- package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
- package/apps/storefront/src/lib/content/index.ts +5 -5
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
- package/apps/storefront/src/lib/content-edit/route.ts +1 -1
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
- package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
- package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
- package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
- package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
- package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
- package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
- package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
- package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
- package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
- package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
- package/apps/storefront/src/lib/edgeCache.ts +18 -6
- package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
- package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
- package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
- package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
- package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
- package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
- package/apps/storefront/src/lib/membership/model.ts +1 -2
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
- package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
- package/apps/storefront/src/lib/newsletter/send.ts +3 -3
- package/apps/storefront/src/lib/previewReload.ts +57 -1
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
- package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
- package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
- package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
- package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
- package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
- package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
- package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
- package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
- package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
- package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
- package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
- package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
- package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
- package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
- package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
- package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
- package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
- package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
- package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
- package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
- package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
- package/apps/storefront/src/lib/rawChrome.ts +125 -47
- package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
- package/apps/storefront/src/lib/seo/alternates.ts +1 -1
- package/apps/storefront/src/lib/social/notify.ts +4 -4
- package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
- package/apps/storefront/src/lib/storyblok/provider.ts +203 -69
- package/apps/storefront/src/lib/subscription/model.ts +4 -4
- package/apps/storefront/src/lib/the-build/provider.ts +9 -4
- package/apps/storefront/src/middleware/index.ts +273 -51
- package/apps/storefront/src/pages/[...slug].astro +9 -4
- package/apps/storefront/src/pages/admin/review-release.astro +265 -0
- package/apps/storefront/src/pages/admin/select.astro +96 -0
- package/apps/storefront/src/pages/admin.astro +16 -2
- package/apps/storefront/src/pages/api/activity.ts +8 -8
- package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
- package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
- package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
- package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
- package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
- package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
- package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
- package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
- package/apps/storefront/src/pages/blog/[slug].astro +5 -5
- package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
- package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
- package/apps/storefront/src/pages/blog/index.astro +2 -2
- package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
- package/apps/storefront/src/pages/cockpit.astro +61 -1362
- package/apps/storefront/src/pages/index.astro +10 -5
- package/apps/storefront/src/pages/llms.txt.ts +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +7 -6
- package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +42 -31
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +8 -3
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +6 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +6 -2
- package/apps/storefront/src/pages/style-guide/index.astro +19 -5
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
- package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
- package/apps/storefront/src/pages/the-build/index.astro +2 -2
- package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
- package/apps/storefront/src/styles/admin.css +27 -1
- package/apps/storefront/src/themes/tenants/index.ts +19 -7
- package/apps/storefront/tsconfig.json +1 -1
- package/package.json +1 -1
- package/packages/public-runtime/package.json +2 -1
- package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
- package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
- package/packages/public-runtime/src/activity/catalog.ts +41 -37
- package/packages/public-runtime/src/activity/event.ts +6 -6
- package/packages/public-runtime/src/activity/index.ts +2 -2
- package/packages/public-runtime/src/activity/redaction.ts +4 -4
- package/packages/public-runtime/src/candidate-index.ts +42 -8
- package/packages/public-runtime/src/checkout-orders.ts +2 -2
- package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
- package/packages/public-runtime/src/checkout-style.ts +7 -7
- package/packages/public-runtime/src/checkout.ts +3 -3
- package/packages/public-runtime/src/chrome.ts +777 -0
- package/packages/public-runtime/src/compliance-obligations.ts +310 -0
- package/packages/public-runtime/src/csp.ts +99 -0
- package/packages/public-runtime/src/customization-runtime.ts +32 -1
- package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
- package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
- package/packages/public-runtime/src/embed-catalog.mjs +40 -0
- package/packages/public-runtime/src/evidence.ts +288 -0
- package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
- package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
- package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
- package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
- package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
- package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
- package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
- package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
- package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
- package/packages/public-runtime/src/index.ts +16 -1
- package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
- package/packages/public-runtime/src/integration/assurance.ts +228 -0
- package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
- package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
- package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
- package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
- package/packages/public-runtime/src/integration/consent-config.ts +260 -0
- package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
- package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
- package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
- package/packages/public-runtime/src/integration/csp.ts +105 -0
- package/packages/public-runtime/src/integration/digest.ts +43 -0
- package/packages/public-runtime/src/integration/environment.ts +228 -0
- package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
- package/packages/public-runtime/src/integration/events.ts +701 -0
- package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
- package/packages/public-runtime/src/integration/index.ts +90 -0
- package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
- package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
- package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
- package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
- package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
- package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
- package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
- package/packages/public-runtime/src/integration/manifest.ts +470 -0
- package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
- package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
- package/packages/public-runtime/src/integration/observability.ts +451 -0
- package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
- package/packages/public-runtime/src/integration/phases.ts +182 -0
- package/packages/public-runtime/src/integration/plan.ts +112 -0
- package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
- package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
- package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
- package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
- package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
- package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
- package/packages/public-runtime/src/integration/render-phases.ts +267 -0
- package/packages/public-runtime/src/integration/resolve.ts +465 -0
- package/packages/public-runtime/src/integration/result.ts +239 -0
- package/packages/public-runtime/src/integration/secrets.ts +92 -0
- package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
- package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
- package/packages/public-runtime/src/integration/version.ts +92 -0
- package/packages/public-runtime/src/integration-capabilities.ts +520 -0
- package/packages/public-runtime/src/membership.ts +2 -2
- package/packages/public-runtime/src/raw-html-policy.ts +2 -4
- package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
- package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
- package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
- package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
- package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
- package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
- package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
- package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
- package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
- package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
- package/packages/public-runtime/src/static-bundle.ts +5 -5
- package/packages/public-runtime/src/tenant-assets.ts +143 -4
- package/packages/public-runtime/src/tenant.ts +136 -9
- package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
- package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
- package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
- package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
- package/scripts/build/copy-tenant-assets.mjs +12 -5
- package/scripts/dev/ai-edit.mjs +2 -1
- package/scripts/dev/file-browser.mjs +2 -2
- package/scripts/dev/file-writer.mjs +2 -2
- package/scripts/dev/integration-guardrails.mjs +498 -0
- package/scripts/dev/safe-path.mjs +2 -2
- package/scripts/dev/tenant-source-root.mjs +3 -2
- package/scripts/tot-dev.mjs +5 -4
|
@@ -34,9 +34,42 @@
|
|
|
34
34
|
* (no detail that would let a caller distinguish "wrong tenant" from
|
|
35
35
|
* "never existed"); a suspended/uninstalled install rejects further
|
|
36
36
|
* mutation with `invalid_state`.
|
|
37
|
+
*
|
|
38
|
+
* Version retention + the single source-of-truth (ext-apps-02b): every
|
|
39
|
+
* activation — `install`, `update`, and `rollback` — retains the version's
|
|
40
|
+
* COMPLETE restorable facet set through {@link AppVersionStore} (its
|
|
41
|
+
* `app_version_snapshots` table over the SAME `Queryable` seam this service
|
|
42
|
+
* uses). The authority for "which version is active" is the version store's
|
|
43
|
+
* `MAX(seq)` current snapshot; `app_installs.app_version` is a DERIVED MIRROR
|
|
44
|
+
* that this service re-points from the store's post-write current version in
|
|
45
|
+
* the same operation ({@link AppRegistryService.syncActiveVersionMirror}). The
|
|
46
|
+
* two can never diverge because the mirror is only ever written from the
|
|
47
|
+
* store's current snapshot — you change the active version by retaining/rolling
|
|
48
|
+
* one, never by writing the mirror directly. `rollback` restores a prior
|
|
49
|
+
* version's whole facet set atomically (store-side) and re-syncs the mirror, so
|
|
50
|
+
* the registry's active version always agrees with the restored snapshot.
|
|
37
51
|
*/
|
|
38
52
|
import { validateScopes, type ScopeDenial } from "./scopes.js";
|
|
39
53
|
import { validateWebhookEndpoint } from "../webhooks/endpointGuard.js";
|
|
54
|
+
import {
|
|
55
|
+
AppVersionStore,
|
|
56
|
+
AppVersionError,
|
|
57
|
+
type AppVersionFacets,
|
|
58
|
+
type AppVersionSnapshot,
|
|
59
|
+
} from "./appVersionStore.js";
|
|
60
|
+
import {
|
|
61
|
+
DEFAULT_EXECUTION_TIER,
|
|
62
|
+
EXECUTION_TIERS,
|
|
63
|
+
ENTRY_FORMATS,
|
|
64
|
+
VENDOR_ADDRESSABLE_PHASES,
|
|
65
|
+
CONSENT_LOAD_WHEN,
|
|
66
|
+
type ExecutionTier,
|
|
67
|
+
type EntryFormat,
|
|
68
|
+
type ReservedPhase,
|
|
69
|
+
type ConsentLoadWhen,
|
|
70
|
+
type PlanIntegrationEntry,
|
|
71
|
+
type PlanIntegrationConsent,
|
|
72
|
+
} from "@tot/public-runtime";
|
|
40
73
|
import type { Queryable } from "../d1/catalog.js";
|
|
41
74
|
|
|
42
75
|
export type AppInstallStatus = "active" | "suspended" | "uninstalled";
|
|
@@ -65,7 +98,8 @@ export type AppAuditEvent =
|
|
|
65
98
|
| "suspended"
|
|
66
99
|
| "resumed"
|
|
67
100
|
| "uninstalled"
|
|
68
|
-
| "credentials_rotated"
|
|
101
|
+
| "credentials_rotated"
|
|
102
|
+
| "rolled_back";
|
|
69
103
|
|
|
70
104
|
/** The auth-derived context every install is scoped to — never caller input. */
|
|
71
105
|
export interface InstallActorContext {
|
|
@@ -81,6 +115,14 @@ export interface InstallInput {
|
|
|
81
115
|
manifest: unknown;
|
|
82
116
|
webhookEndpoint?: string | null;
|
|
83
117
|
widgetPlacements?: unknown[];
|
|
118
|
+
/**
|
|
119
|
+
* The fully plan-resolved facet set to retain for this version. Optional: when
|
|
120
|
+
* a caller has already run the plan resolver (ext-kernel-03) it passes the
|
|
121
|
+
* resolved set and it is snapshotted verbatim; when absent the registry
|
|
122
|
+
* projects a restorable facet set from the captured manifest + granted scopes
|
|
123
|
+
* ({@link resolveVersionFacets}). Either way `install` ALWAYS retains a v1.
|
|
124
|
+
*/
|
|
125
|
+
versionFacets?: AppVersionFacets;
|
|
84
126
|
}
|
|
85
127
|
|
|
86
128
|
export interface AppInstallPatch {
|
|
@@ -89,6 +131,8 @@ export interface AppInstallPatch {
|
|
|
89
131
|
manifest?: unknown;
|
|
90
132
|
webhookEndpoint?: string | null;
|
|
91
133
|
widgetPlacements?: unknown[];
|
|
134
|
+
/** As {@link InstallInput.versionFacets}: the resolved set to retain for this activation. */
|
|
135
|
+
versionFacets?: AppVersionFacets;
|
|
92
136
|
}
|
|
93
137
|
|
|
94
138
|
export type RegistryErrorCode =
|
|
@@ -97,7 +141,9 @@ export type RegistryErrorCode =
|
|
|
97
141
|
| "already_installed"
|
|
98
142
|
| "invalid_state"
|
|
99
143
|
| "invalid_webhook_endpoint"
|
|
100
|
-
| "invalid_widget_placement"
|
|
144
|
+
| "invalid_widget_placement"
|
|
145
|
+
| "unknown_version"
|
|
146
|
+
| "invalid_version_facets";
|
|
101
147
|
|
|
102
148
|
/**
|
|
103
149
|
* `install()`'s return: the install row PLUS the one-time client credential
|
|
@@ -257,12 +303,100 @@ function toDomain(row: AppInstallRow): AppInstall {
|
|
|
257
303
|
};
|
|
258
304
|
}
|
|
259
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Project the version's restorable facet set from what the registry
|
|
308
|
+
* authoritatively holds. A pre-resolved `override` (produced upstream by the
|
|
309
|
+
* plan resolver) is snapshotted verbatim EXCEPT for the two facets the registry
|
|
310
|
+
* — not the resolver — is the authority for: the version handle (`appVersion`)
|
|
311
|
+
* and the granted `scopes`. With no override, the set is projected from the
|
|
312
|
+
* captured `manifest`'s declared browser facets (tier / digest / entry /
|
|
313
|
+
* consent) with fail-closed defaults for a partial declaration; the
|
|
314
|
+
* cross-cutting `contributions` / `cspOrigins` / tenant `publicConfig` a full
|
|
315
|
+
* plan resolution computes are left empty here and only ever arrive through the
|
|
316
|
+
* `override`. Pure — no I/O, never throws (the version store re-scans the result
|
|
317
|
+
* for secret material before it is persisted).
|
|
318
|
+
*/
|
|
319
|
+
export function resolveVersionFacets(args: {
|
|
320
|
+
appVersion: string;
|
|
321
|
+
scopes: string[];
|
|
322
|
+
manifest: unknown;
|
|
323
|
+
override?: AppVersionFacets;
|
|
324
|
+
}): AppVersionFacets {
|
|
325
|
+
const { appVersion, scopes, manifest, override } = args;
|
|
326
|
+
if (override) return { ...override, appVersion, scopes };
|
|
327
|
+
|
|
328
|
+
const browser = asObject(asObject(manifest)?.["browser"]);
|
|
329
|
+
return {
|
|
330
|
+
appVersion,
|
|
331
|
+
digest: readString(asObject(asObject(manifest)?.["app"])?.["digest"]) ?? "",
|
|
332
|
+
executionTier: readExecutionTier(browser?.["executionTier"]),
|
|
333
|
+
entry: readEntry(asObject(browser?.["entry"])),
|
|
334
|
+
publicConfig: {},
|
|
335
|
+
contributions: [],
|
|
336
|
+
scopes,
|
|
337
|
+
consent: readConsent(asObject(browser?.["consent"])),
|
|
338
|
+
cspOrigins: [],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function asObject(value: unknown): Record<string, unknown> | undefined {
|
|
343
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
344
|
+
? (value as Record<string, unknown>)
|
|
345
|
+
: undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function readString(value: unknown): string | undefined {
|
|
349
|
+
return typeof value === "string" ? value : undefined;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function readExecutionTier(value: unknown): ExecutionTier {
|
|
353
|
+
return typeof value === "string" && (EXECUTION_TIERS as readonly string[]).includes(value)
|
|
354
|
+
? (value as ExecutionTier)
|
|
355
|
+
: DEFAULT_EXECUTION_TIER;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function readEntry(entry: Record<string, unknown> | undefined): PlanIntegrationEntry {
|
|
359
|
+
const url = readString(entry?.["asset"]) ?? "";
|
|
360
|
+
const format =
|
|
361
|
+
typeof entry?.["format"] === "string" && (ENTRY_FORMATS as readonly string[]).includes(entry["format"] as string)
|
|
362
|
+
? (entry["format"] as EntryFormat)
|
|
363
|
+
: "module";
|
|
364
|
+
const phase =
|
|
365
|
+
typeof entry?.["phase"] === "string" &&
|
|
366
|
+
(VENDOR_ADDRESSABLE_PHASES as readonly string[]).includes(entry["phase"] as string)
|
|
367
|
+
? (entry["phase"] as ReservedPhase)
|
|
368
|
+
: "document.deferred";
|
|
369
|
+
return { url, format, phase };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function readConsent(consent: Record<string, unknown> | undefined): PlanIntegrationConsent {
|
|
373
|
+
const purposes = Array.isArray(consent?.["purposes"])
|
|
374
|
+
? (consent["purposes"] as unknown[]).filter((p): p is string => typeof p === "string")
|
|
375
|
+
: [];
|
|
376
|
+
const loadWhen: ConsentLoadWhen =
|
|
377
|
+
typeof consent?.["loadWhen"] === "string" &&
|
|
378
|
+
(CONSENT_LOAD_WHEN as readonly string[]).includes(consent["loadWhen"] as string)
|
|
379
|
+
? (consent["loadWhen"] as ConsentLoadWhen)
|
|
380
|
+
: "essential-only";
|
|
381
|
+
return { purposes, loadWhen };
|
|
382
|
+
}
|
|
383
|
+
|
|
260
384
|
export class AppRegistryService {
|
|
385
|
+
private readonly versionStore: AppVersionStore;
|
|
386
|
+
|
|
261
387
|
constructor(
|
|
262
388
|
private readonly db: Queryable,
|
|
263
389
|
private readonly credentials: CredentialPort,
|
|
264
390
|
private readonly now: () => string = () => new Date().toISOString(),
|
|
265
|
-
|
|
391
|
+
/**
|
|
392
|
+
* The version-retention spine. Defaults to one over the SAME `Queryable`
|
|
393
|
+
* seam + clock, so retention writes share this service's transaction shape;
|
|
394
|
+
* injectable for tests.
|
|
395
|
+
*/
|
|
396
|
+
versionStore: AppVersionStore = new AppVersionStore(db, now),
|
|
397
|
+
) {
|
|
398
|
+
this.versionStore = versionStore;
|
|
399
|
+
}
|
|
266
400
|
|
|
267
401
|
/**
|
|
268
402
|
* Install an app for one (tenant, env). Validates scopes BEFORE writing
|
|
@@ -291,6 +425,16 @@ export class AppRegistryService {
|
|
|
291
425
|
);
|
|
292
426
|
}
|
|
293
427
|
|
|
428
|
+
// The active version is whatever we retain below; `app_installs.app_version`
|
|
429
|
+
// is a mirror of it. Resolve the facet set FIRST so the row's `app_version`
|
|
430
|
+
// is the same version handle the retained v1 snapshot carries.
|
|
431
|
+
const facets = resolveVersionFacets({
|
|
432
|
+
appVersion: input.appVersion,
|
|
433
|
+
scopes: validation.scopes,
|
|
434
|
+
manifest: input.manifest,
|
|
435
|
+
override: input.versionFacets,
|
|
436
|
+
});
|
|
437
|
+
|
|
294
438
|
const installId = crypto.randomUUID();
|
|
295
439
|
const now = this.now();
|
|
296
440
|
await this.db.run(
|
|
@@ -300,7 +444,7 @@ export class AppRegistryService {
|
|
|
300
444
|
VALUES (?,?,?,?,?,'active',?,?,?,?,?,?,?)`,
|
|
301
445
|
installId,
|
|
302
446
|
input.appId,
|
|
303
|
-
|
|
447
|
+
facets.appVersion,
|
|
304
448
|
ctx.tenantId,
|
|
305
449
|
input.env,
|
|
306
450
|
JSON.stringify(validation.scopes),
|
|
@@ -323,12 +467,30 @@ export class AppRegistryService {
|
|
|
323
467
|
await this.db.run(`DELETE FROM app_installs WHERE install_id = ?`, installId);
|
|
324
468
|
throw cause;
|
|
325
469
|
}
|
|
470
|
+
|
|
471
|
+
// Retain v1's complete facet set. Fail-closed: a facet set naming secret
|
|
472
|
+
// material (or any retention failure) unwinds the whole install — clear the
|
|
473
|
+
// just-issued credential and delete the row — so a refused install leaves no
|
|
474
|
+
// trace, exactly like the credential-failure path above.
|
|
475
|
+
try {
|
|
476
|
+
await this.versionStore.retainVersion({
|
|
477
|
+
installId,
|
|
478
|
+
tenantId: ctx.tenantId,
|
|
479
|
+
facets,
|
|
480
|
+
actor: ctx.actor,
|
|
481
|
+
});
|
|
482
|
+
} catch (cause) {
|
|
483
|
+
await this.credentials.clear(installId).catch(() => {});
|
|
484
|
+
await this.db.run(`DELETE FROM app_installs WHERE install_id = ?`, installId);
|
|
485
|
+
throw this.asRegistryError(cause);
|
|
486
|
+
}
|
|
487
|
+
|
|
326
488
|
await this.appendAudit({
|
|
327
489
|
installId,
|
|
328
490
|
tenantId: ctx.tenantId,
|
|
329
491
|
event: "installed",
|
|
330
492
|
actor: ctx.actor,
|
|
331
|
-
detail: { appId: input.appId, appVersion:
|
|
493
|
+
detail: { appId: input.appId, appVersion: facets.appVersion, env: input.env },
|
|
332
494
|
});
|
|
333
495
|
|
|
334
496
|
return {
|
|
@@ -340,7 +502,7 @@ export class AppRegistryService {
|
|
|
340
502
|
/** Read one install by id, or `null` if it doesn't exist. */
|
|
341
503
|
async get(installId: string): Promise<AppInstall | null> {
|
|
342
504
|
const row = await this.db.first<AppInstallRow>(
|
|
343
|
-
`SELECT * FROM app_installs WHERE install_id =
|
|
505
|
+
`SELECT * FROM app_installs WHERE install_id = ? AND managed_kind IS NULL`,
|
|
344
506
|
installId,
|
|
345
507
|
);
|
|
346
508
|
return row ? toDomain(row) : null;
|
|
@@ -349,7 +511,7 @@ export class AppRegistryService {
|
|
|
349
511
|
/** All installs for a tenant, newest first. */
|
|
350
512
|
async listByTenant(tenantId: string): Promise<AppInstall[]> {
|
|
351
513
|
const rows = await this.db.all<AppInstallRow>(
|
|
352
|
-
`SELECT * FROM app_installs WHERE tenant_id = ? ORDER BY created_at DESC`,
|
|
514
|
+
`SELECT * FROM app_installs WHERE tenant_id = ? AND managed_kind IS NULL ORDER BY created_at DESC`,
|
|
353
515
|
tenantId,
|
|
354
516
|
);
|
|
355
517
|
return rows.map(toDomain);
|
|
@@ -396,12 +558,34 @@ export class AppRegistryService {
|
|
|
396
558
|
const widgetPlacementsJson =
|
|
397
559
|
widgetPlacements !== undefined ? JSON.stringify(widgetPlacements) : row.widget_placements;
|
|
398
560
|
|
|
561
|
+
// Retain this update as a new activation BEFORE the metadata write — after
|
|
562
|
+
// every fail-closed validation above, so a bad patch retains nothing. This
|
|
563
|
+
// captures the whole facet set at this version and, being the last write
|
|
564
|
+
// gate that can refuse (a secret-bearing facet set), keeps `update` all-or-
|
|
565
|
+
// nothing. `app_version` is NOT written here: it is the mirror, re-pointed
|
|
566
|
+
// below from the store's post-retain current version.
|
|
567
|
+
const facets = resolveVersionFacets({
|
|
568
|
+
appVersion,
|
|
569
|
+
scopes: effectiveScopes,
|
|
570
|
+
manifest: patch.manifest !== undefined ? patch.manifest : (JSON.parse(row.manifest) as unknown),
|
|
571
|
+
override: patch.versionFacets,
|
|
572
|
+
});
|
|
573
|
+
try {
|
|
574
|
+
await this.versionStore.retainVersion({
|
|
575
|
+
installId,
|
|
576
|
+
tenantId: row.tenant_id,
|
|
577
|
+
facets,
|
|
578
|
+
actor,
|
|
579
|
+
});
|
|
580
|
+
} catch (cause) {
|
|
581
|
+
throw this.asRegistryError(cause);
|
|
582
|
+
}
|
|
583
|
+
|
|
399
584
|
await this.db.run(
|
|
400
585
|
`UPDATE app_installs
|
|
401
|
-
SET
|
|
586
|
+
SET scopes = ?, manifest = ?, webhook_endpoint = ?,
|
|
402
587
|
widget_placements = ?, updated_at = ?
|
|
403
588
|
WHERE install_id = ?`,
|
|
404
|
-
appVersion,
|
|
405
589
|
scopesJson,
|
|
406
590
|
manifestJson,
|
|
407
591
|
webhookEndpoint,
|
|
@@ -409,6 +593,7 @@ export class AppRegistryService {
|
|
|
409
593
|
now,
|
|
410
594
|
installId,
|
|
411
595
|
);
|
|
596
|
+
await this.syncActiveVersionMirror(installId);
|
|
412
597
|
|
|
413
598
|
await this.appendAudit({
|
|
414
599
|
installId,
|
|
@@ -421,6 +606,37 @@ export class AppRegistryService {
|
|
|
421
606
|
return this.getOrThrow(installId);
|
|
422
607
|
}
|
|
423
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Roll an install back to a retained version, restoring its COMPLETE facet set
|
|
611
|
+
* atomically through {@link AppVersionStore.rollbackTo} (forward-only: a new
|
|
612
|
+
* activation copying the target's facets verbatim), then re-point the
|
|
613
|
+
* `app_installs.app_version` mirror to the restored version so the registry's
|
|
614
|
+
* active version agrees with the store's current snapshot. Rejects an
|
|
615
|
+
* uninstalled install (a dead install can't be mutated) and an unretained
|
|
616
|
+
* `toSeq` (`unknown_version`), leaving the store untouched in both cases.
|
|
617
|
+
*/
|
|
618
|
+
async rollback(installId: string, toSeq: number, actor: string): Promise<AppInstall> {
|
|
619
|
+
const row = await this.mustFindRow(installId);
|
|
620
|
+
if (row.status === "uninstalled") {
|
|
621
|
+
throw new RegistryError(`Cannot roll back an uninstalled app.`, "invalid_state");
|
|
622
|
+
}
|
|
623
|
+
let restored: AppVersionSnapshot;
|
|
624
|
+
try {
|
|
625
|
+
restored = await this.versionStore.rollbackTo({ installId, toSeq, actor });
|
|
626
|
+
} catch (cause) {
|
|
627
|
+
throw this.asRegistryError(cause);
|
|
628
|
+
}
|
|
629
|
+
await this.syncActiveVersionMirror(installId);
|
|
630
|
+
await this.appendAudit({
|
|
631
|
+
installId,
|
|
632
|
+
tenantId: row.tenant_id,
|
|
633
|
+
event: "rolled_back",
|
|
634
|
+
actor,
|
|
635
|
+
detail: { toSeq, restoredVersion: restored.appVersion, activationSeq: restored.seq },
|
|
636
|
+
});
|
|
637
|
+
return this.getOrThrow(installId);
|
|
638
|
+
}
|
|
639
|
+
|
|
424
640
|
/** Suspend an active install: disables API/webhook/widget access. */
|
|
425
641
|
async suspend(installId: string, actor: string): Promise<AppInstall> {
|
|
426
642
|
const row = await this.mustFindRow(installId);
|
|
@@ -519,6 +735,40 @@ export class AppRegistryService {
|
|
|
519
735
|
return validation.placements;
|
|
520
736
|
}
|
|
521
737
|
|
|
738
|
+
/**
|
|
739
|
+
* Re-point `app_installs.app_version` (the derived mirror) to the version
|
|
740
|
+
* store's current (`MAX(seq)`) snapshot — the single reconciliation step that
|
|
741
|
+
* keeps the mirror and the source-of-truth authority from diverging. Called
|
|
742
|
+
* after every store write that can change the active version (`update`,
|
|
743
|
+
* `rollback`); `install` sets the mirror from the same resolved facet set at
|
|
744
|
+
* INSERT time, so it is already consistent there.
|
|
745
|
+
*/
|
|
746
|
+
private async syncActiveVersionMirror(installId: string): Promise<void> {
|
|
747
|
+
const current = await this.versionStore.currentVersion(installId);
|
|
748
|
+
if (!current) return;
|
|
749
|
+
await this.db.run(
|
|
750
|
+
`UPDATE app_installs SET app_version = ?, updated_at = ? WHERE install_id = ?`,
|
|
751
|
+
current.appVersion,
|
|
752
|
+
this.now(),
|
|
753
|
+
installId,
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Translate a version-store {@link AppVersionError} into this service's
|
|
759
|
+
* `RegistryError` vocabulary so callers branch on one `.code` space: an
|
|
760
|
+
* unretained rollback target → `unknown_version`, a secret-bearing/invalid
|
|
761
|
+
* facet set → `invalid_version_facets`. Anything else rethrows unchanged.
|
|
762
|
+
*/
|
|
763
|
+
private asRegistryError(cause: unknown): unknown {
|
|
764
|
+
if (cause instanceof AppVersionError) {
|
|
765
|
+
const code: RegistryErrorCode =
|
|
766
|
+
cause.code === "unknown_target" ? "unknown_version" : "invalid_version_facets";
|
|
767
|
+
return new RegistryError(cause.message, code);
|
|
768
|
+
}
|
|
769
|
+
return cause;
|
|
770
|
+
}
|
|
771
|
+
|
|
522
772
|
private async setStatus(installId: string, status: AppInstallStatus): Promise<void> {
|
|
523
773
|
await this.db.run(
|
|
524
774
|
`UPDATE app_installs SET status = ?, updated_at = ? WHERE install_id = ?`,
|
|
@@ -544,7 +794,11 @@ export class AppRegistryService {
|
|
|
544
794
|
/** Fetch a row or throw the generic fail-closed `not_found` — no enumeration detail. */
|
|
545
795
|
private async mustFindRow(installId: string): Promise<AppInstallRow> {
|
|
546
796
|
const row = await this.db.first<AppInstallRow>(
|
|
547
|
-
|
|
797
|
+
// Managed-analytics installs have a reviewed-first lifecycle and are owned
|
|
798
|
+
// exclusively by ManagedAnalyticsApiV1Facade. Hiding them here fences the
|
|
799
|
+
// legacy registry's update/status/rollback/uninstall methods from racing
|
|
800
|
+
// the managed CAS or appending an activation behind its revision counter.
|
|
801
|
+
`SELECT * FROM app_installs WHERE install_id = ? AND managed_kind IS NULL`,
|
|
548
802
|
installId,
|
|
549
803
|
);
|
|
550
804
|
if (!row) throw new RegistryError(`App install not found.`, "not_found");
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-plane materialization of a tenant's OWN (`tenant-local`) integration
|
|
3
|
+
* declarations for the admin seams (ext-kernel-09).
|
|
4
|
+
*
|
|
5
|
+
* The middleware materializes these same declarations on the render hot path; this
|
|
6
|
+
* module materializes them for the ADMIN plane — the ext-admin-02 read model and the
|
|
7
|
+
* ext-admin-07 lifecycle route — so a declared integration shows its REAL effective
|
|
8
|
+
* state (its resolved plan, compiled CSP, consent needs) instead of a null /
|
|
9
|
+
* `not_materialized` placeholder. Resolution runs through the SAME interim marketing
|
|
10
|
+
* bridge the render path uses, so a regulated tenant is refused identically (no
|
|
11
|
+
* effective plan → the declaration surfaces honestly as unmaterialized) and an
|
|
12
|
+
* eligible unregulated one resolves the exact plan it serves.
|
|
13
|
+
*
|
|
14
|
+
* A tenant-local declaration is a live-by-config integration (on-by-default), not a
|
|
15
|
+
* candidate under review: its effective state is what the read model shows and what
|
|
16
|
+
* the lifecycle route loads. It has no separate promotion gate — publishing an
|
|
17
|
+
* on-by-default declaration would change nothing — so the lifecycle route surfaces
|
|
18
|
+
* its live state and refuses a preview/publish with no candidate rather than
|
|
19
|
+
* fabricating one.
|
|
20
|
+
*/
|
|
21
|
+
import { loadTenantIntegrationDeclarations } from "@/lib/integration/tenantDeclarations";
|
|
22
|
+
import {
|
|
23
|
+
resolveInterimBridgePlan,
|
|
24
|
+
compileIntegrationCspFromPlan,
|
|
25
|
+
collectDeclaredCspProfiles,
|
|
26
|
+
buildCandidateEvidence,
|
|
27
|
+
defaultConsentSnapshot,
|
|
28
|
+
ENVIRONMENT_SCHEMA_VERSION,
|
|
29
|
+
type TenantConfig,
|
|
30
|
+
type BrowserEnvironment,
|
|
31
|
+
type IntegrationEnvironment,
|
|
32
|
+
type IntegrationSiteSurface,
|
|
33
|
+
type EffectivePlan,
|
|
34
|
+
type CompiledIntegrationCsp,
|
|
35
|
+
type ConsentSnapshot,
|
|
36
|
+
type AssetVerificationResult,
|
|
37
|
+
} from "@tot/public-runtime";
|
|
38
|
+
import type { IntegrationReadModelInputItem } from "@/lib/apps/integrationReadModel.js";
|
|
39
|
+
import {
|
|
40
|
+
facetsFromEvidence,
|
|
41
|
+
initialLifecycleState,
|
|
42
|
+
type TenantIntegrationLifecycleState,
|
|
43
|
+
} from "@/lib/apps/integrationLifecycle.js";
|
|
44
|
+
|
|
45
|
+
/** Stable, synthetic install id for a tenant-local declaration (no registry install backs it). */
|
|
46
|
+
export function tenantLocalInstallId(appId: string): string {
|
|
47
|
+
return `tenant-local:${appId}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A same-origin (public/-committed) entry declares no R2 app assets — a clean zero verify. */
|
|
51
|
+
const NO_R2_APP_ASSETS: AssetVerificationResult = { expected: 0, verified: 0, missing: [] };
|
|
52
|
+
|
|
53
|
+
/** The admin-plane browser environment for a lifecycle stage — the `admin` surface. */
|
|
54
|
+
function adminEnvironment(tenant: TenantConfig, stage: IntegrationEnvironment): BrowserEnvironment {
|
|
55
|
+
const surface: IntegrationSiteSurface = "admin";
|
|
56
|
+
return {
|
|
57
|
+
schemaVersion: ENVIRONMENT_SCHEMA_VERSION,
|
|
58
|
+
siteSurface: surface,
|
|
59
|
+
environment: stage,
|
|
60
|
+
canonicalHost: tenant.canonicalDomain || tenant.tenant_id,
|
|
61
|
+
revision: `admin-${stage}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The resolved plan + compiled CSP + consent snapshot for a tenant's tenant-local declarations. */
|
|
66
|
+
export interface MaterializedTenantLocalPlan {
|
|
67
|
+
plan: EffectivePlan;
|
|
68
|
+
compiledCsp: CompiledIntegrationCsp;
|
|
69
|
+
consentSnapshot: ConsentSnapshot;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a tenant's tenant-local declarations into the effective plan + compiled CSP
|
|
74
|
+
* for `stage`, through the interim bridge. Null when the tenant declares none, the
|
|
75
|
+
* bridge refuses (regulated), or the plan/CSP fail closed — every "no effective state"
|
|
76
|
+
* case an honest null, never a partial plan.
|
|
77
|
+
*/
|
|
78
|
+
export function materializeTenantLocalPlan(
|
|
79
|
+
tenant: TenantConfig,
|
|
80
|
+
stage: IntegrationEnvironment,
|
|
81
|
+
): MaterializedTenantLocalPlan | null {
|
|
82
|
+
const declarations = loadTenantIntegrationDeclarations(tenant);
|
|
83
|
+
if (declarations.length === 0) return null;
|
|
84
|
+
const bridge = resolveInterimBridgePlan({
|
|
85
|
+
tenantPublicId: tenant.tenant_id,
|
|
86
|
+
tenant,
|
|
87
|
+
environment: adminEnvironment(tenant, stage),
|
|
88
|
+
declarations,
|
|
89
|
+
});
|
|
90
|
+
if (!bridge.ok) return null;
|
|
91
|
+
const compiled = compileIntegrationCspFromPlan(
|
|
92
|
+
bridge.plan,
|
|
93
|
+
collectDeclaredCspProfiles(declarations),
|
|
94
|
+
);
|
|
95
|
+
if (!compiled.ok) return null;
|
|
96
|
+
return { plan: bridge.plan, compiledCsp: compiled.value, consentSnapshot: defaultConsentSnapshot() };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build the ext-admin-02 read-model input items for a tenant's tenant-local
|
|
101
|
+
* declarations. Each carries the resolved effective plan + compiled CSP so the
|
|
102
|
+
* assembler surfaces REAL effective state; a refused/failed resolution yields a null
|
|
103
|
+
* effective plan, so the declaration surfaces as honestly unmaterialized (never a
|
|
104
|
+
* fabricated live state).
|
|
105
|
+
*/
|
|
106
|
+
export function tenantLocalReadModelItems(
|
|
107
|
+
tenant: TenantConfig,
|
|
108
|
+
stage: IntegrationEnvironment,
|
|
109
|
+
): IntegrationReadModelInputItem[] {
|
|
110
|
+
const declarations = loadTenantIntegrationDeclarations(tenant);
|
|
111
|
+
if (declarations.length === 0) return [];
|
|
112
|
+
const materialized = materializeTenantLocalPlan(tenant, stage);
|
|
113
|
+
return declarations.map((decl) => {
|
|
114
|
+
const { app } = decl.manifest;
|
|
115
|
+
return {
|
|
116
|
+
install: {
|
|
117
|
+
installId: tenantLocalInstallId(app.id),
|
|
118
|
+
appId: app.id,
|
|
119
|
+
appVersion: app.version,
|
|
120
|
+
tenantId: tenant.tenant_id,
|
|
121
|
+
status: "active",
|
|
122
|
+
},
|
|
123
|
+
purpose: app.name,
|
|
124
|
+
desiredConfig: decl.publicConfig,
|
|
125
|
+
effectivePlan: materialized?.plan ?? null,
|
|
126
|
+
effectiveCsp: materialized?.compiledCsp ?? null,
|
|
127
|
+
consentSnapshot: materialized?.consentSnapshot ?? null,
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Load the real lifecycle state for a tenant-local declared install — a live-by-config
|
|
134
|
+
* integration whose effective plan occupies the resolved stage slot. Null when the
|
|
135
|
+
* install id names no materialized tenant-local declaration for the tenant (so the
|
|
136
|
+
* lifecycle route answers a fail-closed 409, never a fabricated state).
|
|
137
|
+
*/
|
|
138
|
+
export function loadTenantLocalLifecycleState(
|
|
139
|
+
tenant: TenantConfig,
|
|
140
|
+
installId: string,
|
|
141
|
+
stage: IntegrationEnvironment,
|
|
142
|
+
): TenantIntegrationLifecycleState | null {
|
|
143
|
+
const declarations = loadTenantIntegrationDeclarations(tenant);
|
|
144
|
+
const decl = declarations.find((d) => tenantLocalInstallId(d.manifest.app.id) === installId);
|
|
145
|
+
if (!decl) return null;
|
|
146
|
+
const materialized = materializeTenantLocalPlan(tenant, stage);
|
|
147
|
+
if (!materialized) return null;
|
|
148
|
+
|
|
149
|
+
const appId = decl.manifest.app.id;
|
|
150
|
+
const evidence = buildCandidateEvidence({
|
|
151
|
+
plan: materialized.plan,
|
|
152
|
+
compiledCsp: materialized.compiledCsp,
|
|
153
|
+
assets: NO_R2_APP_ASSETS,
|
|
154
|
+
});
|
|
155
|
+
if (!evidence.ok) return null;
|
|
156
|
+
const facets = facetsFromEvidence(evidence.value, appId);
|
|
157
|
+
|
|
158
|
+
const state = initialLifecycleState({
|
|
159
|
+
tenantPublicId: tenant.tenant_id,
|
|
160
|
+
installId,
|
|
161
|
+
appId,
|
|
162
|
+
});
|
|
163
|
+
// A tenant-local declaration is live-by-config: its resolved plan IS the serving
|
|
164
|
+
// (live) facet, and it is its own known-good rollback target.
|
|
165
|
+
return {
|
|
166
|
+
...state,
|
|
167
|
+
stages: { ...state.stages, live: facets },
|
|
168
|
+
knownGood: [facets],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Shared fake/local state contract for one composed v84 clean-clone pass. */
|
|
2
|
+
export const V84_REPLAY_SESSION_ID = "offline-v84-shared-clone-session" as const;
|
|
3
|
+
export const V84_REPLAY_CLONE_TENANT = "tokenoftrust.com~clone~gtm-migration-v84" as const;
|
|
4
|
+
|
|
5
|
+
export interface V84CleanCloneReplayContext {
|
|
6
|
+
readonly sessionId: typeof V84_REPLAY_SESSION_ID;
|
|
7
|
+
readonly cloneTenant: typeof V84_REPLAY_CLONE_TENANT;
|
|
8
|
+
read<T>(key: string): T | undefined;
|
|
9
|
+
write(key: string, value: unknown): void;
|
|
10
|
+
delete(key: string): void;
|
|
11
|
+
keys(prefix?: string): readonly string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function sameV84ReplayValue(left: unknown, right: unknown): boolean {
|
|
15
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
16
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** Clone-only cold/warm cache evidence over local managed-install state. */
|
|
2
|
+
import { getCachedPage, pageCacheKey, putCachedPage } from "../edgeCache.js";
|
|
3
|
+
import type { V84DisabledInstallMaterializationResult } from "./v84CloneLifecycleMaterialization.js";
|
|
4
|
+
import {
|
|
5
|
+
V84_NATIVE_CLONE_TENANT,
|
|
6
|
+
V84_NATIVE_SOURCE_TENANT,
|
|
7
|
+
} from "./v84CloneNativeInstallEvidence.js";
|
|
8
|
+
import type { V84CleanCloneReplayContext } from "./v84CleanCloneReplayContext.js";
|
|
9
|
+
|
|
10
|
+
const CACHE_BODY = "<!-- offline v84 lifecycle cache artifact; no integration markup -->";
|
|
11
|
+
const INTEGRATION_MARKUP = /<script|dataLayer|googletagmanager|GTM-|\beval\s*\(/i;
|
|
12
|
+
|
|
13
|
+
function fakeCache(replayContext?: V84CleanCloneReplayContext) {
|
|
14
|
+
const store = new Map<string, Response>();
|
|
15
|
+
const reads: string[] = [];
|
|
16
|
+
const writes: string[] = [];
|
|
17
|
+
const cache = {
|
|
18
|
+
async match(request: Request) {
|
|
19
|
+
reads.push(request.url);
|
|
20
|
+
if (replayContext) {
|
|
21
|
+
const found = replayContext.read<{
|
|
22
|
+
body: string;
|
|
23
|
+
headers: Record<string, string>;
|
|
24
|
+
status: number;
|
|
25
|
+
}>(`stage/lifecycle/cache/${encodeURIComponent(request.url)}`);
|
|
26
|
+
return found ? new Response(found.body, { headers: found.headers, status: found.status }) : undefined;
|
|
27
|
+
}
|
|
28
|
+
return store.get(request.url)?.clone();
|
|
29
|
+
},
|
|
30
|
+
async put(request: Request, response: Response) {
|
|
31
|
+
writes.push(request.url);
|
|
32
|
+
if (replayContext) {
|
|
33
|
+
replayContext.write(`stage/lifecycle/cache/${encodeURIComponent(request.url)}`, {
|
|
34
|
+
body: await response.clone().text(),
|
|
35
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
36
|
+
status: response.status,
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
store.set(request.url, response.clone());
|
|
41
|
+
},
|
|
42
|
+
} as unknown as Cache;
|
|
43
|
+
return { cache, reads, writes };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Exercise a true cold miss and warm clone-only hit without touching source keys. */
|
|
47
|
+
export async function buildV84CloneCacheEvidence(
|
|
48
|
+
materialization: V84DisabledInstallMaterializationResult,
|
|
49
|
+
replayContext?: V84CleanCloneReplayContext,
|
|
50
|
+
) {
|
|
51
|
+
const memory = fakeCache(replayContext);
|
|
52
|
+
const disabledCacheKeyBefore = pageCacheKey(
|
|
53
|
+
V84_NATIVE_CLONE_TENANT,
|
|
54
|
+
"/v84-lifecycle",
|
|
55
|
+
"",
|
|
56
|
+
1,
|
|
57
|
+
materialization.candidatePlanBefore.planRevision,
|
|
58
|
+
);
|
|
59
|
+
const cloneCacheKey = pageCacheKey(
|
|
60
|
+
V84_NATIVE_CLONE_TENANT,
|
|
61
|
+
"/v84-lifecycle",
|
|
62
|
+
"",
|
|
63
|
+
1,
|
|
64
|
+
materialization.candidatePlanAfter.planRevision,
|
|
65
|
+
);
|
|
66
|
+
const sourceCacheKey = pageCacheKey(V84_NATIVE_SOURCE_TENANT, "/v84-lifecycle", "", 1);
|
|
67
|
+
const cold = await getCachedPage(memory.cache, cloneCacheKey);
|
|
68
|
+
await putCachedPage(memory.cache, cloneCacheKey, new Response(CACHE_BODY, {
|
|
69
|
+
status: 200,
|
|
70
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
71
|
+
}));
|
|
72
|
+
const warm = await getCachedPage(memory.cache, cloneCacheKey);
|
|
73
|
+
const warmBody = warm ? await warm.text() : "";
|
|
74
|
+
if (
|
|
75
|
+
cold !== null ||
|
|
76
|
+
!warm ||
|
|
77
|
+
warmBody !== CACHE_BODY ||
|
|
78
|
+
warm.headers.get("x-tot-cache") !== "hit" ||
|
|
79
|
+
disabledCacheKeyBefore.url !== cloneCacheKey.url ||
|
|
80
|
+
INTEGRATION_MARKUP.test(warmBody)
|
|
81
|
+
) {
|
|
82
|
+
throw new Error("The clone-local warm-cache proof failed.");
|
|
83
|
+
}
|
|
84
|
+
if (memory.reads.includes(sourceCacheKey.url) || memory.writes.includes(sourceCacheKey.url)) {
|
|
85
|
+
throw new Error("The warm-cache proof touched the source namespace.");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
warmCache: {
|
|
90
|
+
tenant: V84_NATIVE_CLONE_TENANT,
|
|
91
|
+
coldMiss: true,
|
|
92
|
+
warmHit: true,
|
|
93
|
+
cloneWriteCount: memory.writes.length,
|
|
94
|
+
sourceReadCount: memory.reads.filter((url) => url === sourceCacheKey.url).length,
|
|
95
|
+
sourceWriteCount: memory.writes.filter((url) => url === sourceCacheKey.url).length,
|
|
96
|
+
managedInstallStateChanged: materialization.managedInstallStateChanged,
|
|
97
|
+
effectivePlanChanged:
|
|
98
|
+
materialization.candidatePlanBefore.planRevision !== materialization.candidatePlanAfter.planRevision,
|
|
99
|
+
disabledDraftsChangedCacheKey: disabledCacheKeyBefore.url !== cloneCacheKey.url,
|
|
100
|
+
artifactContainsIntegrationMarkup: INTEGRATION_MARKUP.test(warmBody),
|
|
101
|
+
},
|
|
102
|
+
cloneCacheReads: memory.reads.length,
|
|
103
|
+
cloneCacheWrites: memory.writes.length,
|
|
104
|
+
};
|
|
105
|
+
}
|