@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,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds for the GTM container-export parser.
|
|
3
|
+
*
|
|
4
|
+
* A GTM export is untrusted, possibly-adversarial DATA. The parser treats it as
|
|
5
|
+
* inert bytes and NEVER executes any of it. Bounds are enforced BEFORE parse (a
|
|
6
|
+
* raw-byte cap, checked ahead of `JSON.parse`) and DURING a post-parse structural
|
|
7
|
+
* tree-walk, so a malicious/huge export is rejected safely instead of exhausting
|
|
8
|
+
* the worker.
|
|
9
|
+
*
|
|
10
|
+
* Depth, node-count, and array-length breaches are HARD violations (the export is
|
|
11
|
+
* rejected). Over-length strings are NOT a hard reject — they are truncated with a
|
|
12
|
+
* recorded flag at extraction time (see parse.ts); enforcing that here would fight
|
|
13
|
+
* the contract, so this walk does not fail on string length.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** A structural violation with a JSON path to the offending value. */
|
|
17
|
+
export interface GtmViolation {
|
|
18
|
+
/** JSON path to the offending value, e.g. `containerVersion.tag[3]`. */
|
|
19
|
+
path: string;
|
|
20
|
+
/** Stable machine code, e.g. `max_depth`, `max_nodes`, `max_array_len`. */
|
|
21
|
+
code: string;
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Strict, tunable caps applied before and during a parse. */
|
|
26
|
+
export interface ParseBounds {
|
|
27
|
+
/** Raw-input byte cap, checked BEFORE `JSON.parse`. */
|
|
28
|
+
maxBytes: number;
|
|
29
|
+
/** Maximum nesting depth of the parsed tree (root = depth 0). */
|
|
30
|
+
maxDepth: number;
|
|
31
|
+
/** Maximum total number of nodes (objects, arrays, primitives) visited. */
|
|
32
|
+
maxNodes: number;
|
|
33
|
+
/** Maximum length of any single array. */
|
|
34
|
+
maxArrayLen: number;
|
|
35
|
+
/** Maximum length of any single string; over-length strings are TRUNCATED, not rejected. */
|
|
36
|
+
maxStringLen: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const DEFAULT_BOUNDS: ParseBounds = {
|
|
40
|
+
maxBytes: 8 * 1024 * 1024, // 8 MiB
|
|
41
|
+
maxDepth: 64,
|
|
42
|
+
maxNodes: 200_000,
|
|
43
|
+
maxArrayLen: 50_000,
|
|
44
|
+
maxStringLen: 256 * 1024, // 256 KiB
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Merge a partial override over the defaults, ignoring non-finite/negative caps. */
|
|
48
|
+
export function resolveBounds(partial?: Partial<ParseBounds>): ParseBounds {
|
|
49
|
+
const out: ParseBounds = { ...DEFAULT_BOUNDS };
|
|
50
|
+
if (partial) {
|
|
51
|
+
for (const key of Object.keys(DEFAULT_BOUNDS) as (keyof ParseBounds)[]) {
|
|
52
|
+
const v = partial[key];
|
|
53
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) out[key] = v;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function joinPath(path: string, key: string): string {
|
|
60
|
+
return path ? `${path}.${key}` : key;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Internal cap so the violations list itself cannot grow unboundedly on adversarial input. */
|
|
64
|
+
const MAX_VIOLATIONS = 1000;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Walk the parsed tree iteratively (explicit stack — never recursion, so an
|
|
68
|
+
* adversarially deep tree cannot overflow the JS call stack) and report each
|
|
69
|
+
* structural breach. Returns HARD violations only; an empty array means the tree
|
|
70
|
+
* is within structural bounds. String length is handled by truncation at
|
|
71
|
+
* extraction, not here.
|
|
72
|
+
*/
|
|
73
|
+
export function enforceBounds(root: unknown, bounds: ParseBounds): GtmViolation[] {
|
|
74
|
+
const violations: GtmViolation[] = [];
|
|
75
|
+
const add = (path: string, code: string, message: string): void => {
|
|
76
|
+
if (violations.length < MAX_VIOLATIONS) violations.push({ path, code, message });
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
let nodes = 0;
|
|
80
|
+
let depthReported = false;
|
|
81
|
+
let nodesReported = false;
|
|
82
|
+
|
|
83
|
+
// Stack of { value, path, depth }. Root is depth 0.
|
|
84
|
+
const stack: { value: unknown; path: string; depth: number }[] = [
|
|
85
|
+
{ value: root, path: "", depth: 0 },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
while (stack.length > 0) {
|
|
89
|
+
const frame = stack.pop()!;
|
|
90
|
+
const { value, path, depth } = frame;
|
|
91
|
+
|
|
92
|
+
nodes += 1;
|
|
93
|
+
if (nodes > bounds.maxNodes) {
|
|
94
|
+
if (!nodesReported) {
|
|
95
|
+
add(
|
|
96
|
+
path || "(root)",
|
|
97
|
+
"max_nodes",
|
|
98
|
+
`Node count exceeded ${bounds.maxNodes}; input rejected.`,
|
|
99
|
+
);
|
|
100
|
+
nodesReported = true;
|
|
101
|
+
}
|
|
102
|
+
// Stop the entire walk: continuing is pointless and costly.
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (depth > bounds.maxDepth) {
|
|
107
|
+
if (!depthReported) {
|
|
108
|
+
add(path || "(root)", "max_depth", `Depth exceeded ${bounds.maxDepth}; input rejected.`);
|
|
109
|
+
depthReported = true;
|
|
110
|
+
}
|
|
111
|
+
// Do not descend past the depth cap on this branch.
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (Array.isArray(value)) {
|
|
116
|
+
if (value.length > bounds.maxArrayLen) {
|
|
117
|
+
add(
|
|
118
|
+
path || "(root)",
|
|
119
|
+
"max_array_len",
|
|
120
|
+
`Array length ${value.length} exceeded ${bounds.maxArrayLen}; input rejected.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const limit = Math.min(value.length, bounds.maxArrayLen);
|
|
124
|
+
for (let i = 0; i < limit; i += 1) {
|
|
125
|
+
stack.push({ value: value[i], path: `${path}[${i}]`, depth: depth + 1 });
|
|
126
|
+
}
|
|
127
|
+
} else if (value !== null && typeof value === "object") {
|
|
128
|
+
// Plain object (null-prototype or otherwise). Enumerate own keys.
|
|
129
|
+
const obj = value as Record<string, unknown>;
|
|
130
|
+
for (const key of Object.keys(obj)) {
|
|
131
|
+
stack.push({ value: obj[key], path: joinPath(path, key), depth: depth + 1 });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Primitives (string/number/boolean/null) are leaf nodes — counted, not descended.
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return violations;
|
|
138
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GTM-type → migration-disposition ruleset: the data-driven knowledge that
|
|
3
|
+
* turns an inert GTM object into one of five disposition classes. Kept as tables
|
|
4
|
+
* + small predicates (not a wall of `if`s) so the classification contract is
|
|
5
|
+
* auditable and extensible in one place.
|
|
6
|
+
*
|
|
7
|
+
* PRODUCT BOUNDARY (drives the taxonomy): the storefront supports migration FROM
|
|
8
|
+
* GTM, never EXECUTION of GTM. There is no GTM runtime, no Custom HTML/JS, no
|
|
9
|
+
* custom-template/sandboxed-JS execution, no dataLayer runtime. Any GTM object
|
|
10
|
+
* that is fundamentally code to be executed is unsupported BY DESIGN — a
|
|
11
|
+
* deliberate boundary, not a defect.
|
|
12
|
+
*
|
|
13
|
+
* SECURITY CONTRACT (inherited — a reviewer WILL reject violations):
|
|
14
|
+
* - Pure DATA transformation. Nothing here executes, evaluates, interprets, or
|
|
15
|
+
* compiles any part of the export. Code-bearing fields are read as inert text
|
|
16
|
+
* for their PRESENCE/type only, never run.
|
|
17
|
+
* - Pure and deterministic: no I/O, no globals, stable output for stable input.
|
|
18
|
+
* - Null-prototype safety: every lookup keyed by an attacker-controlled
|
|
19
|
+
* id/name/type goes through a `Map`/`Set`, never an object literal.
|
|
20
|
+
*
|
|
21
|
+
* PRECEDENCE (highest wins; tested):
|
|
22
|
+
* 1. code-bearing → unsupported-by-design (code-execution-unsupported)
|
|
23
|
+
* 2. UA deprecated (tag `ua`)→ retire (ua-deprecated-retire)
|
|
24
|
+
* 3. orphaned/unused var → retire (orphaned-unused)
|
|
25
|
+
* 4. explicit kind/type rule → per the tables below
|
|
26
|
+
* 5. safe default → portable-with-review (unmapped-type-review)
|
|
27
|
+
* Dangling references are always `retire` (dangling-reference) — see dispositions.ts.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { GraphNode } from "./graph.js";
|
|
31
|
+
import { isCustomTemplateType } from "./references.js";
|
|
32
|
+
import type { DispositionClass } from "./dispositions.js";
|
|
33
|
+
|
|
34
|
+
/** Every stable machine reason code the engine can emit, each mapped to one class. */
|
|
35
|
+
export type ReasonCode =
|
|
36
|
+
| "code-execution-unsupported"
|
|
37
|
+
| "no-native-trigger-semantics"
|
|
38
|
+
| "ga4-native"
|
|
39
|
+
| "native-equivalent"
|
|
40
|
+
| "ua-deprecated-retire"
|
|
41
|
+
| "dangling-reference"
|
|
42
|
+
| "orphaned-unused"
|
|
43
|
+
| "google-ads-review"
|
|
44
|
+
| "await-managed-support"
|
|
45
|
+
| "native-mapping-review"
|
|
46
|
+
| "unmapped-type-review"
|
|
47
|
+
| "heatmap-excluded-protected-surfaces"
|
|
48
|
+
| "no-direct-mapping";
|
|
49
|
+
|
|
50
|
+
/** One classification outcome: the class, the stable code, and a human explanation. */
|
|
51
|
+
export interface DispositionVerdict {
|
|
52
|
+
readonly disposition: DispositionClass;
|
|
53
|
+
readonly reasonCode: ReasonCode;
|
|
54
|
+
readonly explanation: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Single source of truth: reason code → its disposition class + base explanation.
|
|
59
|
+
* A finite-key record, so lookups are total (never `undefined`).
|
|
60
|
+
*/
|
|
61
|
+
export const REASON_CATALOG: Record<ReasonCode, { disposition: DispositionClass; explanation: string }> = {
|
|
62
|
+
"code-execution-unsupported": {
|
|
63
|
+
disposition: "unsupported-by-design",
|
|
64
|
+
explanation:
|
|
65
|
+
"Token of Trust never executes GTM code; rebuild the intent as a native integration.",
|
|
66
|
+
},
|
|
67
|
+
"no-native-trigger-semantics": {
|
|
68
|
+
disposition: "unsupported-by-design",
|
|
69
|
+
explanation:
|
|
70
|
+
"This trigger depends on client-side script with no native named equivalent Token of Trust can honor; rebuild the intent natively.",
|
|
71
|
+
},
|
|
72
|
+
"ga4-native": {
|
|
73
|
+
disposition: "automatically-portable",
|
|
74
|
+
explanation: "Maps 1:1 to native GA4; moves automatically with no code.",
|
|
75
|
+
},
|
|
76
|
+
"native-equivalent": {
|
|
77
|
+
disposition: "automatically-portable",
|
|
78
|
+
explanation: "Maps 1:1 to a native concept with no code or ambiguity; moves automatically.",
|
|
79
|
+
},
|
|
80
|
+
"ua-deprecated-retire": {
|
|
81
|
+
disposition: "retire",
|
|
82
|
+
explanation: "Universal Analytics is deprecated by Google with no migration target; retire it.",
|
|
83
|
+
},
|
|
84
|
+
"dangling-reference": {
|
|
85
|
+
disposition: "retire",
|
|
86
|
+
explanation: "References an object that does not exist in the container; retire the broken reference.",
|
|
87
|
+
},
|
|
88
|
+
"orphaned-unused": {
|
|
89
|
+
disposition: "retire",
|
|
90
|
+
explanation:
|
|
91
|
+
"Referenced by nothing and referencing nothing, so it carries no standalone value; retire it.",
|
|
92
|
+
},
|
|
93
|
+
"google-ads-review": {
|
|
94
|
+
disposition: "portable-with-review",
|
|
95
|
+
explanation:
|
|
96
|
+
"Recognized Google Ads tag with a native adapter; confirm the conversion configuration before migrating.",
|
|
97
|
+
},
|
|
98
|
+
"await-managed-support": {
|
|
99
|
+
disposition: "portable-with-review",
|
|
100
|
+
explanation:
|
|
101
|
+
"Recognized third-party vendor whose native adapter is demand-gated; confirm managed support before migrating.",
|
|
102
|
+
},
|
|
103
|
+
"native-mapping-review": {
|
|
104
|
+
disposition: "portable-with-review",
|
|
105
|
+
explanation: "Recognized concept that maps to native but needs human confirmation of the mapping.",
|
|
106
|
+
},
|
|
107
|
+
"unmapped-type-review": {
|
|
108
|
+
disposition: "portable-with-review",
|
|
109
|
+
explanation:
|
|
110
|
+
"Unrecognized type with no known native mapping; review manually — never assumed automatically portable.",
|
|
111
|
+
},
|
|
112
|
+
"heatmap-excluded-protected-surfaces": {
|
|
113
|
+
disposition: "requires-redesign",
|
|
114
|
+
explanation:
|
|
115
|
+
"Heatmap/session-recording vendor excluded from protected and checkout surfaces by policy; redesign around that boundary.",
|
|
116
|
+
},
|
|
117
|
+
"no-direct-mapping": {
|
|
118
|
+
disposition: "requires-redesign",
|
|
119
|
+
explanation: "Recognized concept with no direct native mapping; achievable through a different native design.",
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** The class a reason code implies (derived from the catalog). */
|
|
124
|
+
export function dispositionForReason(code: ReasonCode): DispositionClass {
|
|
125
|
+
return REASON_CATALOG[code].disposition;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Build a verdict from a reason code, optionally appending a per-object detail. */
|
|
129
|
+
export function verdict(code: ReasonCode, detail?: string): DispositionVerdict {
|
|
130
|
+
const entry = REASON_CATALOG[code];
|
|
131
|
+
const explanation = detail ? `${entry.explanation} (${detail})` : entry.explanation;
|
|
132
|
+
return { disposition: entry.disposition, reasonCode: code, explanation };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- Type tables (data-driven; the extension seam for new GTM types) ----------
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Recognized tag `type` → reason code. Absent types fall to the safe default
|
|
139
|
+
* (`unmapped-type-review`). `html` is intentionally NOT here — it is code-bearing
|
|
140
|
+
* and caught by the higher-priority code-execution rule. `ua` is handled ahead of
|
|
141
|
+
* this table (it retires, and must beat any vendor rule).
|
|
142
|
+
*/
|
|
143
|
+
export const TAG_TYPE_RULES = new Map<string, ReasonCode>([
|
|
144
|
+
// Google Analytics 4 / Google tag — native.
|
|
145
|
+
["googtag", "ga4-native"],
|
|
146
|
+
["gaawc", "ga4-native"], // GA4 configuration
|
|
147
|
+
["gaawe", "ga4-native"], // GA4 event
|
|
148
|
+
// Google Ads — recognized, needs conversion-config review.
|
|
149
|
+
["awct", "google-ads-review"], // Conversion Tracking
|
|
150
|
+
["gclidw", "google-ads-review"], // Conversion Linker
|
|
151
|
+
// Managed-pending third-party vendors — native adapter is demand/roadmap-gated.
|
|
152
|
+
["bzi", "await-managed-support"], // LinkedIn Insight
|
|
153
|
+
["hubspot", "await-managed-support"],
|
|
154
|
+
["capterra", "await-managed-support"],
|
|
155
|
+
["g2", "await-managed-support"],
|
|
156
|
+
// Heatmap / session-recording vendors — excluded from protected/checkout surfaces.
|
|
157
|
+
["hjtc", "heatmap-excluded-protected-surfaces"], // Hotjar
|
|
158
|
+
["clarity", "heatmap-excluded-protected-surfaces"], // Microsoft Clarity
|
|
159
|
+
["fullstory", "heatmap-excluded-protected-surfaces"],
|
|
160
|
+
["mouseflow", "heatmap-excluded-protected-surfaces"],
|
|
161
|
+
["mf", "heatmap-excluded-protected-surfaces"], // Mouseflow (GTM gallery-template type code)
|
|
162
|
+
["smartlook", "heatmap-excluded-protected-surfaces"],
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Recognized variable `type` → reason code. Absent types fall to the safe default.
|
|
167
|
+
* `jsm` (Custom JavaScript) is code-bearing and caught earlier, not listed here.
|
|
168
|
+
*/
|
|
169
|
+
export const VARIABLE_TYPE_RULES = new Map<string, ReasonCode>([
|
|
170
|
+
["c", "native-equivalent"], // constant
|
|
171
|
+
["u", "native-equivalent"], // URL
|
|
172
|
+
["v", "native-mapping-review"], // data layer variable
|
|
173
|
+
["j", "native-mapping-review"], // JavaScript global variable
|
|
174
|
+
["k", "native-mapping-review"], // first-party cookie
|
|
175
|
+
["smm", "native-mapping-review"], // lookup table
|
|
176
|
+
["remm", "native-mapping-review"], // regex table lookup
|
|
177
|
+
["d", "native-mapping-review"], // DOM element
|
|
178
|
+
["aev", "native-mapping-review"], // auto-event variable
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Recognized trigger `type` (normalized: lowercased, non-alphanumerics stripped,
|
|
183
|
+
* so `DOM_READY` and `domReady` collapse to one key) → reason code. Absent types
|
|
184
|
+
* fall to the safe default.
|
|
185
|
+
*/
|
|
186
|
+
export const TRIGGER_TYPE_RULES = new Map<string, ReasonCode>([
|
|
187
|
+
// Native named page-lifecycle / interaction semantics — portable.
|
|
188
|
+
["pageview", "native-equivalent"],
|
|
189
|
+
["domready", "native-equivalent"],
|
|
190
|
+
["windowloaded", "native-equivalent"],
|
|
191
|
+
["click", "native-equivalent"],
|
|
192
|
+
["linkclick", "native-equivalent"],
|
|
193
|
+
// Native exists but needs mapping confirmation.
|
|
194
|
+
["formsubmission", "native-mapping-review"],
|
|
195
|
+
["historychange", "native-mapping-review"],
|
|
196
|
+
["elementvisibility", "native-mapping-review"],
|
|
197
|
+
["customevent", "native-mapping-review"],
|
|
198
|
+
["init", "native-mapping-review"],
|
|
199
|
+
["consentinit", "native-mapping-review"],
|
|
200
|
+
// No native named equivalent — depends on client-side script we do not run.
|
|
201
|
+
["scrolldepth", "no-native-trigger-semantics"],
|
|
202
|
+
["youtubevideo", "no-native-trigger-semantics"],
|
|
203
|
+
["timer", "no-native-trigger-semantics"],
|
|
204
|
+
["jserror", "no-native-trigger-semantics"],
|
|
205
|
+
// Recognized concept, no direct mapping — redesign.
|
|
206
|
+
["triggergroup", "no-direct-mapping"],
|
|
207
|
+
["serverpixel", "no-direct-mapping"],
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
/** GTM object kinds with a fixed kind-level disposition (independent of `type`). */
|
|
211
|
+
export const KIND_RULES = new Map<GraphNode["kind"], ReasonCode>([
|
|
212
|
+
["folder", "native-equivalent"], // pure organization
|
|
213
|
+
["zone", "no-direct-mapping"],
|
|
214
|
+
["client", "no-direct-mapping"],
|
|
215
|
+
["transformation", "no-direct-mapping"],
|
|
216
|
+
["gtagConfig", "no-direct-mapping"],
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Built-in variable `type` prefixes that depend on client-side interaction capture
|
|
221
|
+
* (clicks, forms, scroll, video, element visibility, JS errors, history). These map
|
|
222
|
+
* to native but need review; all other built-ins are straight native equivalents.
|
|
223
|
+
*/
|
|
224
|
+
const INTERACTION_BUILTIN_PREFIXES: readonly string[] = [
|
|
225
|
+
"CLICK",
|
|
226
|
+
"FORM",
|
|
227
|
+
"SCROLL",
|
|
228
|
+
"VIDEO",
|
|
229
|
+
"ELEMENT",
|
|
230
|
+
"ERROR",
|
|
231
|
+
"HISTORY",
|
|
232
|
+
"NEW_HISTORY",
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
/** Normalize a tag/variable `type` for exact matching: lowercase, trimmed. */
|
|
236
|
+
function normalizeExact(type: string | null): string {
|
|
237
|
+
return typeof type === "string" ? type.trim().toLowerCase() : "";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Normalize a trigger `type`: lowercase + strip non-alphanumerics (camelCase ≈ UPPER_SNAKE). */
|
|
241
|
+
function normalizeTrigger(type: string | null): string {
|
|
242
|
+
return typeof type === "string" ? type.toLowerCase().replace(/[^a-z0-9]/g, "") : "";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Read a null-prototype-safe own value off a `raw` bag (missing → undefined). */
|
|
246
|
+
function own(raw: Record<string, unknown>, key: string): unknown {
|
|
247
|
+
return Object.prototype.hasOwnProperty.call(raw, key) ? raw[key] : undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** True when a `raw` field holds a non-empty array (used for tag sequencing detection). */
|
|
251
|
+
function hasNonEmptyArray(raw: Record<string, unknown>, key: string): boolean {
|
|
252
|
+
const v = own(raw, key);
|
|
253
|
+
return Array.isArray(v) && v.length > 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Context the classifier needs beyond the node itself — derived from the graph edges. */
|
|
257
|
+
export interface NodeRuleContext {
|
|
258
|
+
/** The node has an outgoing `usesTemplate` edge (→ code-bearing). */
|
|
259
|
+
readonly usesTemplate: boolean;
|
|
260
|
+
/** The node is touched by at least one real edge (in or out) or makes a reference. */
|
|
261
|
+
readonly connected: boolean;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* True when a node is fundamentally GTM code to be executed — the highest-priority
|
|
266
|
+
* disposition. Reads types/fields for presence only; never executes anything.
|
|
267
|
+
*/
|
|
268
|
+
export function isCodeBearing(node: GraphNode, ctx: NodeRuleContext): boolean {
|
|
269
|
+
if (node.kind === "customTemplate") return true; // a template IS code
|
|
270
|
+
if (isCustomTemplateType(node.type)) return true; // `cvt_*` type resolves to a template
|
|
271
|
+
if (ctx.usesTemplate) return true; // wired to a custom template
|
|
272
|
+
if (node.kind === "tag" && normalizeExact(node.type) === "html") return true; // Custom HTML
|
|
273
|
+
if (node.kind === "variable" && normalizeExact(node.type) === "jsm") return true; // Custom JS
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** A short human detail for an object's explanation. */
|
|
278
|
+
function detailFor(node: GraphNode): string {
|
|
279
|
+
const label = node.name ?? node.id ?? node.key;
|
|
280
|
+
const typePart = node.type ? ` type "${node.type}"` : "";
|
|
281
|
+
return `${node.kind} "${label}"${typePart}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function classifyTag(node: GraphNode, detail: string): DispositionVerdict {
|
|
285
|
+
const type = normalizeExact(node.type);
|
|
286
|
+
// UA is the one tag type that retires rather than reviews — beats every vendor rule.
|
|
287
|
+
if (type === "ua") return verdict("ua-deprecated-retire", detail);
|
|
288
|
+
// Tag sequencing / priority has no direct native mapping — redesign.
|
|
289
|
+
if (hasNonEmptyArray(node.record.raw, "setupTag") || hasNonEmptyArray(node.record.raw, "teardownTag")) {
|
|
290
|
+
return verdict("no-direct-mapping", `${detail}: uses tag sequencing`);
|
|
291
|
+
}
|
|
292
|
+
const rule = TAG_TYPE_RULES.get(type);
|
|
293
|
+
if (rule) return verdict(rule, detail);
|
|
294
|
+
// Unrecognized tag type — safe default, NEVER auto, NEVER silently unsupported.
|
|
295
|
+
return verdict("unmapped-type-review", detail);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function classifyVariable(node: GraphNode, detail: string): DispositionVerdict {
|
|
299
|
+
const rule = VARIABLE_TYPE_RULES.get(normalizeExact(node.type));
|
|
300
|
+
if (rule) return verdict(rule, detail);
|
|
301
|
+
return verdict("unmapped-type-review", detail);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function classifyBuiltInVariable(node: GraphNode, detail: string): DispositionVerdict {
|
|
305
|
+
const upper = typeof node.type === "string" ? node.type.trim().toUpperCase() : "";
|
|
306
|
+
for (const prefix of INTERACTION_BUILTIN_PREFIXES) {
|
|
307
|
+
if (upper.startsWith(prefix)) return verdict("native-mapping-review", detail);
|
|
308
|
+
}
|
|
309
|
+
return verdict("native-equivalent", detail);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function classifyTrigger(node: GraphNode, detail: string): DispositionVerdict {
|
|
313
|
+
const rule = TRIGGER_TYPE_RULES.get(normalizeTrigger(node.type));
|
|
314
|
+
if (rule) return verdict(rule, detail);
|
|
315
|
+
return verdict("unmapped-type-review", detail);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Classify a single graph node into exactly one disposition, applying the full
|
|
320
|
+
* precedence chain. Every node returns a verdict — nothing is left unclassified.
|
|
321
|
+
*/
|
|
322
|
+
export function classifyNode(node: GraphNode, ctx: NodeRuleContext): DispositionVerdict {
|
|
323
|
+
const detail = detailFor(node);
|
|
324
|
+
|
|
325
|
+
// 0. Synthetic GTM built-ins (reserved triggers, `_`-prefixed variables) map to
|
|
326
|
+
// native page/event semantics. A recognized built-in is straight native-portable;
|
|
327
|
+
// an unknown reserved-range id maps to native but needs mapping confirmation.
|
|
328
|
+
if (node.builtIn) {
|
|
329
|
+
if (node.builtIn.of === "variable") return verdict("native-equivalent", detail);
|
|
330
|
+
return node.builtIn.known
|
|
331
|
+
? verdict("native-equivalent", detail)
|
|
332
|
+
: verdict("native-mapping-review", detail);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 1. Code-bearing wins over everything.
|
|
336
|
+
if (isCodeBearing(node, ctx)) return verdict("code-execution-unsupported", detail);
|
|
337
|
+
|
|
338
|
+
switch (node.kind) {
|
|
339
|
+
case "tag":
|
|
340
|
+
return classifyTag(node, detail);
|
|
341
|
+
case "variable":
|
|
342
|
+
// 3. Orphaned/unused variable — retire (beats the type rule).
|
|
343
|
+
if (!ctx.connected) return verdict("orphaned-unused", detail);
|
|
344
|
+
return classifyVariable(node, detail);
|
|
345
|
+
case "builtInVariable":
|
|
346
|
+
if (!ctx.connected) return verdict("orphaned-unused", detail);
|
|
347
|
+
return classifyBuiltInVariable(node, detail);
|
|
348
|
+
case "trigger":
|
|
349
|
+
return classifyTrigger(node, detail);
|
|
350
|
+
case "customTemplate":
|
|
351
|
+
// Defensive: already caught by code-bearing above.
|
|
352
|
+
return verdict("code-execution-unsupported", detail);
|
|
353
|
+
default: {
|
|
354
|
+
// folder / zone / client / transformation / gtagConfig.
|
|
355
|
+
const rule = KIND_RULES.get(node.kind);
|
|
356
|
+
if (rule) return verdict(rule, detail);
|
|
357
|
+
// No kind or type rule matched — safe default.
|
|
358
|
+
return verdict("unmapped-type-review", detail);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The migration DISPOSITION (classification) engine: assign every object in a
|
|
3
|
+
* normalized GTM migration graph — and every dangling reference — exactly one
|
|
4
|
+
* disposition class, with a stable machine reason code and a human explanation.
|
|
5
|
+
*
|
|
6
|
+
* COMPLETE ACCOUNTING is a core requirement: `classifiedObjects` equals the graph
|
|
7
|
+
* node count and `classifiedDangling` equals the dangling count, so nothing is ever
|
|
8
|
+
* left unclassified. The GTM-type → disposition knowledge lives in the extensible
|
|
9
|
+
* ruleset (`disposition-rules.ts`); this module is the pure orchestration that walks
|
|
10
|
+
* the graph and tallies the report.
|
|
11
|
+
*
|
|
12
|
+
* SECURITY CONTRACT (inherited — a reviewer WILL reject violations):
|
|
13
|
+
* - Pure DATA transformation: nothing executes, evaluates, or interprets any part
|
|
14
|
+
* of the export. Pure and deterministic; no I/O, no globals, stable ordering.
|
|
15
|
+
* - Every lookup keyed by an attacker-controlled id/name/type goes through a
|
|
16
|
+
* `Map`/`Set`, never an object literal.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { GtmObjectKind, GraphNode, MigrationGraph } from "./graph.js";
|
|
20
|
+
import { classifyNode, verdict, type NodeRuleContext } from "./disposition-rules.js";
|
|
21
|
+
|
|
22
|
+
/** The five migration disposition classes — every object gets exactly one. */
|
|
23
|
+
export type DispositionClass =
|
|
24
|
+
| "automatically-portable"
|
|
25
|
+
| "portable-with-review"
|
|
26
|
+
| "requires-redesign"
|
|
27
|
+
| "unsupported-by-design"
|
|
28
|
+
| "retire";
|
|
29
|
+
|
|
30
|
+
/** One classified GTM object. */
|
|
31
|
+
export interface ObjectDisposition {
|
|
32
|
+
readonly nodeKey: string;
|
|
33
|
+
readonly kind: GtmObjectKind;
|
|
34
|
+
readonly id: string | null;
|
|
35
|
+
readonly name: string | null;
|
|
36
|
+
readonly type: string | null;
|
|
37
|
+
readonly disposition: DispositionClass;
|
|
38
|
+
readonly reasonCode: string;
|
|
39
|
+
readonly explanation: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** One classified dangling reference (its target object does not exist). */
|
|
43
|
+
export interface DanglingDisposition {
|
|
44
|
+
readonly from: string;
|
|
45
|
+
readonly refKind: string;
|
|
46
|
+
readonly ref: string;
|
|
47
|
+
readonly disposition: DispositionClass;
|
|
48
|
+
readonly reasonCode: string;
|
|
49
|
+
readonly explanation: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The complete disposition report for a container. */
|
|
53
|
+
export interface DispositionReport {
|
|
54
|
+
readonly objects: ObjectDisposition[];
|
|
55
|
+
readonly dangling: DanglingDisposition[];
|
|
56
|
+
readonly summary: {
|
|
57
|
+
/** Count per class across BOTH objects and dangling references. Sums to `total`. */
|
|
58
|
+
readonly byClass: Record<DispositionClass, number>;
|
|
59
|
+
/** Per object kind → per class counts (objects only; dangling has no kind). */
|
|
60
|
+
readonly byKind: Record<GtmObjectKind, Record<DispositionClass, number>>;
|
|
61
|
+
/** Total classified items = objects + dangling references. */
|
|
62
|
+
readonly total: number;
|
|
63
|
+
};
|
|
64
|
+
readonly accounting: {
|
|
65
|
+
readonly classifiedObjects: number;
|
|
66
|
+
readonly classifiedDangling: number;
|
|
67
|
+
readonly graphNodeCount: number;
|
|
68
|
+
readonly graphDanglingCount: number;
|
|
69
|
+
/** classifiedObjects===graphNodeCount && classifiedDangling===graphDanglingCount. */
|
|
70
|
+
readonly complete: boolean;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Fixed class order — drives deterministic zeroed count maps. */
|
|
75
|
+
const ALL_CLASSES: readonly DispositionClass[] = [
|
|
76
|
+
"automatically-portable",
|
|
77
|
+
"portable-with-review",
|
|
78
|
+
"requires-redesign",
|
|
79
|
+
"unsupported-by-design",
|
|
80
|
+
"retire",
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
/** Fixed kind order — drives the deterministic `byKind` map. */
|
|
84
|
+
const ALL_KINDS: readonly GtmObjectKind[] = [
|
|
85
|
+
"tag",
|
|
86
|
+
"trigger",
|
|
87
|
+
"variable",
|
|
88
|
+
"builtInVariable",
|
|
89
|
+
"customTemplate",
|
|
90
|
+
"folder",
|
|
91
|
+
"zone",
|
|
92
|
+
"client",
|
|
93
|
+
"gtagConfig",
|
|
94
|
+
"transformation",
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
function zeroedClassCounts(): Record<DispositionClass, number> {
|
|
98
|
+
const out = {} as Record<DispositionClass, number>;
|
|
99
|
+
for (const cls of ALL_CLASSES) out[cls] = 0;
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Classify every node and dangling reference in a migration graph.
|
|
105
|
+
*
|
|
106
|
+
* Pure and deterministic. Output order mirrors the graph's node and dangling order.
|
|
107
|
+
*/
|
|
108
|
+
export function classifyMigration(graph: MigrationGraph): DispositionReport {
|
|
109
|
+
// --- Context derived from edges: usesTemplate wiring + connectivity. --------
|
|
110
|
+
const usesTemplateFrom = new Set<string>();
|
|
111
|
+
const connected = new Set<string>();
|
|
112
|
+
for (const edge of graph.edges) {
|
|
113
|
+
connected.add(edge.from);
|
|
114
|
+
connected.add(edge.to);
|
|
115
|
+
if (edge.kind === "usesTemplate") usesTemplateFrom.add(edge.from);
|
|
116
|
+
}
|
|
117
|
+
// A node that MAKES a reference (even a dangling one) is "referencing something",
|
|
118
|
+
// so it is not an orphan — conservative: prefer review over retire.
|
|
119
|
+
for (const d of graph.danglingReferences) connected.add(d.from);
|
|
120
|
+
|
|
121
|
+
const contextFor = (node: GraphNode): NodeRuleContext => ({
|
|
122
|
+
usesTemplate: usesTemplateFrom.has(node.key),
|
|
123
|
+
connected: connected.has(node.key),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// --- Objects: one disposition per node, in graph node order. ---------------
|
|
127
|
+
const objects: ObjectDisposition[] = graph.nodes.map((node) => {
|
|
128
|
+
const v = classifyNode(node, contextFor(node));
|
|
129
|
+
return {
|
|
130
|
+
nodeKey: node.key,
|
|
131
|
+
kind: node.kind,
|
|
132
|
+
id: node.id,
|
|
133
|
+
name: node.name,
|
|
134
|
+
type: node.type,
|
|
135
|
+
disposition: v.disposition,
|
|
136
|
+
reasonCode: v.reasonCode,
|
|
137
|
+
explanation: v.explanation,
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// --- Dangling references: always retire, in graph dangling order. ----------
|
|
142
|
+
const dangling: DanglingDisposition[] = graph.danglingReferences.map((d) => {
|
|
143
|
+
const v = verdict("dangling-reference", `${d.kind} reference "${d.ref}" from ${d.from}`);
|
|
144
|
+
return {
|
|
145
|
+
from: d.from,
|
|
146
|
+
refKind: d.kind,
|
|
147
|
+
ref: d.ref,
|
|
148
|
+
disposition: v.disposition,
|
|
149
|
+
reasonCode: v.reasonCode,
|
|
150
|
+
explanation: v.explanation,
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// --- Summary tallies. -------------------------------------------------------
|
|
155
|
+
const byClass = zeroedClassCounts();
|
|
156
|
+
const byKind = {} as Record<GtmObjectKind, Record<DispositionClass, number>>;
|
|
157
|
+
for (const kind of ALL_KINDS) byKind[kind] = zeroedClassCounts();
|
|
158
|
+
|
|
159
|
+
for (const obj of objects) {
|
|
160
|
+
byClass[obj.disposition] += 1;
|
|
161
|
+
byKind[obj.kind][obj.disposition] += 1;
|
|
162
|
+
}
|
|
163
|
+
for (const d of dangling) {
|
|
164
|
+
byClass[d.disposition] += 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const total = objects.length + dangling.length;
|
|
168
|
+
|
|
169
|
+
// --- Accounting invariant. --------------------------------------------------
|
|
170
|
+
const graphNodeCount = graph.nodes.length;
|
|
171
|
+
const graphDanglingCount = graph.danglingReferences.length;
|
|
172
|
+
const accounting = {
|
|
173
|
+
classifiedObjects: objects.length,
|
|
174
|
+
classifiedDangling: dangling.length,
|
|
175
|
+
graphNodeCount,
|
|
176
|
+
graphDanglingCount,
|
|
177
|
+
complete: objects.length === graphNodeCount && dangling.length === graphDanglingCount,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return { objects, dangling, summary: { byClass, byKind, total }, accounting };
|
|
181
|
+
}
|