@tokenoftrust/storefront-runner 1.3.4-rc.4 → 1.4.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 +15 -0
- package/apps/storefront/dev-plugins/dev-publish.mjs +55 -0
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +86 -7
- package/apps/storefront/drizzle.config.apps.ts +13 -0
- package/apps/storefront/env.d.ts +10 -0
- package/apps/storefront/migrations/README.md +13 -7
- package/apps/storefront/migrations-apps/0000_fast_millenium_guard.sql +129 -0
- package/apps/storefront/migrations-apps/meta/0000_snapshot.json +843 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +13 -0
- package/apps/storefront/package.json +13 -1
- package/apps/storefront/perf/README.md +64 -0
- package/apps/storefront/perf/assert-budgets.ts +159 -0
- package/apps/storefront/playwright.config.ts +23 -0
- package/apps/storefront/public/js/dashboard-apps.js +173 -0
- package/apps/storefront/public/shared/commerce-marketing.css +221 -0
- package/apps/storefront/src/components/CollectionCard.astro +1 -0
- package/apps/storefront/src/components/ProductCard.astro +1 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1959 -0
- package/apps/storefront/src/components/apps/AppWidgetFrame.astro +30 -0
- package/apps/storefront/src/components/chrome/NavDropdown.astro +6 -3
- package/apps/storefront/src/components/chrome/SiteFooter.astro +10 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +10 -0
- package/apps/storefront/src/components/commerce/RatingStars.astro +3 -2
- package/apps/storefront/src/components/content/Callout.astro +75 -0
- package/apps/storefront/src/components/content/NeedsReviewCallout.astro +66 -0
- package/apps/storefront/src/components/content/ProseSections.astro +121 -0
- package/apps/storefront/src/components/content/ProseToc.astro +34 -0
- package/apps/storefront/src/components/content/RichText.astro +44 -0
- package/apps/storefront/src/components/content/TrustStrip.astro +46 -0
- package/apps/storefront/src/components/home/Hero.astro +14 -0
- package/apps/storefront/src/components/islands/CheckoutComplianceGate.tsx +295 -0
- package/apps/storefront/src/components/islands/ImageGallery.tsx +42 -21
- package/apps/storefront/src/components/islands/VariantSelector.tsx +62 -12
- package/apps/storefront/src/components/plp/FacetSidebar.astro +2 -2
- package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +1 -0
- package/apps/storefront/src/config/compliance/rulesets.ts +79 -0
- package/apps/storefront/src/config/storeName.ts +34 -0
- package/apps/storefront/src/layouts/Layout.astro +97 -23
- package/apps/storefront/src/lib/analytics/budgets.json +69 -0
- package/apps/storefront/src/lib/analytics/lighthouseReport.ts +109 -0
- package/apps/storefront/src/lib/analytics/perfBudgets.ts +452 -0
- package/apps/storefront/src/lib/analytics/rumAlert.ts +179 -0
- package/apps/storefront/src/lib/analytics/webVitals.ts +269 -0
- package/apps/storefront/src/lib/apps/adminService.ts +106 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +205 -0
- package/apps/storefront/src/lib/apps/apiAuth.ts +91 -0
- package/apps/storefront/src/lib/apps/apiRoute.ts +35 -0
- package/apps/storefront/src/lib/apps/catalogMapper.ts +39 -0
- package/apps/storefront/src/lib/apps/credentials.ts +153 -0
- package/apps/storefront/src/lib/apps/gatewayKeys.ts +156 -0
- package/apps/storefront/src/lib/apps/healthAggregate.ts +66 -0
- package/apps/storefront/src/lib/apps/orders/attributionService.ts +206 -0
- package/apps/storefront/src/lib/apps/orders/customerHash.ts +28 -0
- package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +203 -0
- package/apps/storefront/src/lib/apps/orders/idempotency.ts +100 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +117 -0
- package/apps/storefront/src/lib/apps/orders/orderMapper.ts +91 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +181 -0
- package/apps/storefront/src/lib/apps/registryService.ts +579 -0
- package/apps/storefront/src/lib/apps/scopes.ts +79 -0
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +121 -0
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +148 -0
- package/apps/storefront/src/lib/apps/widgets/eligibility.ts +18 -0
- package/apps/storefront/src/lib/apps/widgets/frameProps.ts +52 -0
- package/apps/storefront/src/lib/apps/widgets/launchToken.ts +84 -0
- package/apps/storefront/src/lib/apps/widgets/placements.ts +57 -0
- package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +111 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +119 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +21 -2
- package/apps/storefront/src/lib/auth/loginGate.ts +102 -18
- package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +215 -0
- package/apps/storefront/src/lib/auth/route.ts +16 -1
- package/apps/storefront/src/lib/auth/session.ts +8 -0
- package/apps/storefront/src/lib/auth/stepUpChallenge.ts +107 -0
- package/apps/storefront/src/lib/auth/totAccessClient.ts +208 -0
- package/apps/storefront/src/lib/blog/provider.ts +39 -0
- package/apps/storefront/src/lib/blog/types.ts +26 -0
- package/apps/storefront/src/lib/checkoutCommerce.ts +39 -1
- package/apps/storefront/src/lib/chrome/model.ts +11 -0
- package/apps/storefront/src/lib/compliance/enforcement.ts +95 -0
- package/apps/storefront/src/lib/content/callout.ts +78 -0
- package/apps/storefront/src/lib/content/index.ts +27 -0
- package/apps/storefront/src/lib/content/needsReview.ts +58 -0
- package/apps/storefront/src/lib/content/prose.ts +186 -0
- package/apps/storefront/src/lib/content/richtext.ts +76 -0
- package/apps/storefront/src/lib/content/trustStrip.ts +74 -0
- package/apps/storefront/src/lib/content-edit/client.ts +70 -14
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +213 -0
- package/apps/storefront/src/lib/dev/apiBase.ts +8 -2
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +65 -115
- package/apps/storefront/src/lib/dev/cockpitStore.ts +65 -0
- package/apps/storefront/src/lib/dev/previewStatus.ts +112 -0
- package/apps/storefront/src/lib/dev/rendezvousBroker.ts +241 -0
- package/apps/storefront/src/lib/dev/subjectReissue.ts +67 -0
- package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +10 -10
- package/apps/storefront/src/lib/env.ts +12 -0
- package/apps/storefront/src/lib/jsonld.ts +12 -17
- package/apps/storefront/src/lib/membership/eligibility.ts +37 -0
- package/apps/storefront/src/lib/monitoring/manifest.ts +302 -0
- package/apps/storefront/src/lib/privacy/emailHint.ts +13 -5
- package/apps/storefront/src/lib/publish/apex-readiness.ts +337 -0
- package/apps/storefront/src/lib/publish/dispatchHealth.ts +269 -0
- package/apps/storefront/src/lib/publish/domainState.ts +351 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +362 -0
- package/apps/storefront/src/lib/rawChrome.ts +34 -3
- package/apps/storefront/src/lib/storyblok/content-model.ts +34 -2
- package/apps/storefront/src/lib/storyblok/provider.ts +11 -4
- package/apps/storefront/src/lib/subscription/model.ts +114 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +3 -3
- package/apps/storefront/src/lib/tot/query.ts +32 -0
- package/apps/storefront/src/lib/webhooks/cloudflareQueueDispatcher.ts +82 -0
- package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +245 -0
- package/apps/storefront/src/lib/webhooks/deliveryMapper.ts +34 -0
- package/apps/storefront/src/lib/webhooks/deliveryStore.ts +668 -0
- package/apps/storefront/src/lib/webhooks/dispatcher.ts +168 -0
- package/apps/storefront/src/lib/webhooks/emit.ts +167 -0
- package/apps/storefront/src/lib/webhooks/endpointGuard.ts +135 -0
- package/apps/storefront/src/lib/webhooks/events.ts +98 -0
- package/apps/storefront/src/lib/webhooks/getDispatcher.ts +49 -0
- package/apps/storefront/src/lib/webhooks/signing.ts +29 -0
- package/apps/storefront/src/lib/webhooks/webhookSigningKey.ts +146 -0
- package/apps/storefront/src/middleware/index.ts +28 -13
- package/apps/storefront/src/pages/404.astro +21 -9
- package/apps/storefront/src/pages/[...slug].astro +36 -2
- package/apps/storefront/src/pages/admin.astro +29 -3
- package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +60 -0
- package/apps/storefront/src/pages/api/apps/admin/health.ts +44 -0
- package/apps/storefront/src/pages/api/apps/admin/install.ts +100 -0
- package/apps/storefront/src/pages/api/apps/admin/list.ts +26 -0
- package/apps/storefront/src/pages/api/apps/admin/resume.ts +56 -0
- package/apps/storefront/src/pages/api/apps/admin/suspend.ts +57 -0
- package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +74 -0
- package/apps/storefront/src/pages/api/apps/admin/update.ts +103 -0
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +59 -0
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +51 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +172 -0
- package/apps/storefront/src/pages/api/apps/oauth/token.ts +87 -0
- package/apps/storefront/src/pages/api/apps/v1/attribution.ts +142 -0
- package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +39 -0
- package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +49 -0
- package/apps/storefront/src/pages/api/apps/v1/health.ts +32 -0
- package/apps/storefront/src/pages/api/apps/v1/inventory.ts +59 -0
- package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +48 -0
- package/apps/storefront/src/pages/api/apps/v1/orders.ts +73 -0
- package/apps/storefront/src/pages/api/apps/v1/reports.ts +21 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +62 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +47 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +48 -1
- package/apps/storefront/src/pages/api/auth/step-up-send.ts +57 -0
- package/apps/storefront/src/pages/api/auth/step-up-verify.ts +129 -0
- package/apps/storefront/src/pages/api/auth/verify.ts +23 -0
- package/apps/storefront/src/pages/api/compliance/preflight.ts +206 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +54 -0
- package/apps/storefront/src/pages/api/test/dev-session.ts +133 -0
- package/apps/storefront/src/pages/auth/login.astro +197 -41
- package/apps/storefront/src/pages/auth/magic.astro +75 -43
- package/apps/storefront/src/pages/blog/[slug].astro +107 -0
- package/apps/storefront/src/pages/blog/index.astro +98 -0
- package/apps/storefront/src/pages/capabilities.astro +8 -0
- package/apps/storefront/src/pages/cockpit.astro +433 -69
- package/apps/storefront/src/pages/collections/[handle].astro +8 -0
- package/apps/storefront/src/pages/collections/index.astro +10 -2
- package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +119 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +5 -0
- package/apps/storefront/src/pages/index.astro +57 -0
- package/apps/storefront/src/pages/llms.txt.ts +31 -10
- package/apps/storefront/src/pages/products/[handle].astro +100 -9
- package/apps/storefront/src/pages/sitemap.xml.ts +21 -4
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +198 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +7 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +7 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +7 -0
- package/apps/storefront/src/pages/style-guide/index.astro +10 -0
- package/apps/storefront/src/styles/fonts.css +54 -0
- package/apps/storefront/src/styles/global.css +22 -2
- package/apps/storefront/src/themes/schema.ts +3 -25
- package/apps/storefront/tsconfig.json +1 -1
- package/apps/storefront/vitest.config.ts +4 -1
- package/package.json +1 -1
- package/packages/public-runtime/src/candidate-index.ts +311 -0
- package/packages/public-runtime/src/checkout.ts +94 -2
- package/packages/public-runtime/src/compliance/evaluate.ts +265 -0
- package/packages/public-runtime/src/compliance/evidence-signals.ts +81 -0
- package/packages/public-runtime/src/compliance/index.ts +22 -0
- package/packages/public-runtime/src/compliance/pact-report.ts +94 -0
- package/packages/public-runtime/src/compliance/profile.ts +198 -0
- package/packages/public-runtime/src/compliance/ruleset.ts +117 -0
- package/packages/public-runtime/src/compliance/verification.ts +81 -0
- package/packages/public-runtime/src/csp.ts +27 -3
- package/packages/public-runtime/src/customization-reconcile.ts +35 -0
- package/packages/public-runtime/src/customization-runtime.ts +8 -0
- package/packages/public-runtime/src/customization-versioning.ts +17 -0
- package/packages/public-runtime/src/extension-contract.ts +2 -1
- package/packages/public-runtime/src/hash.ts +25 -0
- package/packages/public-runtime/src/index.ts +6 -0
- package/packages/public-runtime/src/membership.ts +353 -0
- package/packages/public-runtime/src/product.ts +24 -2
- package/packages/public-runtime/src/review-trust-proof.ts +194 -0
- package/packages/public-runtime/src/tenant-assets.ts +40 -5
- package/packages/public-runtime/src/tenant.ts +236 -0
- package/packages/public-runtime/src/widget-postmessage.ts +205 -0
- package/scripts/dev/publish.mjs +158 -0
- package/scripts/dev/transient-files.mjs +2 -1
- package/tenants/home/public/fonts/inter-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-800-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-800-normal.woff2 +0 -0
- package/tenants/home/public/pages/storefront.css +23 -0
- package/apps/storefront/src/lib/dev/hostedCockpit.ts +0 -169
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compliance RULESET — the data-driven jurisdiction/federal rule model the
|
|
3
|
+
* enforcement engine evaluates against. This is deliberately DATA, not code
|
|
4
|
+
* branches: a tenant's regulated obligations (state flavor/no-ship bans, per-order
|
|
5
|
+
* limits, PACT Act, verification, excise) are expressed as a serializable record
|
|
6
|
+
* so the rules can be reviewed, versioned, and updated without touching the
|
|
7
|
+
* evaluator. The concrete per-tenant data literals live in the app
|
|
8
|
+
* (`apps/storefront/src/config/compliance/rulesets.ts`); this module owns the
|
|
9
|
+
* SHAPE + resolution helpers.
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT — legal posture: this framework CAPTURES and ENFORCES the configured
|
|
12
|
+
* rules and captures the data required for filings. It does not constitute legal
|
|
13
|
+
* advice; the rule DATA and any regulatory FILING (e.g. PACT Act monthly reports)
|
|
14
|
+
* are the merchant's / their counsel's responsibility. Every ruleset carries a
|
|
15
|
+
* `source`/`updatedAt` provenance stamp to make that boundary explicit.
|
|
16
|
+
*
|
|
17
|
+
* Pure + edge-safe (no node, no I/O).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** A single jurisdiction's data-driven rule. `region` is a 2-letter code (uppercased). */
|
|
21
|
+
export interface StateComplianceRule {
|
|
22
|
+
region: string;
|
|
23
|
+
/** No regulated (ENDS/vapor) shipments to this region at all (state no-ship). */
|
|
24
|
+
banAll?: boolean;
|
|
25
|
+
/** Flavored-ENDS ban — tobacco/menthol still allowed, characterizing flavors not. */
|
|
26
|
+
banFlavored?: boolean;
|
|
27
|
+
/** Per-order regulated-unit cap for this region (overrides the ruleset default). */
|
|
28
|
+
perOrderUnitLimit?: number;
|
|
29
|
+
/** Adult signature on delivery required for this region (else the PACT baseline). */
|
|
30
|
+
adultSignatureRequired?: boolean;
|
|
31
|
+
/** Short citation/note surfaced in the block message. */
|
|
32
|
+
note?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Age/identity verification requirement. `provider` is the LIVE-provider contract:
|
|
37
|
+
* it is populated only when a verification provider is actually configured
|
|
38
|
+
* (external/operational config). A required verification with NO provider FAILS
|
|
39
|
+
* CLOSED (the enforcement gate blocks) — a notice that cannot verify is not
|
|
40
|
+
* compliance.
|
|
41
|
+
*/
|
|
42
|
+
export interface VerificationRequirement {
|
|
43
|
+
/** Verification must pass before checkout can complete. */
|
|
44
|
+
required: boolean;
|
|
45
|
+
/** Minimum age enforced (e.g. 21 for nicotine). */
|
|
46
|
+
minAge: number;
|
|
47
|
+
/**
|
|
48
|
+
* Live provider id once wired (e.g. "tot"). Absent ⇒ no provider configured;
|
|
49
|
+
* `resolveVerificationDecision` then blocks (safe default). Set by
|
|
50
|
+
* `resolveTenantRuleset` from operational config, never hardcoded in the data.
|
|
51
|
+
*/
|
|
52
|
+
provider?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Excise-tax obligation + how it is satisfied. Enforcement is fail-closed: an
|
|
57
|
+
* obligation with `handling: "unavailable"` BLOCKS checkout rather than silently
|
|
58
|
+
* shipping untaxed.
|
|
59
|
+
*/
|
|
60
|
+
export interface ExciseRequirement {
|
|
61
|
+
required: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* - "managed-cart" — materialized in the managed cart via the pre-cart webhook +
|
|
64
|
+
* `tot_excise_tax` coupon (Track B).
|
|
65
|
+
* - "provider" — external excise calc/handoff (contract in `note`).
|
|
66
|
+
* - "unavailable" — no excise path wired; enforcement fails closed.
|
|
67
|
+
*/
|
|
68
|
+
handling: "managed-cart" | "provider" | "unavailable";
|
|
69
|
+
/** External excise-provider handoff contract note. */
|
|
70
|
+
note?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** PACT Act (ENDS shipments) rule. */
|
|
74
|
+
export interface PactActRule {
|
|
75
|
+
enabled: boolean;
|
|
76
|
+
/** Adult signature (21+) on delivery — PACT baseline for ENDS. */
|
|
77
|
+
adultSignatureRequired: boolean;
|
|
78
|
+
/** Carriers that must NOT carry ENDS (PACT: USPS won't). Documents the carrier contract. */
|
|
79
|
+
prohibitedCarriers: string[];
|
|
80
|
+
/** Per-order data must be captured for the PACT Act monthly state report. */
|
|
81
|
+
reportingRequired: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ComplianceRuleset {
|
|
85
|
+
tenantId: string;
|
|
86
|
+
minAge: number;
|
|
87
|
+
verification: VerificationRequirement;
|
|
88
|
+
excise: ExciseRequirement;
|
|
89
|
+
pactAct: PactActRule;
|
|
90
|
+
/** Fallback per-order regulated-unit cap when no state rule sets one. */
|
|
91
|
+
defaultPerOrderUnitLimit?: number;
|
|
92
|
+
/** The substance of jurisdiction enforcement — data-driven, not a static list in code. */
|
|
93
|
+
states: StateComplianceRule[];
|
|
94
|
+
/** ISO date the rule DATA was last reviewed (provenance). */
|
|
95
|
+
updatedAt: string;
|
|
96
|
+
/** Provenance / disclaimer note (who owns the data + filing responsibility). */
|
|
97
|
+
source: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Find the rule for a region (case-insensitive), or `undefined`. */
|
|
101
|
+
export function stateRuleFor(
|
|
102
|
+
ruleset: Pick<ComplianceRuleset, "states">,
|
|
103
|
+
region: string | undefined | null,
|
|
104
|
+
): StateComplianceRule | undefined {
|
|
105
|
+
if (!region) return undefined;
|
|
106
|
+
const key = region.trim().toUpperCase();
|
|
107
|
+
return ruleset.states.find((s) => s.region.toUpperCase() === key);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The effective per-order unit limit for a region (state rule wins over the default). */
|
|
111
|
+
export function effectivePerOrderLimit(
|
|
112
|
+
ruleset: Pick<ComplianceRuleset, "states" | "defaultPerOrderUnitLimit">,
|
|
113
|
+
region: string | undefined | null,
|
|
114
|
+
): number | undefined {
|
|
115
|
+
const stateLimit = stateRuleFor(ruleset, region)?.perOrderUnitLimit;
|
|
116
|
+
return stateLimit ?? ruleset.defaultPerOrderUnitLimit;
|
|
117
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Age/identity VERIFICATION gate — the enforcement decision on the verification
|
|
3
|
+
* axis. The soft 21+ affirmation (the front-door `AgeGate` island) is UX only and
|
|
4
|
+
* is NEVER accepted here as verification; this gate resolves the REAL requirement.
|
|
5
|
+
*
|
|
6
|
+
* The live provider is external/operational config (see
|
|
7
|
+
* `VerificationRequirement.provider`). This module's contract:
|
|
8
|
+
* - not required → pass ("not_required")
|
|
9
|
+
* - required, no provider → BLOCK, fail-closed (safe default)
|
|
10
|
+
* - required, unknown/pending state → "required" (do the step; not yet cleared)
|
|
11
|
+
* - required, failed → BLOCK
|
|
12
|
+
* - required, verified but under minAge → BLOCK
|
|
13
|
+
* - required, verified ≥ minAge → pass ("satisfied")
|
|
14
|
+
*
|
|
15
|
+
* Pure + edge-safe.
|
|
16
|
+
*/
|
|
17
|
+
import type { VerificationRequirement } from "./ruleset.js";
|
|
18
|
+
|
|
19
|
+
export type VerificationOutcome = "verified" | "failed" | "pending" | "unknown";
|
|
20
|
+
|
|
21
|
+
/** The provider-reported (or absent) verification result for this shopper/session. */
|
|
22
|
+
export interface VerificationState {
|
|
23
|
+
outcome: VerificationOutcome;
|
|
24
|
+
/** Verified age when the provider returns one. */
|
|
25
|
+
verifiedAge?: number;
|
|
26
|
+
/** Opaque provider evidence reference (feeds the evidence timeline). */
|
|
27
|
+
evidenceRef?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface VerificationDecision {
|
|
31
|
+
status: "satisfied" | "required" | "blocked" | "not_required";
|
|
32
|
+
reason?: string;
|
|
33
|
+
/**
|
|
34
|
+
* True when the block is a fail-closed SAFETY default (no provider configured),
|
|
35
|
+
* not a real verification failure — surfaced distinctly so operators can see
|
|
36
|
+
* "verification isn't wired" vs "the shopper failed verification".
|
|
37
|
+
*/
|
|
38
|
+
failedClosed: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function resolveVerificationDecision(
|
|
42
|
+
requirement: VerificationRequirement,
|
|
43
|
+
state: VerificationState | null | undefined,
|
|
44
|
+
): VerificationDecision {
|
|
45
|
+
if (!requirement.required) {
|
|
46
|
+
return { status: "not_required", failedClosed: false };
|
|
47
|
+
}
|
|
48
|
+
// Required but no live provider configured → FAIL CLOSED. Never silently allow.
|
|
49
|
+
if (!requirement.provider) {
|
|
50
|
+
return {
|
|
51
|
+
status: "blocked",
|
|
52
|
+
failedClosed: true,
|
|
53
|
+
reason:
|
|
54
|
+
"Age/identity verification is required but no verification provider is configured. " +
|
|
55
|
+
"Checkout is blocked until a provider is wired (safe default = BLOCK).",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!state || state.outcome === "unknown" || state.outcome === "pending") {
|
|
59
|
+
return {
|
|
60
|
+
status: "required",
|
|
61
|
+
failedClosed: false,
|
|
62
|
+
reason: "Age/identity verification must be completed before checkout.",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (state.outcome === "failed") {
|
|
66
|
+
return {
|
|
67
|
+
status: "blocked",
|
|
68
|
+
failedClosed: false,
|
|
69
|
+
reason: "Age/identity verification did not pass.",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// verified
|
|
73
|
+
if (state.verifiedAge != null && state.verifiedAge < requirement.minAge) {
|
|
74
|
+
return {
|
|
75
|
+
status: "blocked",
|
|
76
|
+
failedClosed: false,
|
|
77
|
+
reason: `Verified age ${state.verifiedAge} is below the required minimum of ${requirement.minAge}.`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { status: "satisfied", failedClosed: false };
|
|
81
|
+
}
|
|
@@ -46,7 +46,11 @@ export function buildCsp(opts: CspOptions): string {
|
|
|
46
46
|
// JS, so this is the one tolerated relaxation. style-src-elem still inherits
|
|
47
47
|
// the strict 'self'+nonce from style-src; script-src stays fully strict.
|
|
48
48
|
"style-src-attr": ["'unsafe-inline'"],
|
|
49
|
-
|
|
49
|
+
// 'self' covers same-origin @fontsource files; `data:` covers the small ones
|
|
50
|
+
// Vite inlines as base64 `data:font/…` URIs at build time (assets under
|
|
51
|
+
// assetsInlineLimit get baked into the bundled CSS). Fonts can't execute JS,
|
|
52
|
+
// so allowing `data:` here is the same tolerated relaxation as img-src.
|
|
53
|
+
"font-src": ["'self'", "data:", ...(opts.fontSrc ?? [])],
|
|
50
54
|
"img-src": ["'self'", "data:", ...(opts.imgSrc ?? [])],
|
|
51
55
|
"connect-src": ["'self'", ...(opts.connectSrc ?? [])],
|
|
52
56
|
"frame-src": ["'self'", ...(opts.frameSrc ?? [])],
|
|
@@ -70,8 +74,12 @@ export const CSP_REPORT_GROUP = "csp-endpoint";
|
|
|
70
74
|
*/
|
|
71
75
|
export const STOREFRONT_CSP_ALLOWLIST = {
|
|
72
76
|
scriptSrc: ["https://static.cloudflareinsights.com"],
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
// Web fonts are self-hosted (@fontsource, bundled same-origin) — no
|
|
78
|
+
// fonts.googleapis.com stylesheet or fonts.gstatic.com woff2 to allowlist;
|
|
79
|
+
// 'self' + `data:` (both added by buildCsp) cover style-src + font-src,
|
|
80
|
+
// including the small fonts Vite inlines as `data:font/…` base64 at build time.
|
|
81
|
+
styleSrc: [],
|
|
82
|
+
fontSrc: [],
|
|
75
83
|
// Product imagery is rehosted to R2 or served from remote CDNs.
|
|
76
84
|
imgSrc: ["https:"],
|
|
77
85
|
connectSrc: ["https://cloudflareinsights.com"],
|
|
@@ -107,3 +115,19 @@ export function checkoutCspAdditions(storeOrigin: string): {
|
|
|
107
115
|
imgSrc: [cdn, storeOrigin],
|
|
108
116
|
};
|
|
109
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* CSP `frame-src` additions for the private-app widgets actually rendered on
|
|
121
|
+
* THIS page (PrivateApps epic, D5). Exact per-install origins, NEVER a
|
|
122
|
+
* wildcard — the sandbox (`allow-scripts`, no `allow-same-origin`) already
|
|
123
|
+
* isolates a widget from the page, but a broad `frame-src` would still let
|
|
124
|
+
* an unrelated origin be framed under cover of "it's a widget"; deriving the
|
|
125
|
+
* allowlist from the SAME resolved placements that rendered means an origin
|
|
126
|
+
* only ever appears here because a live, eligible install's iframe is
|
|
127
|
+
* actually on the page.
|
|
128
|
+
*/
|
|
129
|
+
export function appWidgetCspAdditions(
|
|
130
|
+
placements: readonly { endpoint: string }[],
|
|
131
|
+
): { frameSrc: string[] } {
|
|
132
|
+
return { frameSrc: [...new Set(placements.map((p) => new URL(p.endpoint).origin))] };
|
|
133
|
+
}
|
|
@@ -36,6 +36,29 @@ export interface PreviewStateEvidenceCheck {
|
|
|
36
36
|
required?: boolean;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* One validation finding surfaced to the admin panel (mirrors
|
|
41
|
+
* @tot/private-controlplane's `ReconcileFinding` — kept structural here so this
|
|
42
|
+
* read-only type stays dependency-free). `disposition` is one of
|
|
43
|
+
* `"blocking-safety" | "advisory-skip" | "advisory"`; `skipped` = the offending
|
|
44
|
+
* artifact was NOT materialized (a skip, or an un-overridden safety block).
|
|
45
|
+
*/
|
|
46
|
+
export interface PreviewStateFinding {
|
|
47
|
+
code: string;
|
|
48
|
+
path: string;
|
|
49
|
+
message: string;
|
|
50
|
+
disposition: string;
|
|
51
|
+
skipped: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Auditable authorization that materialized a preview past a blocking-safety
|
|
55
|
+
* finding — the WHO/WHY the admin panel shows (mirrors `SafetyOverride`). */
|
|
56
|
+
export interface PreviewStateOverride {
|
|
57
|
+
by: string;
|
|
58
|
+
reason: string;
|
|
59
|
+
at: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
39
62
|
/** Fail-safe preview state — target/actual can diverge when target is broken. */
|
|
40
63
|
export interface PreviewState {
|
|
41
64
|
tenantId: string;
|
|
@@ -46,6 +69,18 @@ export interface PreviewState {
|
|
|
46
69
|
/** Why target != actual, when the target failed to render. Null on success. */
|
|
47
70
|
lastError: string | null;
|
|
48
71
|
lastReconcileAt: string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Validation findings from the last reconcile — the advisory findings on a
|
|
74
|
+
* materialized preview, or the blocking-safety findings that stopped one —
|
|
75
|
+
* surfaced on the shareable dashboard / admin panel for the live/preview site.
|
|
76
|
+
* Absent/empty ⇒ the tenant's files validated clean. Written by `reconcilePreview`.
|
|
77
|
+
*/
|
|
78
|
+
findings?: PreviewStateFinding[];
|
|
79
|
+
/**
|
|
80
|
+
* The auditable safety-block override applied to this reconcile (who/why), when a
|
|
81
|
+
* blocking-safety finding was materialized past. Absent/null ⇒ no override.
|
|
82
|
+
*/
|
|
83
|
+
overriddenBy?: PreviewStateOverride | null;
|
|
49
84
|
/**
|
|
50
85
|
* The compliance verdict for the last successful reconcile (ADR 0010's evidence,
|
|
51
86
|
* mirrored here so the PUBLIC, unauthenticated /preview/dashboard page can show
|
|
@@ -38,6 +38,14 @@ export function siteVersionKey(tenantId: string, versionId: string): string {
|
|
|
38
38
|
export function siteVersionIndexKey(tenantId: string): string {
|
|
39
39
|
return `cust:versions:${tenantId}`;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* KV key for a tenant's LIVE-PROMOTION history (versions actually flipped onto
|
|
43
|
+
* the `live` channel, in promotion order). Distinct from the raw publish history
|
|
44
|
+
* at `siteVersionIndexKey` — see `PromotionEvent`.
|
|
45
|
+
*/
|
|
46
|
+
export function promotionHistoryKey(tenantId: string): string {
|
|
47
|
+
return `cust:promotions:${tenantId}`;
|
|
48
|
+
}
|
|
41
49
|
|
|
42
50
|
/**
|
|
43
51
|
* Canonical artifact path for a tenant's theme override.
|
|
@@ -93,3 +93,20 @@ export interface SiteVersionSummary {
|
|
|
93
93
|
createdAt: string;
|
|
94
94
|
artifactCount: number;
|
|
95
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* One entry in a tenant's LIVE-PROMOTION history: a version that was actually
|
|
99
|
+
* flipped onto the `live` channel (via `setChannelPointer(..., "live", ...)` —
|
|
100
|
+
* the go-live promote or the initial live seed), in promotion order.
|
|
101
|
+
*
|
|
102
|
+
* Deliberately distinct from `SiteVersionSummary` (the raw publish history):
|
|
103
|
+
* that also contains candidate versions reconciled/published but NEVER promoted,
|
|
104
|
+
* so "the version before the current live" must be resolved from THIS list, not
|
|
105
|
+
* the publish history. A plain rollback (repointing `live` at a prior version)
|
|
106
|
+
* is NOT a promotion and is not recorded here.
|
|
107
|
+
*/
|
|
108
|
+
export interface PromotionEvent {
|
|
109
|
+
versionId: string;
|
|
110
|
+
/** ISO timestamp of the promotion. */
|
|
111
|
+
at: string;
|
|
112
|
+
}
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* used by docs, validators, CLI checks, and the storefront app itself.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export const BLOCK_PALETTE_CURRENT_CONTRACT = "block-palette@
|
|
9
|
+
export const BLOCK_PALETTE_CURRENT_CONTRACT = "block-palette@4" as const;
|
|
10
10
|
export const BLOCK_PALETTE_SUPPORTED_CONTRACTS = [
|
|
11
11
|
"block-palette@1",
|
|
12
12
|
"block-palette@2",
|
|
13
|
+
"block-palette@3",
|
|
13
14
|
BLOCK_PALETTE_CURRENT_CONTRACT,
|
|
14
15
|
] as const;
|
|
15
16
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sha256Hex` — a dependency-free, runtime-portable (Web Crypto, no `node:*`)
|
|
3
|
+
* sha256-hex primitive shared by the write side
|
|
4
|
+
* (`@tot/private-controlplane`'s `customization-versioning.ts`, content-
|
|
5
|
+
* addressed publish) and the renderer (`apps/storefront/src/lib/apps/
|
|
6
|
+
* credentials.ts`, client-secret hashing for the App Gateway).
|
|
7
|
+
*
|
|
8
|
+
* Lives HERE rather than in `@tot/private-controlplane` because it carries no
|
|
9
|
+
* control-plane business logic — it's a bare crypto primitive — and the
|
|
10
|
+
* renderer's `lib/apps/` is machine-fenced (depcruise
|
|
11
|
+
* `renderer-no-controlplane-pkg`) from ever importing that private package.
|
|
12
|
+
* `@tot/private-controlplane` still calls this exact function (no behavior
|
|
13
|
+
* change); PrivateApps epic D2 Chunk B just needed a copy the renderer is
|
|
14
|
+
* allowed to import.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** sha256 of content as lowercase hex, via Web Crypto (Workers + Node 20+). */
|
|
18
|
+
export async function sha256Hex(content: string | Uint8Array): Promise<string> {
|
|
19
|
+
const bytes =
|
|
20
|
+
typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
21
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes as unknown as BufferSource);
|
|
22
|
+
return Array.from(new Uint8Array(digest))
|
|
23
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
24
|
+
.join("");
|
|
25
|
+
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
export * from "./product.js";
|
|
17
17
|
export * from "./tenant.js";
|
|
18
18
|
export * from "./checkout.js";
|
|
19
|
+
export * from "./membership.js";
|
|
19
20
|
export * from "./checkout-orders.js";
|
|
20
21
|
export * from "./capabilities.js";
|
|
21
22
|
export * from "./checkout-style.js";
|
|
@@ -29,8 +30,13 @@ export * from "./customization-runtime.js";
|
|
|
29
30
|
export * from "./customization-versioning.js";
|
|
30
31
|
export * from "./customization-scripts.js";
|
|
31
32
|
export * from "./customization-reconcile.js";
|
|
33
|
+
export * from "./candidate-index.js";
|
|
32
34
|
export * from "./extension-contract.js";
|
|
33
35
|
export * from "./raw-html-policy.js";
|
|
36
|
+
export * from "./review-trust-proof.js";
|
|
37
|
+
export * from "./compliance/index.js";
|
|
34
38
|
export * from "./tenant-assets.js";
|
|
35
39
|
export * from "./binary-path.js";
|
|
36
40
|
export * from "./hot-standby-readiness.js";
|
|
41
|
+
export * from "./hash.js";
|
|
42
|
+
export * from "./widget-postmessage.js";
|