@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,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport-free controller for the integration-config lifecycle write actions
|
|
3
|
+
* ext-admin-07 uniquely owns — `preview` / `publish` / `rollback` — over the pure
|
|
4
|
+
* {@link integrationLifecycle} state machine. (The install-status actions
|
|
5
|
+
* `suspend` / `resume` / `uninstall` already have their own registry-backed routes;
|
|
6
|
+
* this controller does not duplicate them.)
|
|
7
|
+
*
|
|
8
|
+
* It is the seam the Astro route wires real I/O into: loading the current lifecycle
|
|
9
|
+
* state, resolving the candidate evidence to preview, resolving the policy-06 gate
|
|
10
|
+
* input to publish, and persisting an advanced state — every effect an injected
|
|
11
|
+
* dependency, so the controller stays unit-testable against in-memory deps. The pure
|
|
12
|
+
* transitions decide; this only routes the request to one and maps the result to an
|
|
13
|
+
* HTTP status. A refusal never persists (the state is returned unchanged), so a
|
|
14
|
+
* blocked publish leaves the ACTUAL last-good state exactly as loaded.
|
|
15
|
+
*/
|
|
16
|
+
import type { IntegrationCandidateEvidence, IntegrationPromotionGateInput } from "@tot/public-runtime";
|
|
17
|
+
import {
|
|
18
|
+
stageCandidate,
|
|
19
|
+
publishCandidate,
|
|
20
|
+
rollbackIntegration,
|
|
21
|
+
type TenantIntegrationLifecycleState,
|
|
22
|
+
type LifecycleResult,
|
|
23
|
+
type PreviewResult,
|
|
24
|
+
type LifecycleRefusalReason,
|
|
25
|
+
} from "./integrationLifecycle.js";
|
|
26
|
+
|
|
27
|
+
/** The write actions this controller owns (status actions live on their own routes). */
|
|
28
|
+
export type LifecycleWriteAction = "preview" | "publish" | "rollback";
|
|
29
|
+
|
|
30
|
+
export interface LifecycleRequest {
|
|
31
|
+
action: LifecycleWriteAction;
|
|
32
|
+
installId: string;
|
|
33
|
+
/** Opaque audit actor (already pseudonymized by the session resolver). */
|
|
34
|
+
actor: string;
|
|
35
|
+
/** `publish` only — optimistic concurrency against the live slot the caller last saw. */
|
|
36
|
+
expectedLiveRevision?: string | null;
|
|
37
|
+
/** `rollback` only — explicit known-good revision; omit for "one step back". */
|
|
38
|
+
targetRevision?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Everything the controller needs to reach the world — each an injected effect. */
|
|
42
|
+
export interface LifecycleDeps {
|
|
43
|
+
/** The current lifecycle state for an install; `null` when not materialized yet. */
|
|
44
|
+
load(installId: string): Promise<TenantIntegrationLifecycleState | null>;
|
|
45
|
+
/** Persist an advanced state (called only when a transition applied). */
|
|
46
|
+
save(state: TenantIntegrationLifecycleState): Promise<void>;
|
|
47
|
+
/** The candidate evidence to stage for `preview`; `null` when none is prepared. */
|
|
48
|
+
resolveCandidateEvidence(installId: string): Promise<IntegrationCandidateEvidence | null>;
|
|
49
|
+
/** The policy-06 gate input for `publish`; `null` when it cannot be materialized. */
|
|
50
|
+
resolveGate(installId: string): Promise<IntegrationPromotionGateInput | null>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface LifecycleControllerResult {
|
|
54
|
+
status: number;
|
|
55
|
+
body: unknown;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** HTTP status for each pure-core refusal reason — the one fail-closed mapping. */
|
|
59
|
+
const STATUS_BY_REASON: Record<LifecycleRefusalReason, number> = {
|
|
60
|
+
not_active: 409,
|
|
61
|
+
no_candidate: 409,
|
|
62
|
+
version_mismatch: 409,
|
|
63
|
+
gate_blocked: 422,
|
|
64
|
+
stale_live: 409,
|
|
65
|
+
no_known_good: 409,
|
|
66
|
+
unknown_target: 404,
|
|
67
|
+
already_removed: 409,
|
|
68
|
+
not_suspended: 409,
|
|
69
|
+
invalid_stage: 422,
|
|
70
|
+
cross_tenant: 403,
|
|
71
|
+
integrity: 500,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function refusalResponse(result: Exclude<LifecycleResult, { ok: true }>): LifecycleControllerResult {
|
|
75
|
+
return {
|
|
76
|
+
status: STATUS_BY_REASON[result.reason],
|
|
77
|
+
body: { error: result.blockers.join("; "), code: result.reason, event: result.event },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Execute one lifecycle write action end-to-end: load state, run the pure transition,
|
|
83
|
+
* and persist ONLY on success. Returns the HTTP status + body the route serializes.
|
|
84
|
+
* Fail-closed: an unmaterialized install (or a `preview`/`publish` whose evidence/gate
|
|
85
|
+
* cannot be resolved) is a 409, never a fabricated success.
|
|
86
|
+
*/
|
|
87
|
+
export async function handleLifecycleRequest(
|
|
88
|
+
deps: LifecycleDeps,
|
|
89
|
+
req: LifecycleRequest,
|
|
90
|
+
): Promise<LifecycleControllerResult> {
|
|
91
|
+
const state = await deps.load(req.installId);
|
|
92
|
+
if (!state) {
|
|
93
|
+
return { status: 409, body: { error: "integration is not materialized", code: "not_materialized" } };
|
|
94
|
+
}
|
|
95
|
+
const ctx = { actor: req.actor };
|
|
96
|
+
|
|
97
|
+
if (req.action === "preview") {
|
|
98
|
+
const evidence = await deps.resolveCandidateEvidence(req.installId);
|
|
99
|
+
if (!evidence) {
|
|
100
|
+
return { status: 409, body: { error: "no candidate is prepared to preview", code: "no_candidate" } };
|
|
101
|
+
}
|
|
102
|
+
const result: PreviewResult = stageCandidate(state, evidence, ctx);
|
|
103
|
+
if (!result.ok) return refusalResponse(result);
|
|
104
|
+
await deps.save(result.state);
|
|
105
|
+
return { status: 200, body: { event: result.event, diff: result.diff } };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (req.action === "publish") {
|
|
109
|
+
const gate = await deps.resolveGate(req.installId);
|
|
110
|
+
if (!gate) {
|
|
111
|
+
return { status: 409, body: { error: "candidate cannot be gated for promotion", code: "not_materialized" } };
|
|
112
|
+
}
|
|
113
|
+
const opts = req.expectedLiveRevision !== undefined ? { expectedLiveRevision: req.expectedLiveRevision } : {};
|
|
114
|
+
const result = publishCandidate(state, gate, ctx, opts);
|
|
115
|
+
if (!result.ok) return refusalResponse(result);
|
|
116
|
+
await deps.save(result.state);
|
|
117
|
+
return { status: 200, body: { event: result.event, liveRevision: result.state.stages.live?.planRevision ?? null } };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const opts = req.targetRevision !== undefined ? { targetRevision: req.targetRevision } : {};
|
|
121
|
+
const result = rollbackIntegration(state, ctx, opts);
|
|
122
|
+
if (!result.ok) return refusalResponse(result);
|
|
123
|
+
await deps.save(result.state);
|
|
124
|
+
return { status: 200, body: { event: result.event, liveRevision: result.state.stages.live?.planRevision ?? null } };
|
|
125
|
+
}
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The effective integration admin read model (ext-admin-02) — the tenant-scoped
|
|
3
|
+
* data behind ext-admin-01's `/admin#integrations` IA.
|
|
4
|
+
*
|
|
5
|
+
* This assembler COMPOSES the already-materialized runtime contracts into one safe
|
|
6
|
+
* admin view: the tenant's desired config, the resolved effective plan
|
|
7
|
+
* (ext-kernel-03), its compiled CSP additions (ext-policy-02), the consent snapshot
|
|
8
|
+
* (ext-policy-03), the elevated-integration assurance decision (ext-policy-05), the
|
|
9
|
+
* pending candidate evidence + promotion verdict (ext-kernel-06), and the recorded
|
|
10
|
+
* known-good version. Like the resolver it consumes, it is PURE and SYNCHRONOUS over
|
|
11
|
+
* already-materialized inputs — materializing them is the control plane's job (the
|
|
12
|
+
* route below), composing them into a safe view is this function's.
|
|
13
|
+
*
|
|
14
|
+
* It holds five safety properties by construction, one per acceptance clause:
|
|
15
|
+
* 1. TENANT-SCOPED — every input's tenant is asserted equal to the resolved scope;
|
|
16
|
+
* a foreign plan/evidence/assurance/install is a fail-closed violation, so a
|
|
17
|
+
* mis-plumbed control plane can never leak another tenant's state. (The route
|
|
18
|
+
* is the first gate: a vendor/other tenant is denied before assembly.)
|
|
19
|
+
* 2. SECRETS REDACTED — desired config is the one raw tenant-input surface; its
|
|
20
|
+
* secret-named keys are DROPPED (their paths recorded, never their values), and
|
|
21
|
+
* the whole assembled model is re-scanned with {@link findForbiddenFields} as
|
|
22
|
+
* defense in depth: any surviving secret field fails the assembly closed.
|
|
23
|
+
* 3. DESIRED-vs-EFFECTIVE EXPLICIT — {@link DesiredEffectiveDrift} names each field
|
|
24
|
+
* whose desired input differs from what is effectively serving, and marks a
|
|
25
|
+
* desired config that has not materialized into any effective plan at all.
|
|
26
|
+
* 4. CANDIDATE / STAGED / LIVE UNMISTAKABLE — the pending candidate and the serving
|
|
27
|
+
* effective state are SEPARATE typed slots that are never merged; each carries
|
|
28
|
+
* its own lifecycle stage, and a candidate that claims the live stage is a
|
|
29
|
+
* fail-closed violation (a candidate is never live).
|
|
30
|
+
* 5. ROLLBACK ACCURATE — {@link RollbackAvailability} is available only when a real
|
|
31
|
+
* known-good live version is recorded, and it says whether that target is already
|
|
32
|
+
* the effective version (a rollback that would change nothing).
|
|
33
|
+
*/
|
|
34
|
+
import type { AppInstallStatus } from "./registryService.js";
|
|
35
|
+
import {
|
|
36
|
+
type IntegrationAuthorityTier,
|
|
37
|
+
type IntegrationInstallStatus,
|
|
38
|
+
type IntegrationInventoryItem,
|
|
39
|
+
type IntegrationDetailView,
|
|
40
|
+
type IntegrationEffectiveAuthority,
|
|
41
|
+
type IntegrationDiagnosticIssue,
|
|
42
|
+
type IntegrationOwner,
|
|
43
|
+
} from "./integrationsView.js";
|
|
44
|
+
import {
|
|
45
|
+
findForbiddenFields,
|
|
46
|
+
isForbiddenKey,
|
|
47
|
+
decideAssurance,
|
|
48
|
+
verifyCandidatePromotable,
|
|
49
|
+
INTEGRATION_CSP_DIRECTIVES,
|
|
50
|
+
type ContractResult,
|
|
51
|
+
type ContractViolation,
|
|
52
|
+
type EffectivePlan,
|
|
53
|
+
type CompiledIntegrationCsp,
|
|
54
|
+
type IntegrationCspAdditions,
|
|
55
|
+
type ConsentSnapshot,
|
|
56
|
+
type ConsentPurpose,
|
|
57
|
+
type IntegrationEnvironment,
|
|
58
|
+
type ExecutionTier,
|
|
59
|
+
type ComplianceAssuranceRecord,
|
|
60
|
+
type AssuranceDecisionContext,
|
|
61
|
+
type AssuranceSurface,
|
|
62
|
+
type AssuranceClassification,
|
|
63
|
+
type AssuranceDenialReason,
|
|
64
|
+
type IntegrationCandidateEvidence,
|
|
65
|
+
type ServedObservation,
|
|
66
|
+
} from "@tot/public-runtime";
|
|
67
|
+
|
|
68
|
+
/** A lifecycle stage — the same closed set the runtime environment uses. */
|
|
69
|
+
export type IntegrationLifecycleStage = IntegrationEnvironment;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* What is CURRENTLY SERVING for this install — the ACTUAL/effective state. It carries
|
|
73
|
+
* its OWN stage (`local`/`staged`/`live`) and is a slot distinct from the candidate:
|
|
74
|
+
* the two are never merged, so a reviewer can never mistake the pending candidate for
|
|
75
|
+
* what is live.
|
|
76
|
+
*/
|
|
77
|
+
export interface EffectiveIntegrationState {
|
|
78
|
+
stage: IntegrationLifecycleStage;
|
|
79
|
+
planRevision: string;
|
|
80
|
+
version: string;
|
|
81
|
+
cspOrigins: string[];
|
|
82
|
+
consentEligible: ConsentPurpose[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The PENDING candidate under review — always the `candidate` stage (asserted; a
|
|
87
|
+
* candidate is never live) and always its own slot, so it is never conflated with the
|
|
88
|
+
* effective serving state.
|
|
89
|
+
*/
|
|
90
|
+
export interface CandidateIntegrationState {
|
|
91
|
+
stage: "candidate";
|
|
92
|
+
planRevision: string;
|
|
93
|
+
version: string;
|
|
94
|
+
promotable: boolean;
|
|
95
|
+
blockers: string[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The tenant's DESIRED (input) configuration, with secret-named keys already redacted. */
|
|
99
|
+
export interface DesiredIntegrationState {
|
|
100
|
+
version: string;
|
|
101
|
+
config: Record<string, unknown>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** One field whose desired input differs from what is effectively serving. */
|
|
105
|
+
export interface DesiredEffectiveChange {
|
|
106
|
+
field: string;
|
|
107
|
+
desired: unknown;
|
|
108
|
+
effective: unknown;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Whether the desired input matches the effective serving state, made explicit. */
|
|
112
|
+
export interface DesiredEffectiveDrift {
|
|
113
|
+
/** True only when an effective plan exists AND every desired field matches it. */
|
|
114
|
+
inSync: boolean;
|
|
115
|
+
/** True when a desired config exists but nothing has materialized into an effective plan yet. */
|
|
116
|
+
unmaterialized: boolean;
|
|
117
|
+
changes: DesiredEffectiveChange[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Accurate rollback availability — reflects a real recorded known-good live version. */
|
|
121
|
+
export interface RollbackAvailability {
|
|
122
|
+
available: boolean;
|
|
123
|
+
targetVersion: string | null;
|
|
124
|
+
targetRevision: string | null;
|
|
125
|
+
/** True when the known-good target is already the effective version (a no-op rollback). */
|
|
126
|
+
isCurrent: boolean;
|
|
127
|
+
reason: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The elevated-integration assurance state, decided fail-closed by ext-policy-05. */
|
|
131
|
+
export interface AssuranceReadState {
|
|
132
|
+
surface: AssuranceSurface;
|
|
133
|
+
classification: AssuranceClassification;
|
|
134
|
+
approved: boolean;
|
|
135
|
+
outsideGuarantee: boolean;
|
|
136
|
+
denials: AssuranceDenialReason[];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** One assembled read-model row: the full effective view of one install. */
|
|
140
|
+
export interface IntegrationReadModelItem {
|
|
141
|
+
installId: string;
|
|
142
|
+
appId: string;
|
|
143
|
+
purpose: string;
|
|
144
|
+
/** Coarse admin-display status derived from the raw install status + lifecycle slots. */
|
|
145
|
+
status: IntegrationInstallStatus;
|
|
146
|
+
/** The raw install-record status — kept DISTINCT from the derived display status. */
|
|
147
|
+
installStatus: AppInstallStatus;
|
|
148
|
+
authorityTier: IntegrationAuthorityTier;
|
|
149
|
+
effective: EffectiveIntegrationState | null;
|
|
150
|
+
candidate: CandidateIntegrationState | null;
|
|
151
|
+
desired: DesiredIntegrationState;
|
|
152
|
+
drift: DesiredEffectiveDrift;
|
|
153
|
+
effectiveAuthority: IntegrationEffectiveAuthority | null;
|
|
154
|
+
assurance: AssuranceReadState | null;
|
|
155
|
+
rollback: RollbackAvailability;
|
|
156
|
+
diagnostics: IntegrationDiagnosticIssue[];
|
|
157
|
+
/** Desired-config key paths withheld because they name secret material — never the values. */
|
|
158
|
+
redactedFields: string[];
|
|
159
|
+
lastActivity: { actor: string; at: string } | null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The full tenant-scoped admin read model for one lifecycle stage. */
|
|
163
|
+
export interface IntegrationAdminReadModel {
|
|
164
|
+
tenantPublicId: string;
|
|
165
|
+
stage: IntegrationLifecycleStage;
|
|
166
|
+
items: IntegrationReadModelItem[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** One install's already-materialized input; every nullable slot is an honest "not yet". */
|
|
170
|
+
export interface IntegrationReadModelInputItem {
|
|
171
|
+
install: {
|
|
172
|
+
installId: string;
|
|
173
|
+
appId: string;
|
|
174
|
+
appVersion: string;
|
|
175
|
+
tenantId: string;
|
|
176
|
+
status: AppInstallStatus;
|
|
177
|
+
};
|
|
178
|
+
/** Plain-language purpose for the inventory row — never an internal adapter name. */
|
|
179
|
+
purpose: string;
|
|
180
|
+
/** The tenant's DESIRED config input (raw; secret-named keys are redacted here). */
|
|
181
|
+
desiredConfig: Record<string, unknown>;
|
|
182
|
+
/** The install's declared authority tier, used until an effective plan resolves one. */
|
|
183
|
+
declaredTier?: IntegrationAuthorityTier;
|
|
184
|
+
/** The EFFECTIVE resolved plan currently serving this tenant/stage, or null if unmaterialized. */
|
|
185
|
+
effectivePlan?: EffectivePlan | null;
|
|
186
|
+
/** Compiled CSP bound to the effective plan (its `planRevision` must match), or null. */
|
|
187
|
+
effectiveCsp?: CompiledIntegrationCsp | null;
|
|
188
|
+
/** The consent snapshot the effective plan filters against, or null. */
|
|
189
|
+
consentSnapshot?: ConsentSnapshot | null;
|
|
190
|
+
/** The elevated-integration assurance record + decision context, when one resolved. */
|
|
191
|
+
assurance?: { record: ComplianceAssuranceRecord; context: AssuranceDecisionContext } | null;
|
|
192
|
+
/** The pending candidate: its evidence + what a mount actually served (for parity), when any. */
|
|
193
|
+
candidate?: { evidence: IntegrationCandidateEvidence; served?: ServedObservation | null } | null;
|
|
194
|
+
/** The recorded known-good live version to roll back to, or null if none ever lived. */
|
|
195
|
+
knownGood?: { version: string; revision: string } | null;
|
|
196
|
+
/** Extra developer diagnostics the control plane already derived. */
|
|
197
|
+
diagnostics?: IntegrationDiagnosticIssue[];
|
|
198
|
+
lastActivity?: { actor: string; at: string } | null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface AssembleReadModelInput {
|
|
202
|
+
/** The auth-resolved tenant scope. Every item MUST belong to it. */
|
|
203
|
+
tenantPublicId: string;
|
|
204
|
+
/** The lifecycle stage this admin view is resolved for. */
|
|
205
|
+
stage: IntegrationLifecycleStage;
|
|
206
|
+
items: readonly IntegrationReadModelInputItem[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Map a runtime execution tier to the admin authority-tier vocabulary. */
|
|
210
|
+
function mapExecutionTier(tier: ExecutionTier): IntegrationAuthorityTier {
|
|
211
|
+
return tier === "sandbox" ? "sandbox-broker" : tier;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Recursively drop secret-named keys, recording their paths — never their values. */
|
|
215
|
+
function redactForbidden(
|
|
216
|
+
value: Record<string, unknown>,
|
|
217
|
+
base = "",
|
|
218
|
+
): { config: Record<string, unknown>; redacted: string[] } {
|
|
219
|
+
const out: Record<string, unknown> = {};
|
|
220
|
+
const redacted: string[] = [];
|
|
221
|
+
for (const [key, child] of Object.entries(value)) {
|
|
222
|
+
const path = base ? `${base}.${key}` : key;
|
|
223
|
+
if (isForbiddenKey(key)) {
|
|
224
|
+
redacted.push(path);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (isPlainRecord(child)) {
|
|
228
|
+
const nested = redactForbidden(child, path);
|
|
229
|
+
out[key] = nested.config;
|
|
230
|
+
redacted.push(...nested.redacted);
|
|
231
|
+
} else {
|
|
232
|
+
out[key] = child;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return { config: out, redacted };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
239
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Sorted, deduped exact origins across all contributable directives. */
|
|
243
|
+
function flattenCspOrigins(additions: IntegrationCspAdditions | undefined): string[] {
|
|
244
|
+
if (!additions) return [];
|
|
245
|
+
const set = new Set<string>();
|
|
246
|
+
for (const directive of INTEGRATION_CSP_DIRECTIVES) {
|
|
247
|
+
for (const origin of additions[directive] ?? []) set.add(origin);
|
|
248
|
+
}
|
|
249
|
+
return [...set].sort();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** The coarse admin-display status — derived, never the raw install status. */
|
|
253
|
+
function deriveDisplayStatus(
|
|
254
|
+
installStatus: AppInstallStatus,
|
|
255
|
+
effective: EffectiveIntegrationState | null,
|
|
256
|
+
candidate: CandidateIntegrationState | null,
|
|
257
|
+
diagnostics: readonly IntegrationDiagnosticIssue[],
|
|
258
|
+
): IntegrationInstallStatus {
|
|
259
|
+
if (installStatus === "suspended" || installStatus === "uninstalled") return "suspended";
|
|
260
|
+
if (diagnostics.some((d) => d.severity === "blocking")) return "error";
|
|
261
|
+
if (effective && effective.stage === "live") return "live";
|
|
262
|
+
if (candidate || effective) return "preview-ready";
|
|
263
|
+
return "configuring";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Assemble the tenant-scoped effective integration read model from already-materialized
|
|
268
|
+
* inputs. Fail-closed: a cross-tenant input, a candidate that claims the live stage, or
|
|
269
|
+
* any secret field surviving into the assembled model yields violations and NO model.
|
|
270
|
+
*/
|
|
271
|
+
export function assembleIntegrationReadModel(
|
|
272
|
+
input: AssembleReadModelInput,
|
|
273
|
+
): ContractResult<IntegrationAdminReadModel> {
|
|
274
|
+
const violations: ContractViolation[] = [];
|
|
275
|
+
const scope = input.tenantPublicId;
|
|
276
|
+
|
|
277
|
+
if (typeof scope !== "string" || scope.length === 0) {
|
|
278
|
+
violations.push({ path: "tenantPublicId", code: "type", message: "tenantPublicId must be a non-empty string." });
|
|
279
|
+
return { ok: false, violations };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const items: IntegrationReadModelItem[] = [];
|
|
283
|
+
|
|
284
|
+
input.items.forEach((raw, index) => {
|
|
285
|
+
const path = `items[${index}]`;
|
|
286
|
+
|
|
287
|
+
// 1. TENANT SCOPE — every materialized input must belong to the resolved scope.
|
|
288
|
+
if (raw.install.tenantId !== scope) {
|
|
289
|
+
violations.push({
|
|
290
|
+
path: `${path}.install.tenantId`,
|
|
291
|
+
code: "cross_tenant",
|
|
292
|
+
message: `Install "${raw.install.installId}" is scoped to "${raw.install.tenantId}", not "${scope}".`,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (raw.effectivePlan && raw.effectivePlan.tenantPublicId !== scope) {
|
|
296
|
+
violations.push({
|
|
297
|
+
path: `${path}.effectivePlan.tenantPublicId`,
|
|
298
|
+
code: "cross_tenant",
|
|
299
|
+
message: `Effective plan is scoped to "${raw.effectivePlan.tenantPublicId}", not "${scope}".`,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (raw.candidate && raw.candidate.evidence.tenantPublicId !== scope) {
|
|
303
|
+
violations.push({
|
|
304
|
+
path: `${path}.candidate.tenantPublicId`,
|
|
305
|
+
code: "cross_tenant",
|
|
306
|
+
message: `Candidate evidence is scoped to "${raw.candidate.evidence.tenantPublicId}", not "${scope}".`,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
if (raw.assurance && raw.assurance.record.tenantPublicId !== scope) {
|
|
310
|
+
violations.push({
|
|
311
|
+
path: `${path}.assurance.tenantPublicId`,
|
|
312
|
+
code: "cross_tenant",
|
|
313
|
+
message: `Assurance record is scoped to "${raw.assurance.record.tenantPublicId}", not "${scope}".`,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const diagnostics: IntegrationDiagnosticIssue[] = [...(raw.diagnostics ?? [])];
|
|
318
|
+
|
|
319
|
+
// 2. SECRETS — redact the raw desired input; keep only the withheld paths.
|
|
320
|
+
const { config: desiredConfig, redacted } = redactForbidden(raw.desiredConfig ?? {});
|
|
321
|
+
const desired: DesiredIntegrationState = { version: raw.install.appVersion, config: desiredConfig };
|
|
322
|
+
|
|
323
|
+
// 4. EFFECTIVE slot — what is serving now (its own stage).
|
|
324
|
+
const plan = raw.effectivePlan ?? null;
|
|
325
|
+
const planIntegration = plan?.integrations.find((i) => i.appId === raw.install.appId) ?? null;
|
|
326
|
+
let effective: EffectiveIntegrationState | null = null;
|
|
327
|
+
let effectiveAuthority: IntegrationEffectiveAuthority | null = null;
|
|
328
|
+
if (plan) {
|
|
329
|
+
if (raw.effectiveCsp && raw.effectiveCsp.planRevision !== plan.planRevision) {
|
|
330
|
+
diagnostics.push({
|
|
331
|
+
severity: "blocking",
|
|
332
|
+
summary: "The compiled CSP does not match the version currently serving.",
|
|
333
|
+
copyableDetail: `Compiled CSP is bound to plan revision "${raw.effectiveCsp.planRevision}" but the effective plan resolved to "${plan.planRevision}".`,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const cspOrigins = flattenCspOrigins(raw.effectiveCsp?.additions);
|
|
337
|
+
const consentEligible = raw.consentSnapshot?.eligiblePurposes ?? [];
|
|
338
|
+
effective = {
|
|
339
|
+
stage: plan.environment.environment,
|
|
340
|
+
planRevision: plan.planRevision,
|
|
341
|
+
version: planIntegration?.version ?? raw.install.appVersion,
|
|
342
|
+
cspOrigins,
|
|
343
|
+
consentEligible: [...consentEligible],
|
|
344
|
+
};
|
|
345
|
+
if (planIntegration) {
|
|
346
|
+
effectiveAuthority = {
|
|
347
|
+
authorityTier: mapExecutionTier(planIntegration.executionTier),
|
|
348
|
+
cspOrigins,
|
|
349
|
+
hasElevatedPiiScope: planIntegration.executionTier === "elevated-parent",
|
|
350
|
+
consentPurposeIds: [...plan.consentNeeds],
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
// A destination that depends on a purpose not yet eligible is a real, surfaced gap.
|
|
354
|
+
if (raw.consentSnapshot) {
|
|
355
|
+
for (const purpose of plan.consentNeeds) {
|
|
356
|
+
if (!raw.consentSnapshot.eligiblePurposes.includes(purpose as ConsentPurpose)) {
|
|
357
|
+
diagnostics.push({
|
|
358
|
+
severity: "warning",
|
|
359
|
+
summary: `This integration depends on "${purpose}" consent, which is not currently granted.`,
|
|
360
|
+
copyableDetail: `consentNeeds includes "${purpose}" but the resolved consent snapshot ${raw.consentSnapshot.snapshotId} does not make it eligible.`,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 4. CANDIDATE slot — the pending candidate, always its own stage, never live.
|
|
368
|
+
let candidate: CandidateIntegrationState | null = null;
|
|
369
|
+
if (raw.candidate) {
|
|
370
|
+
const evidence = raw.candidate.evidence;
|
|
371
|
+
const stage = evidence.environment.environment;
|
|
372
|
+
if (stage === "live") {
|
|
373
|
+
violations.push({
|
|
374
|
+
path: `${path}.candidate.stage`,
|
|
375
|
+
code: "candidate_is_live",
|
|
376
|
+
message: `Candidate evidence reports the live stage; a candidate is never live.`,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const verdict = verifyCandidatePromotable(evidence, raw.candidate.served ?? null);
|
|
380
|
+
const candVersion = evidence.integrations.find((i) => i.appId === raw.install.appId)?.version ?? evidence.planRevision;
|
|
381
|
+
candidate = {
|
|
382
|
+
stage: "candidate",
|
|
383
|
+
planRevision: evidence.planRevision,
|
|
384
|
+
version: candVersion,
|
|
385
|
+
promotable: verdict.ok,
|
|
386
|
+
blockers: verdict.blockers,
|
|
387
|
+
};
|
|
388
|
+
for (const blocker of verdict.blockers) {
|
|
389
|
+
diagnostics.push({
|
|
390
|
+
severity: "warning",
|
|
391
|
+
summary: "This candidate is not yet ready to publish.",
|
|
392
|
+
copyableDetail: blocker,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 3. DESIRED-vs-EFFECTIVE drift, explicit field-by-field.
|
|
398
|
+
const drift = computeDrift(desired, plan, planIntegration, raw.install.appId);
|
|
399
|
+
|
|
400
|
+
// ext-policy-05 assurance decision (composed here, not re-implemented).
|
|
401
|
+
let assurance: AssuranceReadState | null = null;
|
|
402
|
+
if (raw.assurance) {
|
|
403
|
+
const decision = decideAssurance(raw.assurance.record, raw.assurance.context);
|
|
404
|
+
assurance = {
|
|
405
|
+
surface: raw.assurance.record.surface,
|
|
406
|
+
classification: raw.assurance.record.classification,
|
|
407
|
+
approved: decision.ok,
|
|
408
|
+
outsideGuarantee: decision.ok ? decision.outsideGuarantee : false,
|
|
409
|
+
denials: decision.ok ? [] : decision.denials.map((d) => d.reason),
|
|
410
|
+
};
|
|
411
|
+
if (!decision.ok) {
|
|
412
|
+
for (const denial of decision.denials) {
|
|
413
|
+
diagnostics.push({
|
|
414
|
+
severity: "blocking",
|
|
415
|
+
summary: "This integration's compliance assurance is not approved.",
|
|
416
|
+
copyableDetail: denial.subject ? `${denial.reason} (${denial.subject})` : denial.reason,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// 5. ROLLBACK availability — accurate against a real recorded known-good version.
|
|
423
|
+
const knownGood =
|
|
424
|
+
raw.knownGood ??
|
|
425
|
+
(raw.assurance
|
|
426
|
+
? { version: raw.assurance.record.rollback.knownGoodVersion, revision: raw.assurance.record.rollback.knownGoodRevision }
|
|
427
|
+
: null);
|
|
428
|
+
const rollback = deriveRollback(knownGood, effective);
|
|
429
|
+
|
|
430
|
+
if (drift.unmaterialized && Object.keys(desired.config).length > 0) {
|
|
431
|
+
diagnostics.push({
|
|
432
|
+
severity: "warning",
|
|
433
|
+
summary: "The desired configuration has not been published to an effective version yet.",
|
|
434
|
+
copyableDetail: `Desired config for "${raw.install.appId}" has no resolved effective plan; nothing is serving it.`,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const status = deriveDisplayStatus(raw.install.status, effective, candidate, diagnostics);
|
|
439
|
+
|
|
440
|
+
items.push({
|
|
441
|
+
installId: raw.install.installId,
|
|
442
|
+
appId: raw.install.appId,
|
|
443
|
+
purpose: raw.purpose,
|
|
444
|
+
status,
|
|
445
|
+
installStatus: raw.install.status,
|
|
446
|
+
authorityTier: planIntegration ? mapExecutionTier(planIntegration.executionTier) : raw.declaredTier ?? "sandbox-broker",
|
|
447
|
+
effective,
|
|
448
|
+
candidate,
|
|
449
|
+
desired,
|
|
450
|
+
drift,
|
|
451
|
+
effectiveAuthority,
|
|
452
|
+
assurance,
|
|
453
|
+
rollback,
|
|
454
|
+
diagnostics,
|
|
455
|
+
redactedFields: redacted,
|
|
456
|
+
lastActivity: raw.lastActivity ?? null,
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const model: IntegrationAdminReadModel = { tenantPublicId: scope, stage: input.stage, items };
|
|
461
|
+
|
|
462
|
+
// 2. SECRETS — defense in depth: no secret field may survive anywhere in the model.
|
|
463
|
+
for (const violation of findForbiddenFields(model, "readModel")) violations.push(violation);
|
|
464
|
+
|
|
465
|
+
if (violations.length > 0) return { ok: false, violations };
|
|
466
|
+
return { ok: true, value: model };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Explicit desired-vs-effective diff. Unmaterialized when nothing is serving the desired config. */
|
|
470
|
+
function computeDrift(
|
|
471
|
+
desired: DesiredIntegrationState,
|
|
472
|
+
plan: EffectivePlan | null,
|
|
473
|
+
planIntegration: EffectivePlan["integrations"][number] | null,
|
|
474
|
+
appId: string,
|
|
475
|
+
): DesiredEffectiveDrift {
|
|
476
|
+
if (!plan || !planIntegration) {
|
|
477
|
+
return { inSync: false, unmaterialized: true, changes: [] };
|
|
478
|
+
}
|
|
479
|
+
const effectiveConfig = plan.publicConfig[appId] ?? {};
|
|
480
|
+
const changes: DesiredEffectiveChange[] = [];
|
|
481
|
+
|
|
482
|
+
if (desired.version !== planIntegration.version) {
|
|
483
|
+
changes.push({ field: "version", desired: desired.version, effective: planIntegration.version });
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const keys = new Set([...Object.keys(desired.config), ...Object.keys(effectiveConfig)]);
|
|
487
|
+
for (const key of [...keys].sort()) {
|
|
488
|
+
const d = desired.config[key];
|
|
489
|
+
const e = effectiveConfig[key];
|
|
490
|
+
if (JSON.stringify(d) !== JSON.stringify(e)) {
|
|
491
|
+
changes.push({ field: `config.${key}`, desired: d, effective: e });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return { inSync: changes.length === 0, unmaterialized: false, changes };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** Rollback availability is true only for a real recorded known-good version. */
|
|
499
|
+
function deriveRollback(
|
|
500
|
+
knownGood: { version: string; revision: string } | null,
|
|
501
|
+
effective: EffectiveIntegrationState | null,
|
|
502
|
+
): RollbackAvailability {
|
|
503
|
+
if (!knownGood) {
|
|
504
|
+
return {
|
|
505
|
+
available: false,
|
|
506
|
+
targetVersion: null,
|
|
507
|
+
targetRevision: null,
|
|
508
|
+
isCurrent: false,
|
|
509
|
+
reason: "No version has gone live yet; there is no known-good version to roll back to.",
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
const isCurrent = effective != null && effective.version === knownGood.version;
|
|
513
|
+
return {
|
|
514
|
+
available: true,
|
|
515
|
+
targetVersion: knownGood.version,
|
|
516
|
+
targetRevision: knownGood.revision,
|
|
517
|
+
isCurrent,
|
|
518
|
+
reason: isCurrent
|
|
519
|
+
? "The current version is the known-good version; a rollback would change nothing."
|
|
520
|
+
: `A known-good version (${knownGood.version}) is available to roll back to.`,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Project a read-model item onto ext-admin-01's inventory-row view type. The
|
|
526
|
+
* inventory environment is the effective serving stage when one exists, else the
|
|
527
|
+
* admin view's resolved stage.
|
|
528
|
+
*/
|
|
529
|
+
export function projectInventoryItem(
|
|
530
|
+
item: IntegrationReadModelItem,
|
|
531
|
+
stage: IntegrationLifecycleStage,
|
|
532
|
+
): IntegrationInventoryItem {
|
|
533
|
+
return {
|
|
534
|
+
installId: item.installId,
|
|
535
|
+
appId: item.appId,
|
|
536
|
+
purpose: item.purpose,
|
|
537
|
+
status: item.status,
|
|
538
|
+
environment: item.effective?.stage ?? stage,
|
|
539
|
+
authorityTier: item.authorityTier,
|
|
540
|
+
lastActivity: item.lastActivity,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Project a read-model item onto ext-admin-01's detail view — only once an effective
|
|
546
|
+
* authority has resolved (a still-configuring install has none). The tenant owns every
|
|
547
|
+
* desired-config field it supplied.
|
|
548
|
+
*/
|
|
549
|
+
export function projectDetailView(
|
|
550
|
+
item: IntegrationReadModelItem,
|
|
551
|
+
stage: IntegrationLifecycleStage,
|
|
552
|
+
): IntegrationDetailView | null {
|
|
553
|
+
if (!item.effectiveAuthority) return null;
|
|
554
|
+
const fieldOwners: Record<string, IntegrationOwner> = {};
|
|
555
|
+
for (const key of Object.keys(item.desired.config)) fieldOwners[key] = "tenant";
|
|
556
|
+
return {
|
|
557
|
+
...projectInventoryItem(item, stage),
|
|
558
|
+
fieldOwners,
|
|
559
|
+
effectiveAuthority: item.effectiveAuthority,
|
|
560
|
+
diagnostics: item.diagnostics,
|
|
561
|
+
rollbackAvailable: item.rollback.available,
|
|
562
|
+
};
|
|
563
|
+
}
|