@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.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/apps/storefront/astro.config.mjs +3 -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 +4 -4
- 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 +97 -10
- package/apps/storefront/src/lib/blog/markdown.ts +1 -1
- package/apps/storefront/src/lib/blog/provider.ts +57 -3
- 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 +168 -60
- 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 +31 -27
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
- 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/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 +45 -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/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 +1 -1
- package/scripts/tot-dev.mjs +2 -2
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tenant integration configuration lifecycle (ext-admin-07) — the state
|
|
3
|
+
* machine behind the `IntegrationLifecycleAction`s ext-admin-01's IA names
|
|
4
|
+
* (`preview` / `publish` / `rollback` / `suspend` / `resume` / `uninstall`),
|
|
5
|
+
* built ON the candidate/change-control substrate the umbrella already carries:
|
|
6
|
+
* kernel-06 candidate evidence (the code/config/CSP/consent/assurance facets),
|
|
7
|
+
* admin-02's read model (the same DISTINCT effective/candidate/desired slots),
|
|
8
|
+
* and policy-06's unifying promotion gate ({@link verifyIntegrationPromotable}).
|
|
9
|
+
*
|
|
10
|
+
* It is PURE and SYNCHRONOUS over already-materialized inputs — resolving plans,
|
|
11
|
+
* evidence, and the gate's server-derived facts is the control plane's job (the
|
|
12
|
+
* route); transitioning the lifecycle over them is this module's. Four properties
|
|
13
|
+
* hold by construction, one per acceptance clause:
|
|
14
|
+
*
|
|
15
|
+
* 1. FOUR ENVS DISTINCT — {@link TenantIntegrationLifecycleState.stages} keys the
|
|
16
|
+
* `local` / `candidate` / `staged` / `live` facets under SEPARATE slots that
|
|
17
|
+
* are never merged. A transition advances exactly the slots it names; a read of
|
|
18
|
+
* one stage never returns another's revision. `candidate` is asserted to carry
|
|
19
|
+
* the `candidate` environment (a candidate is never live).
|
|
20
|
+
* 2. FAILED PUBLISH LEAVES ACTUAL — {@link publishCandidate} routes the candidate
|
|
21
|
+
* THROUGH the policy-06 gate and flips `live` ONLY on `verdict.ok`. A blocked
|
|
22
|
+
* candidate (or a version/scope mismatch) returns the input state UNCHANGED —
|
|
23
|
+
* the `live` (last-good) facets never move, so there is no half-applied publish.
|
|
24
|
+
* 3. CANDIDATE SHOWS EVERY FACET — {@link diffStageFacets} reports the candidate's
|
|
25
|
+
* code, config, CSP origins, consent needs, and assurance against what is live,
|
|
26
|
+
* all derived from the kernel-06 evidence — the effective diff a reviewer reads
|
|
27
|
+
* before publishing.
|
|
28
|
+
* 4. ROLLBACK / REMOVAL LEAVE NO STALE EFFECT — {@link rollbackIntegration} repoints
|
|
29
|
+
* `live` to a recorded known-good revision (planRevision-scoped; no page edit),
|
|
30
|
+
* and {@link removeIntegration} clears EVERY stage slot. Nothing partial is left
|
|
31
|
+
* behind; a suspended/uninstalled install serves nothing ({@link effectiveServing}).
|
|
32
|
+
*
|
|
33
|
+
* Fail-closed throughout: a cross-tenant evidence, a candidate claiming a non-candidate
|
|
34
|
+
* stage, or any secret-named facet aborts the transition with the state untouched.
|
|
35
|
+
*/
|
|
36
|
+
import type { AppInstallStatus } from "./registryService.js";
|
|
37
|
+
import {
|
|
38
|
+
INTEGRATION_CSP_DIRECTIVES,
|
|
39
|
+
findForbiddenFields,
|
|
40
|
+
verifyIntegrationPromotable,
|
|
41
|
+
type IntegrationEnvironment,
|
|
42
|
+
type IntegrationCandidateEvidence,
|
|
43
|
+
type EvidenceAssurance,
|
|
44
|
+
type ExecutionTier,
|
|
45
|
+
type IntegrationCspAdditions,
|
|
46
|
+
type IntegrationPromotionGateInput,
|
|
47
|
+
type IntegrationPromotionVerdict,
|
|
48
|
+
} from "@tot/public-runtime";
|
|
49
|
+
|
|
50
|
+
/** One resolved integration's public code identity — the "code" facet of a stage. */
|
|
51
|
+
export interface IntegrationCode {
|
|
52
|
+
appId: string;
|
|
53
|
+
version: string;
|
|
54
|
+
digest: string;
|
|
55
|
+
executionTier: ExecutionTier;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The effective facets serving at one lifecycle stage — the reviewable surface a
|
|
60
|
+
* candidate and the live state each carry. `planRevision` is the identity that keeps
|
|
61
|
+
* stages DISTINCT: two stages holding different revisions can never be conflated.
|
|
62
|
+
*/
|
|
63
|
+
export interface StageFacets {
|
|
64
|
+
planRevision: string;
|
|
65
|
+
/** The primary install's app version at this stage. */
|
|
66
|
+
version: string;
|
|
67
|
+
/** Per-integration public code identity. */
|
|
68
|
+
code: IntegrationCode[];
|
|
69
|
+
/** Per-integration resolved public config (no secret material). */
|
|
70
|
+
config: Record<string, Record<string, unknown>>;
|
|
71
|
+
/** Sorted, deduped exact CSP origins across every contributable directive. */
|
|
72
|
+
cspOrigins: string[];
|
|
73
|
+
/** Consent purpose ids this stage's plan depends on. */
|
|
74
|
+
consentNeeds: string[];
|
|
75
|
+
/** The elevated-integration assurance state (public, non-waivable floor only). */
|
|
76
|
+
assurance: EvidenceAssurance;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Sorted, deduped exact origins across all contributable directives. */
|
|
80
|
+
function flattenCspOrigins(additions: IntegrationCspAdditions | undefined): string[] {
|
|
81
|
+
if (!additions) return [];
|
|
82
|
+
const set = new Set<string>();
|
|
83
|
+
for (const directive of INTEGRATION_CSP_DIRECTIVES) {
|
|
84
|
+
for (const origin of additions[directive] ?? []) set.add(origin);
|
|
85
|
+
}
|
|
86
|
+
return [...set].sort();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Derive the reviewable stage facets from kernel-06 candidate evidence. The evidence
|
|
91
|
+
* is the SAME public-values-only artifact the mounts serve and the gate verifies, so
|
|
92
|
+
* a stage's facets are a property of the evidence, not a claim kept in sync with it.
|
|
93
|
+
*/
|
|
94
|
+
export function facetsFromEvidence(
|
|
95
|
+
evidence: IntegrationCandidateEvidence,
|
|
96
|
+
appId: string,
|
|
97
|
+
): StageFacets {
|
|
98
|
+
const code: IntegrationCode[] = evidence.integrations.map((i) => ({
|
|
99
|
+
appId: i.appId,
|
|
100
|
+
version: i.version,
|
|
101
|
+
digest: i.digest,
|
|
102
|
+
executionTier: i.executionTier,
|
|
103
|
+
}));
|
|
104
|
+
const primary = code.find((c) => c.appId === appId);
|
|
105
|
+
return {
|
|
106
|
+
planRevision: evidence.planRevision,
|
|
107
|
+
version: primary?.version ?? evidence.planRevision,
|
|
108
|
+
code,
|
|
109
|
+
config: evidence.publicConfig,
|
|
110
|
+
cspOrigins: flattenCspOrigins(evidence.cspAdditions),
|
|
111
|
+
consentNeeds: [...evidence.consentNeeds],
|
|
112
|
+
assurance: evidence.assurance,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** One integration's code identity changing between the live and candidate stages. */
|
|
117
|
+
export interface CodeChange {
|
|
118
|
+
appId: string;
|
|
119
|
+
actual: Omit<IntegrationCode, "appId"> | null;
|
|
120
|
+
candidate: Omit<IntegrationCode, "appId"> | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** One public-config field differing between live and candidate. */
|
|
124
|
+
export interface ConfigChange {
|
|
125
|
+
/** `<appId>.<key>` — never a value, so the diff itself carries no secret. */
|
|
126
|
+
field: string;
|
|
127
|
+
actual: unknown;
|
|
128
|
+
candidate: unknown;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** A set-valued facet's diff (CSP origins, consent purposes). */
|
|
132
|
+
export interface SetDiff {
|
|
133
|
+
added: string[];
|
|
134
|
+
removed: string[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The effective diff a reviewer reads before publishing — every facet a candidate
|
|
139
|
+
* changes against what is live: code, config, CSP origins, consent needs, assurance.
|
|
140
|
+
*/
|
|
141
|
+
export interface IntegrationCandidateDiff {
|
|
142
|
+
planRevision: { actual: string | null; candidate: string };
|
|
143
|
+
code: CodeChange[];
|
|
144
|
+
config: ConfigChange[];
|
|
145
|
+
csp: SetDiff;
|
|
146
|
+
consent: SetDiff;
|
|
147
|
+
assurance: { changed: boolean; actual: EvidenceAssurance | null; candidate: EvidenceAssurance };
|
|
148
|
+
/** True when no facet effectively changes — a publish that would alter nothing. */
|
|
149
|
+
unchanged: boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function setDiff(actual: readonly string[], candidate: readonly string[]): SetDiff {
|
|
153
|
+
const a = new Set(actual);
|
|
154
|
+
const c = new Set(candidate);
|
|
155
|
+
return {
|
|
156
|
+
added: candidate.filter((x) => !a.has(x)).sort(),
|
|
157
|
+
removed: actual.filter((x) => !c.has(x)).sort(),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Diff a candidate's facets against the live facets, facet by facet. */
|
|
162
|
+
export function diffStageFacets(
|
|
163
|
+
actual: StageFacets | null,
|
|
164
|
+
candidate: StageFacets,
|
|
165
|
+
): IntegrationCandidateDiff {
|
|
166
|
+
const code: CodeChange[] = [];
|
|
167
|
+
const appIds = new Set<string>([
|
|
168
|
+
...(actual?.code ?? []).map((c) => c.appId),
|
|
169
|
+
...candidate.code.map((c) => c.appId),
|
|
170
|
+
]);
|
|
171
|
+
for (const appId of [...appIds].sort()) {
|
|
172
|
+
const a = actual?.code.find((c) => c.appId === appId) ?? null;
|
|
173
|
+
const c = candidate.code.find((x) => x.appId === appId) ?? null;
|
|
174
|
+
const aFacet = a ? { version: a.version, digest: a.digest, executionTier: a.executionTier } : null;
|
|
175
|
+
const cFacet = c ? { version: c.version, digest: c.digest, executionTier: c.executionTier } : null;
|
|
176
|
+
if (JSON.stringify(aFacet) !== JSON.stringify(cFacet)) {
|
|
177
|
+
code.push({ appId, actual: aFacet, candidate: cFacet });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const config: ConfigChange[] = [];
|
|
182
|
+
const actualConfig = actual?.config ?? {};
|
|
183
|
+
const configApps = new Set<string>([...Object.keys(actualConfig), ...Object.keys(candidate.config)]);
|
|
184
|
+
for (const appId of [...configApps].sort()) {
|
|
185
|
+
const aApp = actualConfig[appId] ?? {};
|
|
186
|
+
const cApp = candidate.config[appId] ?? {};
|
|
187
|
+
const keys = new Set<string>([...Object.keys(aApp), ...Object.keys(cApp)]);
|
|
188
|
+
for (const key of [...keys].sort()) {
|
|
189
|
+
const aVal = aApp[key];
|
|
190
|
+
const cVal = cApp[key];
|
|
191
|
+
if (JSON.stringify(aVal) !== JSON.stringify(cVal)) {
|
|
192
|
+
config.push({ field: `${appId}.${key}`, actual: aVal, candidate: cVal });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const csp = setDiff(actual?.cspOrigins ?? [], candidate.cspOrigins);
|
|
198
|
+
const consent = setDiff(actual?.consentNeeds ?? [], candidate.consentNeeds);
|
|
199
|
+
const assuranceChanged = JSON.stringify(actual?.assurance ?? null) !== JSON.stringify(candidate.assurance);
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
planRevision: { actual: actual?.planRevision ?? null, candidate: candidate.planRevision },
|
|
203
|
+
code,
|
|
204
|
+
config,
|
|
205
|
+
csp,
|
|
206
|
+
consent,
|
|
207
|
+
assurance: { changed: assuranceChanged, actual: actual?.assurance ?? null, candidate: candidate.assurance },
|
|
208
|
+
unchanged:
|
|
209
|
+
code.length === 0 &&
|
|
210
|
+
config.length === 0 &&
|
|
211
|
+
csp.added.length === 0 &&
|
|
212
|
+
csp.removed.length === 0 &&
|
|
213
|
+
consent.added.length === 0 &&
|
|
214
|
+
consent.removed.length === 0 &&
|
|
215
|
+
!assuranceChanged,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The lifecycle actions ext-admin-01's IA names; each is a transition below. */
|
|
220
|
+
export type LifecycleAction =
|
|
221
|
+
| "preview"
|
|
222
|
+
| "publish"
|
|
223
|
+
| "rollback"
|
|
224
|
+
| "suspend"
|
|
225
|
+
| "resume"
|
|
226
|
+
| "uninstall";
|
|
227
|
+
|
|
228
|
+
/** One audit record — actor, action, targeted version, result. Never secret-bearing. */
|
|
229
|
+
export interface LifecycleEvent {
|
|
230
|
+
action: LifecycleAction;
|
|
231
|
+
actor: string;
|
|
232
|
+
at: string;
|
|
233
|
+
/** The plan revision the action targeted (candidate's, promoted, or rolled-to), or null. */
|
|
234
|
+
planRevision: string | null;
|
|
235
|
+
result: "applied" | "refused";
|
|
236
|
+
reason?: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The full tenant-scoped lifecycle state for ONE install. The four env slots are
|
|
241
|
+
* independent keys — a transition advances only the ones it names, so a stage read
|
|
242
|
+
* never conflates with another (acceptance clause 1).
|
|
243
|
+
*/
|
|
244
|
+
export interface TenantIntegrationLifecycleState {
|
|
245
|
+
tenantPublicId: string;
|
|
246
|
+
installId: string;
|
|
247
|
+
appId: string;
|
|
248
|
+
installStatus: AppInstallStatus;
|
|
249
|
+
/** The four DISTINCT env slots — never merged. */
|
|
250
|
+
stages: Record<IntegrationEnvironment, StageFacets | null>;
|
|
251
|
+
/** Prior good live facets to roll back to; most-recent last. planRevision-scoped. */
|
|
252
|
+
knownGood: StageFacets[];
|
|
253
|
+
/** Applied transitions, in order — the audit trail. Refusals are surfaced on the result, not folded here. */
|
|
254
|
+
history: LifecycleEvent[];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface InitialLifecycleInput {
|
|
258
|
+
tenantPublicId: string;
|
|
259
|
+
installId: string;
|
|
260
|
+
appId: string;
|
|
261
|
+
installStatus?: AppInstallStatus;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** A fresh lifecycle state — every stage empty, nothing serving, no rollback target. */
|
|
265
|
+
export function initialLifecycleState(input: InitialLifecycleInput): TenantIntegrationLifecycleState {
|
|
266
|
+
return {
|
|
267
|
+
tenantPublicId: input.tenantPublicId,
|
|
268
|
+
installId: input.installId,
|
|
269
|
+
appId: input.appId,
|
|
270
|
+
installStatus: input.installStatus ?? "active",
|
|
271
|
+
stages: { local: null, candidate: null, staged: null, live: null },
|
|
272
|
+
knownGood: [],
|
|
273
|
+
history: [],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export type LifecycleRefusalReason =
|
|
278
|
+
| "not_active"
|
|
279
|
+
| "no_candidate"
|
|
280
|
+
| "version_mismatch"
|
|
281
|
+
| "gate_blocked"
|
|
282
|
+
| "stale_live"
|
|
283
|
+
| "no_known_good"
|
|
284
|
+
| "unknown_target"
|
|
285
|
+
| "already_removed"
|
|
286
|
+
| "not_suspended"
|
|
287
|
+
| "invalid_stage"
|
|
288
|
+
| "cross_tenant"
|
|
289
|
+
| "integrity";
|
|
290
|
+
|
|
291
|
+
export interface LifecycleApplied {
|
|
292
|
+
ok: true;
|
|
293
|
+
/** The advanced state — a NEW value; the input is never mutated. */
|
|
294
|
+
state: TenantIntegrationLifecycleState;
|
|
295
|
+
event: LifecycleEvent;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface LifecycleRefused {
|
|
299
|
+
ok: false;
|
|
300
|
+
reason: LifecycleRefusalReason;
|
|
301
|
+
blockers: string[];
|
|
302
|
+
/** The UNCHANGED input state — a refusal never advances ACTUAL (same reference). */
|
|
303
|
+
state: TenantIntegrationLifecycleState;
|
|
304
|
+
event: LifecycleEvent;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export type LifecycleResult = LifecycleApplied | LifecycleRefused;
|
|
308
|
+
|
|
309
|
+
/** A `preview` result also carries the effective diff the reviewer reads. */
|
|
310
|
+
export interface PreviewApplied extends LifecycleApplied {
|
|
311
|
+
diff: IntegrationCandidateDiff;
|
|
312
|
+
}
|
|
313
|
+
export type PreviewResult = PreviewApplied | LifecycleRefused;
|
|
314
|
+
|
|
315
|
+
export interface LifecycleActionContext {
|
|
316
|
+
/** Opaque audit actor (already pseudonymized upstream) — never PII. */
|
|
317
|
+
actor: string;
|
|
318
|
+
now?: () => string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function stamp(ctx: LifecycleActionContext): string {
|
|
322
|
+
return (ctx.now ?? (() => new Date().toISOString()))();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function refuse(
|
|
326
|
+
state: TenantIntegrationLifecycleState,
|
|
327
|
+
action: LifecycleAction,
|
|
328
|
+
ctx: LifecycleActionContext,
|
|
329
|
+
reason: LifecycleRefusalReason,
|
|
330
|
+
blockers: string[],
|
|
331
|
+
planRevision: string | null = null,
|
|
332
|
+
): LifecycleRefused {
|
|
333
|
+
return {
|
|
334
|
+
ok: false,
|
|
335
|
+
reason,
|
|
336
|
+
blockers,
|
|
337
|
+
state,
|
|
338
|
+
event: { action, actor: ctx.actor, at: stamp(ctx), planRevision, result: "refused", reason },
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function apply(
|
|
343
|
+
state: TenantIntegrationLifecycleState,
|
|
344
|
+
action: LifecycleAction,
|
|
345
|
+
ctx: LifecycleActionContext,
|
|
346
|
+
next: Omit<TenantIntegrationLifecycleState, "history">,
|
|
347
|
+
planRevision: string | null,
|
|
348
|
+
): LifecycleApplied {
|
|
349
|
+
const event: LifecycleEvent = { action, actor: ctx.actor, at: stamp(ctx), planRevision, result: "applied" };
|
|
350
|
+
return { ok: true, state: { ...next, history: [...state.history, event] }, event };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Fail-closed: no facet may name secret material (defense in depth over the evidence scan). */
|
|
354
|
+
function secretPaths(facets: StageFacets): string[] {
|
|
355
|
+
return findForbiddenFields(facets, "facets").map((v) => v.path);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* `preview` — record a prepared candidate in the `candidate` slot and return the
|
|
360
|
+
* effective diff against what is live. Fail-closed: a cross-tenant evidence, a
|
|
361
|
+
* non-`candidate` environment (a candidate is never live/staged/local), or a
|
|
362
|
+
* secret-named facet aborts with the state untouched. Only the `candidate` slot
|
|
363
|
+
* moves — `local` / `staged` / `live` are never touched here.
|
|
364
|
+
*/
|
|
365
|
+
export function stageCandidate(
|
|
366
|
+
state: TenantIntegrationLifecycleState,
|
|
367
|
+
evidence: IntegrationCandidateEvidence,
|
|
368
|
+
ctx: LifecycleActionContext,
|
|
369
|
+
): PreviewResult {
|
|
370
|
+
if (state.installStatus !== "active") {
|
|
371
|
+
return refuse(state, "preview", ctx, "not_active", [`Install is ${state.installStatus}; cannot stage a candidate.`]);
|
|
372
|
+
}
|
|
373
|
+
if (evidence.tenantPublicId !== state.tenantPublicId) {
|
|
374
|
+
return refuse(state, "preview", ctx, "cross_tenant", [
|
|
375
|
+
`Candidate evidence is scoped to "${evidence.tenantPublicId}", not "${state.tenantPublicId}".`,
|
|
376
|
+
]);
|
|
377
|
+
}
|
|
378
|
+
if (evidence.environment.environment !== "candidate") {
|
|
379
|
+
return refuse(state, "preview", ctx, "invalid_stage", [
|
|
380
|
+
`Candidate evidence reports the "${evidence.environment.environment}" environment; a candidate must be "candidate".`,
|
|
381
|
+
]);
|
|
382
|
+
}
|
|
383
|
+
const facets = facetsFromEvidence(evidence, state.appId);
|
|
384
|
+
const forbidden = secretPaths(facets);
|
|
385
|
+
if (forbidden.length > 0) {
|
|
386
|
+
return refuse(state, "preview", ctx, "integrity", [`Candidate facets named secret material: ${forbidden.join(", ")}.`]);
|
|
387
|
+
}
|
|
388
|
+
const diff = diffStageFacets(state.stages.live, facets);
|
|
389
|
+
const applied = apply(
|
|
390
|
+
state,
|
|
391
|
+
"preview",
|
|
392
|
+
ctx,
|
|
393
|
+
{ ...state, stages: { ...state.stages, candidate: facets } },
|
|
394
|
+
facets.planRevision,
|
|
395
|
+
);
|
|
396
|
+
return { ...applied, diff };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export interface PublishOptions {
|
|
400
|
+
/**
|
|
401
|
+
* The caller's last-seen live revision — an optimistic concurrency guard. When set
|
|
402
|
+
* and it no longer matches the live slot, the publish is refused `stale_live`
|
|
403
|
+
* (re-review before publishing). Pass `null` to assert nothing is live yet.
|
|
404
|
+
*/
|
|
405
|
+
expectedLiveRevision?: string | null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* `publish` — promote the pending candidate to `live` THROUGH the policy-06 gate.
|
|
410
|
+
* The gate ({@link verifyIntegrationPromotable}) composes CSP / consent / obligations
|
|
411
|
+
* / assurance / candidate-served-parity / compliance and is fail-closed; `live` flips
|
|
412
|
+
* ONLY on `verdict.ok`. A blocked candidate returns the input state UNCHANGED — the
|
|
413
|
+
* last-good `live` facets never move (no half-applied publish). On success the prior
|
|
414
|
+
* live becomes a known-good rollback target and the `candidate` slot is consumed.
|
|
415
|
+
*/
|
|
416
|
+
export function publishCandidate(
|
|
417
|
+
state: TenantIntegrationLifecycleState,
|
|
418
|
+
gate: IntegrationPromotionGateInput,
|
|
419
|
+
ctx: LifecycleActionContext,
|
|
420
|
+
opts: PublishOptions = {},
|
|
421
|
+
): LifecycleResult {
|
|
422
|
+
if (state.installStatus !== "active") {
|
|
423
|
+
return refuse(state, "publish", ctx, "not_active", [`Install is ${state.installStatus}; cannot publish.`]);
|
|
424
|
+
}
|
|
425
|
+
const candidate = state.stages.candidate;
|
|
426
|
+
if (!candidate) {
|
|
427
|
+
return refuse(state, "publish", ctx, "no_candidate", ["No candidate is staged to publish; preview one first."]);
|
|
428
|
+
}
|
|
429
|
+
// The gate must target THIS candidate — bind its evidence revision to the staged slot.
|
|
430
|
+
const gateRevision = gate.candidateEvidence?.planRevision ?? null;
|
|
431
|
+
if (gateRevision !== candidate.planRevision) {
|
|
432
|
+
return refuse(
|
|
433
|
+
state,
|
|
434
|
+
"publish",
|
|
435
|
+
ctx,
|
|
436
|
+
"version_mismatch",
|
|
437
|
+
[
|
|
438
|
+
`Promotion gate is for plan revision "${gateRevision ?? "(absent)"}" but the staged candidate is "${candidate.planRevision}".`,
|
|
439
|
+
],
|
|
440
|
+
candidate.planRevision,
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
// Optimistic concurrency against the live slot (re-review if it moved under us).
|
|
444
|
+
if (opts.expectedLiveRevision !== undefined) {
|
|
445
|
+
const liveRevision = state.stages.live?.planRevision ?? null;
|
|
446
|
+
if (opts.expectedLiveRevision !== liveRevision) {
|
|
447
|
+
return refuse(
|
|
448
|
+
state,
|
|
449
|
+
"publish",
|
|
450
|
+
ctx,
|
|
451
|
+
"stale_live",
|
|
452
|
+
[`Live moved to "${liveRevision ?? "(none)"}" (you last saw "${opts.expectedLiveRevision ?? "(none)"}"); re-review before publishing.`],
|
|
453
|
+
candidate.planRevision,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Route THROUGH the policy-06 gate — the single fail-closed authority for reaching live.
|
|
459
|
+
const verdict: IntegrationPromotionVerdict = verifyIntegrationPromotable(gate);
|
|
460
|
+
if (!verdict.ok) {
|
|
461
|
+
return refuse(state, "publish", ctx, "gate_blocked", verdict.blockers, candidate.planRevision);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const priorLive = state.stages.live;
|
|
465
|
+
const knownGood = priorLive ? [...state.knownGood, priorLive] : state.knownGood;
|
|
466
|
+
return apply(
|
|
467
|
+
state,
|
|
468
|
+
"publish",
|
|
469
|
+
ctx,
|
|
470
|
+
{
|
|
471
|
+
...state,
|
|
472
|
+
stages: { ...state.stages, live: candidate, candidate: null },
|
|
473
|
+
knownGood,
|
|
474
|
+
},
|
|
475
|
+
candidate.planRevision,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export interface RollbackOptions {
|
|
480
|
+
/** Explicit known-good revision to restore. Omit for "one step back" (the most recent). */
|
|
481
|
+
targetRevision?: string;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* `rollback` — repoint `live` to a recorded known-good revision. planRevision-scoped:
|
|
486
|
+
* it swaps the whole `live` facets to a previously-good revision in one step — no page
|
|
487
|
+
* edit, nothing half-applied. The rolled-FROM revision is NOT re-recorded as known-good
|
|
488
|
+
* (it is the suspect one); known-good newer than the restored point is dropped so a
|
|
489
|
+
* further "one step back" is well-defined.
|
|
490
|
+
*/
|
|
491
|
+
export function rollbackIntegration(
|
|
492
|
+
state: TenantIntegrationLifecycleState,
|
|
493
|
+
ctx: LifecycleActionContext,
|
|
494
|
+
opts: RollbackOptions = {},
|
|
495
|
+
): LifecycleResult {
|
|
496
|
+
if (state.installStatus !== "active") {
|
|
497
|
+
return refuse(state, "rollback", ctx, "not_active", [`Install is ${state.installStatus}; nothing is live to roll back.`]);
|
|
498
|
+
}
|
|
499
|
+
if (state.knownGood.length === 0) {
|
|
500
|
+
return refuse(state, "rollback", ctx, "no_known_good", ["No version has gone live before; there is no known-good version to roll back to."]);
|
|
501
|
+
}
|
|
502
|
+
const idx =
|
|
503
|
+
opts.targetRevision === undefined
|
|
504
|
+
? state.knownGood.length - 1
|
|
505
|
+
: state.knownGood.findIndex((f) => f.planRevision === opts.targetRevision);
|
|
506
|
+
const restored = idx < 0 ? undefined : state.knownGood[idx];
|
|
507
|
+
if (!restored) {
|
|
508
|
+
return refuse(state, "rollback", ctx, "unknown_target", [
|
|
509
|
+
`No known-good version "${opts.targetRevision}" is available to roll back to.`,
|
|
510
|
+
]);
|
|
511
|
+
}
|
|
512
|
+
return apply(
|
|
513
|
+
state,
|
|
514
|
+
"rollback",
|
|
515
|
+
ctx,
|
|
516
|
+
{
|
|
517
|
+
...state,
|
|
518
|
+
stages: { ...state.stages, live: restored },
|
|
519
|
+
knownGood: state.knownGood.slice(0, idx),
|
|
520
|
+
},
|
|
521
|
+
restored.planRevision,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* `uninstall` — remove the integration completely. Every stage slot is cleared and
|
|
527
|
+
* the rollback history dropped, so nothing serves and no stale candidate/staged/live
|
|
528
|
+
* facet is left behind. The install status becomes `uninstalled` (terminal).
|
|
529
|
+
*/
|
|
530
|
+
export function removeIntegration(
|
|
531
|
+
state: TenantIntegrationLifecycleState,
|
|
532
|
+
ctx: LifecycleActionContext,
|
|
533
|
+
): LifecycleResult {
|
|
534
|
+
if (state.installStatus === "uninstalled") {
|
|
535
|
+
return refuse(state, "uninstall", ctx, "already_removed", ["Install is already uninstalled."]);
|
|
536
|
+
}
|
|
537
|
+
return apply(
|
|
538
|
+
state,
|
|
539
|
+
"uninstall",
|
|
540
|
+
ctx,
|
|
541
|
+
{
|
|
542
|
+
...state,
|
|
543
|
+
installStatus: "uninstalled",
|
|
544
|
+
stages: { local: null, candidate: null, staged: null, live: null },
|
|
545
|
+
knownGood: [],
|
|
546
|
+
},
|
|
547
|
+
null,
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** `suspend` — stop serving without removing. Live facets are retained for `resume`. */
|
|
552
|
+
export function suspendIntegration(
|
|
553
|
+
state: TenantIntegrationLifecycleState,
|
|
554
|
+
ctx: LifecycleActionContext,
|
|
555
|
+
): LifecycleResult {
|
|
556
|
+
if (state.installStatus !== "active") {
|
|
557
|
+
return refuse(state, "suspend", ctx, "not_active", [`Install is ${state.installStatus}; cannot suspend.`]);
|
|
558
|
+
}
|
|
559
|
+
return apply(state, "suspend", ctx, { ...state, installStatus: "suspended" }, state.stages.live?.planRevision ?? null);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** `resume` — restore a suspended install to active; serving resumes exactly where it stopped. */
|
|
563
|
+
export function resumeIntegration(
|
|
564
|
+
state: TenantIntegrationLifecycleState,
|
|
565
|
+
ctx: LifecycleActionContext,
|
|
566
|
+
): LifecycleResult {
|
|
567
|
+
if (state.installStatus !== "suspended") {
|
|
568
|
+
return refuse(state, "resume", ctx, "not_suspended", [`Install is ${state.installStatus}; only a suspended install can resume.`]);
|
|
569
|
+
}
|
|
570
|
+
return apply(state, "resume", ctx, { ...state, installStatus: "active" }, state.stages.live?.planRevision ?? null);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* What ACTUALLY serves in production right now — the `live` facets only while the
|
|
575
|
+
* install is active. A suspended or uninstalled install serves nothing, so a withdrawn
|
|
576
|
+
* install can never leave a stale live effect.
|
|
577
|
+
*/
|
|
578
|
+
export function effectiveServing(state: TenantIntegrationLifecycleState): StageFacets | null {
|
|
579
|
+
return state.installStatus === "active" ? state.stages.live : null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** The facets at one env stage — a DISTINCT read that never returns another stage's value. */
|
|
583
|
+
export function stageFacets(
|
|
584
|
+
state: TenantIntegrationLifecycleState,
|
|
585
|
+
env: IntegrationEnvironment,
|
|
586
|
+
): StageFacets | null {
|
|
587
|
+
return state.stages[env];
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export interface LifecycleRollbackAvailability {
|
|
591
|
+
available: boolean;
|
|
592
|
+
targetRevision: string | null;
|
|
593
|
+
targetVersion: string | null;
|
|
594
|
+
reason: string;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** Accurate rollback availability — reflects a real recorded known-good revision. */
|
|
598
|
+
export function rollbackAvailability(state: TenantIntegrationLifecycleState): LifecycleRollbackAvailability {
|
|
599
|
+
const target = state.knownGood.at(-1) ?? null;
|
|
600
|
+
if (!target) {
|
|
601
|
+
return {
|
|
602
|
+
available: false,
|
|
603
|
+
targetRevision: null,
|
|
604
|
+
targetVersion: null,
|
|
605
|
+
reason: "No version has gone live yet; there is no known-good version to roll back to.",
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
available: true,
|
|
610
|
+
targetRevision: target.planRevision,
|
|
611
|
+
targetVersion: target.version,
|
|
612
|
+
reason: `A known-good version (${target.version}) is available to roll back to.`,
|
|
613
|
+
};
|
|
614
|
+
}
|