@nextcommerce/campaigns-os 1.61.0 → 1.62.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.
Files changed (127) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +118 -0
  3. package/README.md +86 -438
  4. package/agents/claude/CLAUDE.md +9 -4
  5. package/agents/codex/AGENTS.md +8 -4
  6. package/agents/copilot/copilot-instructions.md +8 -4
  7. package/agents/cursor/campaigns-os.mdc +10 -4
  8. package/campaign-spec/README.md +7 -0
  9. package/campaign-spec/dist/analytics-vendors.d.ts +40 -0
  10. package/campaign-spec/dist/analytics-vendors.js +44 -0
  11. package/campaign-spec/dist/checkout-flow.d.ts +18 -0
  12. package/campaign-spec/dist/checkout-flow.js +28 -0
  13. package/campaign-spec/dist/index.d.ts +4 -2
  14. package/campaign-spec/dist/index.js +5 -1
  15. package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +8 -0
  16. package/campaign-spec/dist/rules/analytics-contract-shape.js +39 -0
  17. package/campaign-spec/dist/rules/campaign-currencies.d.ts +29 -0
  18. package/campaign-spec/dist/rules/campaign-currencies.js +130 -0
  19. package/campaign-spec/dist/rules/checkout-path-has-packages.d.ts +31 -0
  20. package/campaign-spec/dist/rules/checkout-path-has-packages.js +79 -0
  21. package/campaign-spec/dist/rules/index.d.ts +2 -0
  22. package/campaign-spec/dist/rules/index.js +6 -0
  23. package/campaign-spec/dist/types.d.ts +43 -9
  24. package/compatibility.json +1 -1
  25. package/contracts/commerce-surface-catalog.json +59 -53
  26. package/contracts/effects.v1.json +549 -165
  27. package/contracts/fixtures/runtime-recipe/accept/current.json +3 -0
  28. package/contracts/fixtures/runtime-recipe/accept/minimal.json +3 -0
  29. package/contracts/fixtures/runtime-recipe/dist-states.json +1 -1
  30. package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +3 -0
  31. package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +3 -0
  32. package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +3 -0
  33. package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +3 -0
  34. package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +3 -0
  35. package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +3 -0
  36. package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +3 -0
  37. package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +3 -0
  38. package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +3 -0
  39. package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +3 -0
  40. package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +3 -0
  41. package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +3 -0
  42. package/contracts/release-ledger.json +999 -0
  43. package/contracts/runtime-recipe.campaigns-os-node-v1.json +3 -0
  44. package/contracts/supported-surface.json +8 -8
  45. package/contracts/template-brand-contract.shared-commerce.v0.json +23 -1
  46. package/docs/build-packet.md +430 -37
  47. package/docs/campaign-standardization-report.md +4 -4
  48. package/docs/campaigns-os-build-flow.md +54 -3
  49. package/docs/campaignspec-authoring-examples.md +29 -4
  50. package/docs/design-source-package.md +18 -6
  51. package/docs/effects.md +17 -9
  52. package/docs/local-setup.md +1 -1
  53. package/docs/orientation-contract-reference.md +1 -1
  54. package/docs/polish-evidence.md +2 -0
  55. package/docs/progress-snapshots.md +7 -4
  56. package/docs/qa-and-test-orders.md +147 -1
  57. package/docs/readback.md +40 -5
  58. package/docs/runtime-readiness.md +6 -3
  59. package/docs/sdk-storage-compatibility.md +1 -1
  60. package/docs/skills-revision.md +10 -10
  61. package/package.json +1 -1
  62. package/schemas/campaign-runtime-build-context.v0.schema.json +9 -0
  63. package/schemas/campaign-runtime-build-packet.v0.schema.json +9 -0
  64. package/schemas/campaign-spec.v4.schema.json +19 -3
  65. package/schemas/campaign-spec.v5.schema.json +19 -3
  66. package/schemas/campaigns-os-readback.v2.schema.json +62 -1
  67. package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
  68. package/skills/campaign-readback-classification/SKILL.md +3 -3
  69. package/skills/campaign-run-evidence/SKILL.md +3 -3
  70. package/skills/contribution-intake/SKILL.md +3 -3
  71. package/skills/next-campaigns-build/SKILL.md +4 -3
  72. package/skills/next-campaigns-os/SKILL.md +6 -4
  73. package/skills/next-campaigns-os-setup/SKILL.md +5 -3
  74. package/skills/next-campaigns-polish/SKILL.md +5 -4
  75. package/skills/next-campaigns-qa/SKILL.md +7 -4
  76. package/skills/next-campaigns-sdk-bump/SKILL.md +42 -9
  77. package/skills.json +11 -11
  78. package/src/brand-theme.mjs +2 -1
  79. package/src/build-brief.mjs +4 -1
  80. package/src/campaign-workspace.mjs +288 -15
  81. package/src/change-scope.mjs +284 -0
  82. package/src/cli.mjs +308 -86
  83. package/src/commercial-journey.mjs +16 -3
  84. package/src/commercial-parity.mjs +15 -7
  85. package/src/content-residue.mjs +4 -2
  86. package/src/design-source-package.mjs +36 -0
  87. package/src/design-source-publication.mjs +9 -6
  88. package/src/directory-lock.mjs +73 -19
  89. package/src/doctor/checks.mjs +79 -7
  90. package/src/doctor/ci.mjs +75 -10
  91. package/src/doctor/inspect.mjs +23 -6
  92. package/src/doctor/next-step.mjs +7 -0
  93. package/src/doctor-sidecar.mjs +3 -2
  94. package/src/evidence-debt.mjs +47 -7
  95. package/src/finding-cause.mjs +12 -11
  96. package/src/input-refresh.mjs +339 -32
  97. package/src/invocation.mjs +7 -1
  98. package/src/live-campaign-refs.mjs +7 -2
  99. package/src/local-preview-policy.mjs +35 -2
  100. package/src/page-kit-analytics-vendors.mjs +132 -0
  101. package/src/polish-browser.mjs +3 -1
  102. package/src/polish-readability.mjs +221 -9
  103. package/src/polish-visual.mjs +9 -2
  104. package/src/progress-node.mjs +3 -2
  105. package/src/qa-analytics-parity.mjs +17 -1
  106. package/src/qa-browser.mjs +40 -4
  107. package/src/qa-commercial-parity.mjs +50 -3
  108. package/src/qa-node.mjs +196 -16
  109. package/src/qa-publish.mjs +9 -8
  110. package/src/qa-sidecar.mjs +16 -4
  111. package/src/qa-vendor-beacons.mjs +232 -0
  112. package/src/qa-vendor-endpoints.mjs +306 -0
  113. package/src/qc-accept.mjs +29 -1
  114. package/src/qc-check-registry.mjs +5 -1
  115. package/src/qc-results.mjs +73 -26
  116. package/src/readback.mjs +58 -10
  117. package/src/route-identity.mjs +10 -0
  118. package/src/run-record.mjs +18 -10
  119. package/src/runtime-state-ignore.mjs +6 -2
  120. package/src/sdk-storage-compatibility.mjs +21 -2
  121. package/src/source-html-intake.mjs +15 -1
  122. package/src/spec-derive-campaign.mjs +216 -0
  123. package/src/spec-derive.mjs +473 -53
  124. package/src/spec-identity.mjs +14 -0
  125. package/src/stage-ledger.mjs +40 -1
  126. package/src/stage-record.mjs +58 -13
  127. package/src/target-lock.mjs +11 -1
@@ -0,0 +1,79 @@
1
+ /**
2
+ * CheckoutPathHasPackages — every path from an entry page to a Checkout passes
3
+ * at least one page that sells a main-cart package (campaigns-os#711, #206).
4
+ *
5
+ * A Checkout the shopper reaches with nothing in the cart cannot take an
6
+ * order, and until this rule nothing said so before a test order failed. The
7
+ * selling page may be any page on the path up to and including the Checkout:
8
+ * a select page's bundle picker, a landing page's package cards, a checkout
9
+ * step, or the Checkout's own selector. So the rule counts per path, never per
10
+ * page: a Checkout with no packages of its own passes when every path into it
11
+ * sells first.
12
+ *
13
+ * "Main-cart package" is the ratified #206 contract (checkout-flow.ts
14
+ * `isMainCartPackage`, docs/template-family-contracts.md "Main-cart entry"):
15
+ * an entry with neither `is_upsell: true` nor `is_order_bump: true`. Bump rows
16
+ * alone sell nothing, `default_selected` never changes the role, and entries
17
+ * on upsell, downsell or thank-you pages never count.
18
+ *
19
+ * Paths are walked the way OneCheckoutPerPath walks them: both edges a page
20
+ * can traverse (routing.ts), starting at the funnel's entry pages
21
+ * (checkout-flow.ts `entryPages`). No page order or name is consulted. The
22
+ * search runs, per entry page, over (page, sold yet) states and stops at the
23
+ * first Checkout, so each empty Checkout is reported once per entry however
24
+ * many of that entry's paths reach it. A second Checkout on one path is
25
+ * OneCheckoutPerPath's to report.
26
+ *
27
+ * With build_scope.mode === 'partial' the finding is a warning: a partial
28
+ * build may ship a Checkout that a page outside the build fills.
29
+ */
30
+ import { outgoingEdgeIds } from "../routing.js";
31
+ import { entryPages, isPaymentPage, resolveRouteTargetPage, sellsMainCartPackage } from "../checkout-flow.js";
32
+ export const CheckoutPathHasPackages = {
33
+ id: 'CheckoutPathHasPackages',
34
+ severity: 'error',
35
+ tags: ['structure', 'spec-only'],
36
+ check(spec) {
37
+ const violations = [];
38
+ const partial = spec.build_scope?.mode === 'partial';
39
+ spec.funnels.forEach((funnel, funnelIdx) => {
40
+ const pages = (funnel.pages ?? []).filter((page) => page && page.enabled !== false);
41
+ const indexOf = new Map();
42
+ (funnel.pages ?? []).forEach((page, idx) => indexOf.set(page, idx));
43
+ for (const entry of entryPages(funnel)) {
44
+ const seen = new Set();
45
+ const reported = new Set();
46
+ const stack = [[entry, false]];
47
+ while (stack.length) {
48
+ const [page, before] = stack.pop();
49
+ const sold = before || sellsMainCartPackage(page);
50
+ const key = `${indexOf.get(page)}:${sold}`;
51
+ if (seen.has(key))
52
+ continue;
53
+ seen.add(key);
54
+ if (isPaymentPage(page)) {
55
+ if (!sold && !reported.has(page)) {
56
+ reported.add(page);
57
+ violations.push({
58
+ ruleId: 'CheckoutPathHasPackages',
59
+ severity: partial ? 'warning' : 'error',
60
+ message: `A path from entry page "${entry.id}" reaches Checkout "${page.id}" without passing a page that sells a main-cart package, so the cart is empty and no order can be placed. ` +
61
+ 'Add a package entry not marked is_upsell or is_order_bump to the Checkout or a page before it on this path.' +
62
+ (partial ? ' Valid for partial builds when a page outside this build fills the cart.' : ''),
63
+ path: `/funnels/${funnelIdx}/pages/${indexOf.get(page)}`,
64
+ data: { pageId: page.id, entryPageId: entry.id, isPartialScope: partial },
65
+ });
66
+ }
67
+ continue;
68
+ }
69
+ for (const target of outgoingEdgeIds(page)) {
70
+ const next = resolveRouteTargetPage(pages, target);
71
+ if (next)
72
+ stack.push([next, sold]);
73
+ }
74
+ }
75
+ }
76
+ });
77
+ return violations;
78
+ },
79
+ };
@@ -35,11 +35,13 @@ export { CheckoutForwardTarget } from './checkout-forward-target.ts';
35
35
  export { PrePaymentForwardTarget } from './pre-payment-forward-target.ts';
36
36
  export { CheckoutStepReachesCheckout } from './checkout-step-reaches-checkout.ts';
37
37
  export { OneCheckoutPerPath } from './one-checkout-per-path.ts';
38
+ export { CheckoutPathHasPackages } from './checkout-path-has-packages.ts';
38
39
  export { UpsellWithoutCheckout } from './upsell-without-checkout.ts';
39
40
  export { DownsellWithoutUpsell } from './downsell-without-upsell.ts';
40
41
  export { PackagePricingSanity } from './package-pricing-sanity.ts';
41
42
  export { ShippingMethodsPresent } from './shipping-methods-present.ts';
42
43
  export { ShippingCountriesShape } from './shipping-countries-shape.ts';
44
+ export { CampaignCurrencies } from './campaign-currencies.ts';
43
45
  export { OfferRefIntegrity } from './offer-ref-integrity.ts';
44
46
  export { ExitIntentValidation } from './exit-intent-validation.ts';
45
47
  export { PromoCodeInputValidation } from './promo-code-input-validation.ts';
@@ -34,11 +34,13 @@ import { CheckoutForwardTarget } from "./checkout-forward-target.js";
34
34
  import { PrePaymentForwardTarget } from "./pre-payment-forward-target.js";
35
35
  import { CheckoutStepReachesCheckout } from "./checkout-step-reaches-checkout.js";
36
36
  import { OneCheckoutPerPath } from "./one-checkout-per-path.js";
37
+ import { CheckoutPathHasPackages } from "./checkout-path-has-packages.js";
37
38
  import { UpsellWithoutCheckout } from "./upsell-without-checkout.js";
38
39
  import { DownsellWithoutUpsell } from "./downsell-without-upsell.js";
39
40
  import { CampaignMetadata } from "./campaign-metadata.js";
40
41
  import { ShippingMethodsPresent } from "./shipping-methods-present.js";
41
42
  import { ShippingCountriesShape } from "./shipping-countries-shape.js";
43
+ import { CampaignCurrencies } from "./campaign-currencies.js";
42
44
  import { PackagePricingSanity } from "./package-pricing-sanity.js";
43
45
  import { OfferRefIntegrity } from "./offer-ref-integrity.js";
44
46
  import { ExitIntentValidation } from "./exit-intent-validation.js";
@@ -69,11 +71,13 @@ export { CheckoutForwardTarget } from "./checkout-forward-target.js";
69
71
  export { PrePaymentForwardTarget } from "./pre-payment-forward-target.js";
70
72
  export { CheckoutStepReachesCheckout } from "./checkout-step-reaches-checkout.js";
71
73
  export { OneCheckoutPerPath } from "./one-checkout-per-path.js";
74
+ export { CheckoutPathHasPackages } from "./checkout-path-has-packages.js";
72
75
  export { UpsellWithoutCheckout } from "./upsell-without-checkout.js";
73
76
  export { DownsellWithoutUpsell } from "./downsell-without-upsell.js";
74
77
  export { PackagePricingSanity } from "./package-pricing-sanity.js";
75
78
  export { ShippingMethodsPresent } from "./shipping-methods-present.js";
76
79
  export { ShippingCountriesShape } from "./shipping-countries-shape.js";
80
+ export { CampaignCurrencies } from "./campaign-currencies.js";
77
81
  export { OfferRefIntegrity } from "./offer-ref-integrity.js";
78
82
  export { ExitIntentValidation } from "./exit-intent-validation.js";
79
83
  export { PromoCodeInputValidation } from "./promo-code-input-validation.js";
@@ -114,11 +118,13 @@ export const allRules = [
114
118
  PrePaymentForwardTarget,
115
119
  CheckoutStepReachesCheckout,
116
120
  OneCheckoutPerPath,
121
+ CheckoutPathHasPackages,
117
122
  UpsellWithoutCheckout,
118
123
  DownsellWithoutUpsell,
119
124
  PackagePricingSanity,
120
125
  ShippingMethodsPresent,
121
126
  ShippingCountriesShape,
127
+ CampaignCurrencies,
122
128
  OfferRefIntegrity,
123
129
  ExitIntentValidation,
124
130
  PromoCodeInputValidation,
@@ -519,12 +519,17 @@ export interface Campaign {
519
519
  *
520
520
  * Modeled on real production-funnel usage, NOT the idealized "SDK fires the
521
521
  * canonical dl_* set" view. The field reality the block must express:
522
- * - Events fire from three sources: SDK auto, SDK-blocked-then-manual
523
- * (`blockedEvents` suppresses the SDK event, a side script re-fires it via
524
- * raw fbq/gtag to control timing), and fully out-of-band pixels loaded via
525
- * GTM (Everflow / TriplePixel / Northbeam / RudderStack) that never touch
526
- * the dataLayer. So outbound pixel fires — not dataLayer events — are the
527
- * QA source of truth.
522
+ * - Events reach vendors four ways: SDK providers (`providers`: GTM, Meta,
523
+ * RudderStack, a custom endpoint, configured through config.js);
524
+ * SDK-blocked-then-manual (`blockedEvents` suppresses the SDK event, a
525
+ * side script re-fires it via raw fbq/gtag to control timing); the starter
526
+ * templates' vendor adapters (`vendors`: GA4, Axon, Taboola, Triple Whale,
527
+ * TikTok, Northbeam, Snapchat, Pinterest, switched on by a campaigns.json
528
+ * key, riding the SDK's dl_* stream through the template forwarder); and
529
+ * out-of-band pixels a merchant loads some other way, typically through
530
+ * their own GTM container (`out_of_band_pixels`, e.g. Everflow), which
531
+ * never touch the dataLayer. So outbound pixel fires, not dataLayer
532
+ * events, are the QA source of truth.
528
533
  * - A custom provider may carry an endpoint + transform (cookie injection).
529
534
  * - Querystring params split into two classes: CONTENT (param.* → visibility
530
535
  * via data-next-hide) and TRACKING (utm params, gclid, fbclid, subN, click-ids that
@@ -549,8 +554,33 @@ export interface AnalyticsProvider {
549
554
  blockedEvents?: string[];
550
555
  [key: string]: unknown;
551
556
  }
552
- /** A pixel/tag fired OUTSIDE the SDK (via GTM or a raw snippet) — QA must still
553
- * expect it on a live run even though it never appears in the dataLayer. */
557
+ /**
558
+ * One starter-template vendor adapter, keyed in `analytics.vendors` by the
559
+ * vendor key (ga4 | axon | taboola | triplewhale | tiktok | northbeam | snap |
560
+ * pinterest; registry: analytics-vendors.ts). This is the shape the Map
561
+ * Builder emits; its spec compiler writes the matching flat keys into the
562
+ * campaign's `_data/campaigns.json` entry, and `spec derive` mirrors the id
563
+ * back from there.
564
+ */
565
+ export interface AnalyticsVendor {
566
+ /** false turns the vendor off (no campaigns.json key is compiled). */
567
+ enabled?: boolean;
568
+ /** The vendor id: the value of the vendor's campaigns.json id key (`ga4_id`, `triplewhale_name`, …). */
569
+ id?: string;
570
+ /** Triple Whale only: `triplewhale_platform` (e.g. SHOPIFY). The template default, custom-msp, is omitted. */
571
+ platform?: string;
572
+ /** Turns on the vendor's identity / advanced-matching flag (raw email and phone; the `*_enabled` key). */
573
+ identityEnabled?: boolean;
574
+ /** dl_* events the adapter forwards (`<vendor>_allowed_events`); absent means all. */
575
+ allowedEvents?: string[];
576
+ /** dl_* events the adapter drops (`<vendor>_blocked_events`), e.g. purchases on a Shop Sync store. */
577
+ blockedEvents?: string[];
578
+ [key: string]: unknown;
579
+ }
580
+ /** A pixel/tag fired OUTSIDE the SDK and the template adapters (via the
581
+ * merchant's GTM container or a raw snippet). QA must still expect it on a
582
+ * live run even though it never appears in the dataLayer. A vendor the starter
583
+ * templates ship an adapter for belongs in `vendors`, not here. */
554
584
  export interface OutOfBandPixel {
555
585
  vendor: string;
556
586
  loaded_via?: 'gtm' | 'script' | (string & {});
@@ -595,9 +625,13 @@ export interface UtmTransfer {
595
625
  excludedDomains?: string[];
596
626
  }
597
627
  export interface AnalyticsContract {
628
+ /** false turns the whole contract off, as mode "disabled" does. */
629
+ enabled?: boolean;
598
630
  mode?: AnalyticsMode;
599
- /** Keyed by provider kind: gtm | facebook | rudderstack | custom | … */
631
+ /** SDK providers, keyed by provider kind: gtm | facebook | rudderstack | custom | … */
600
632
  providers?: Record<string, AnalyticsProvider>;
633
+ /** Starter-template vendor adapters, keyed by vendor key (see AnalyticsVendor). */
634
+ vendors?: Record<string, AnalyticsVendor>;
601
635
  out_of_band_pixels?: OutOfBandPixel[];
602
636
  manual_events?: ManualEvent[];
603
637
  utmTransfer?: UtmTransfer;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@nextcommerce/campaigns-os",
3
- "version": "1.61.0",
3
+ "version": "1.62.0",
4
4
  "status": "developer-preview",
5
5
  "contracts": {
6
6
  "campaign_spec": "4.2-5.0",
@@ -93,20 +93,18 @@
93
93
  "agentRule": "Replace starter refs from the target CampaignSpec/API. Remove optional bump surfaces when the referenced prepurchase package is absent."
94
94
  },
95
95
  "shipping_methods": {
96
- "purpose": "Named Campaigns App shipping method ref_ids used by checkout selector cards.",
96
+ "purpose": "Named Campaigns App shipping method ref_ids used by checkout selector cards. Starter checkouts use ONE method (the campaign's single storefront method, priced at the highest tier rate) on every card.",
97
97
  "canonicalKeys": [
98
- "standard",
99
- "free"
98
+ "standard"
100
99
  ],
101
100
  "sourceOfTruth": [
102
101
  "CampaignSpec shipping_methods[]",
103
102
  "Campaigns API shipping_options[].ref_id"
104
103
  ],
105
104
  "demoOnlyValues": [
106
- 2,
107
105
  1
108
106
  ],
109
- "agentRule": "Use only on checkout/cart selector surfaces. Do not carry starter refs between campaigns; remove shipping_method rows when the target campaign has no matching method."
107
+ "agentRule": "Use only on checkout/cart selector surfaces. Do not carry starter refs between campaigns. A campaign lists each storefront shipping method once: price differences between tiers are shipping coupons on the cards (bundles[].vouchers / variant_slots[].vouchers), not a second method. Map a second key only to a genuinely different storefront method (e.g. Express)."
110
108
  },
111
109
  "bundles": {
112
110
  "purpose": "Checkout bundle-card rows for package quantity tiers.",
@@ -114,6 +112,7 @@
114
112
  "id",
115
113
  "quantity",
116
114
  "shipping_method",
115
+ "vouchers",
117
116
  "selected",
118
117
  "title_prefix",
119
118
  "badge_text",
@@ -123,7 +122,7 @@
123
122
  "CampaignSpec checkout page packages/offers",
124
123
  "Campaigns API package and shipping refs"
125
124
  ],
126
- "agentRule": "Prefer package_id plus quantity. Use explicit items_json only for genuinely custom item sets."
125
+ "agentRule": "Prefer package_id plus quantity. Use explicit items_json only for genuinely custom item sets. `vouchers` (string or list) renders data-next-bundle-vouchers: the coupon applied while that card is selected — use a shipping_percentage Code Offer for tier shipping price."
127
126
  },
128
127
  "variant_slots": {
129
128
  "purpose": "MV/configurable bundle slots for selector rows and later variant choice stages.",
@@ -131,6 +130,7 @@
131
130
  "id",
132
131
  "quantity",
133
132
  "shipping_method",
133
+ "vouchers",
134
134
  "selected",
135
135
  "title",
136
136
  "label_text",
@@ -608,7 +608,7 @@
608
608
  ],
609
609
  "optionalWhenSupported": [
610
610
  "cart_summary_variant",
611
- "shipping_methods.free",
611
+ "bundles[].vouchers",
612
612
  "order_bump_variant",
613
613
  "order_bump",
614
614
  "packages.prepurchase_1",
@@ -623,8 +623,8 @@
623
623
  "packages.main_package=1",
624
624
  "packages.prepurchase_1=7",
625
625
  "packages.prepurchase_2=9",
626
- "shipping_methods.standard=2",
627
- "shipping_methods.free=1",
626
+ "shipping_methods.standard=1",
627
+ "bundles[].vouchers=FREESHIP",
628
628
  "upsell_offer.package_id=3",
629
629
  "upsell_offer.vouchers_json=[\"UP50\"]",
630
630
  "upsell_bundle_tiers packageId=3"
@@ -633,13 +633,14 @@
633
633
  "packages.*",
634
634
  "shipping_methods.*",
635
635
  "bundles[].shipping_method",
636
+ "bundles[].vouchers",
636
637
  "order_bump.*.package_id",
637
638
  "upsell_offer.*",
638
639
  "upsell_bundle_tiers[].items_json",
639
640
  "upsell_bundle_tiers[].vouchers_json"
640
641
  ],
641
642
  "removeWhenUnsupported": [
642
- "bundles[].shipping_method when no tier-specific shipping exists",
643
+ "bundles[].vouchers when no tier-specific shipping price exists",
643
644
  "bump includes when prepurchase packages are absent",
644
645
  "unsupported payment method rows",
645
646
  "upsell pages not present in the funnel"
@@ -850,8 +851,8 @@
850
851
  },
851
852
  "verification": {
852
853
  "sdk_version": "0.4.43",
853
- "verified_at": "2026-10-21T04:05:46Z",
854
- "evidence": "sdk-0.4.43-2026-10-21.2",
854
+ "verified_at": "2026-10-24T04:58:52Z",
855
+ "evidence": "sdk-0.4.43-2026-10-24",
855
856
  "status": "certified",
856
857
  "source": "template-verification.json"
857
858
  }
@@ -861,8 +862,8 @@
861
862
  "templateReference": {
862
863
  "id": "template-reference-apollo",
863
864
  "family": "apollo",
864
- "version": "sdk-0.4.38-composable-upsells-2026-09-25",
865
- "source_commit": "03f5982d703e07f8dc6beaedc121d55093373e4d",
865
+ "version": "sdk-0.4.43-shipping-coupon-tiers-2026-10-23",
866
+ "source_commit": "11af184434963340df74bee96a1f8559fcbf9028",
866
867
  "provenance_url": "https://raw.githubusercontent.com/NextCommerceCo/campaign-cart-starter-templates/main/docs/template-references/apollo/README.md",
867
868
  "contract_path": "contracts/commerce-surface-catalog.json",
868
869
  "standard_viewport_refs": [
@@ -872,10 +873,10 @@
872
873
  "url": "https://raw.githubusercontent.com/NextCommerceCo/campaign-cart-starter-templates/main/docs/template-references/apollo/checkout-desktop.png",
873
874
  "width": 1440,
874
875
  "height": 1491,
875
- "sha256": "7595630f9e3dd4da09c395f7681c1ff546011141d3981412d5f76a1ffe2d5c90",
876
+ "sha256": "3a619d34f9fc8507b3a606db50218936c4d44bd1c2a21a2e8f87af3efa545fa7",
876
877
  "device_profile": "Desktop 1440",
877
878
  "browser": "chromium",
878
- "captured_at": "2026-09-25T00:10:12.000Z"
879
+ "captured_at": "2026-10-23T22:04:21.000Z"
879
880
  },
880
881
  {
881
882
  "id": "apollo-checkout-mobile",
@@ -883,10 +884,10 @@
883
884
  "url": "https://raw.githubusercontent.com/NextCommerceCo/campaign-cart-starter-templates/main/docs/template-references/apollo/checkout-mobile.png",
884
885
  "width": 390,
885
886
  "height": 2648,
886
- "sha256": "1d797be266beefb10cf7ec4502d8f8c97286a0861b969695986ebfc1ac29ce63",
887
+ "sha256": "5a8dc7560c89e7c9e162ed41a64af94a2324b0b725ff1c167719dc259ebf59a6",
887
888
  "device_profile": "Mobile 390",
888
889
  "browser": "chromium",
889
- "captured_at": "2026-09-25T00:10:12.000Z"
890
+ "captured_at": "2026-10-23T22:04:21.000Z"
890
891
  }
891
892
  ]
892
893
  },
@@ -977,7 +978,7 @@
977
978
  "optionalWhenSupported": [
978
979
  "bundle_card_style",
979
980
  "cart_summary_variant",
980
- "shipping_methods.free",
981
+ "bundles[].vouchers",
981
982
  "selector_order_bump_variant",
982
983
  "order_bump_variant",
983
984
  "order_bump",
@@ -1010,8 +1011,8 @@
1010
1011
  "packages.main_package=1",
1011
1012
  "packages.prepurchase_1=7",
1012
1013
  "packages.prepurchase_2=9",
1013
- "shipping_methods.standard=2",
1014
- "shipping_methods.free=1",
1014
+ "shipping_methods.standard=1",
1015
+ "bundles[].vouchers=FREESHIP",
1015
1016
  "bundle_card_style=product",
1016
1017
  "selector_order_bump_variant=check03",
1017
1018
  "order_bump_variant=check01+switch01",
@@ -1024,13 +1025,14 @@
1024
1025
  "packages.*",
1025
1026
  "shipping_methods.*",
1026
1027
  "bundles[].shipping_method",
1028
+ "bundles[].vouchers",
1027
1029
  "order_bump.*.package_id",
1028
1030
  "upsell_offer.*",
1029
1031
  "upsell_bundle_tiers[].items_json",
1030
1032
  "upsell_bundle_tiers[].vouchers_json"
1031
1033
  ],
1032
1034
  "removeWhenUnsupported": [
1033
- "bundles[].shipping_method when no tier-specific shipping exists",
1035
+ "bundles[].vouchers when no tier-specific shipping price exists",
1034
1036
  "selector_order_bump_variant when no selector-area bump package exists",
1035
1037
  "bump includes when prepurchase packages are absent",
1036
1038
  "unsupported payment method rows",
@@ -1274,8 +1276,8 @@
1274
1276
  },
1275
1277
  "verification": {
1276
1278
  "sdk_version": "0.4.43",
1277
- "verified_at": "2026-10-21T04:05:46Z",
1278
- "evidence": "sdk-0.4.43-2026-10-21.2",
1279
+ "verified_at": "2026-10-24T04:58:52Z",
1280
+ "evidence": "sdk-0.4.43-2026-10-24",
1279
1281
  "status": "certified",
1280
1282
  "source": "template-verification.json"
1281
1283
  }
@@ -1366,7 +1368,7 @@
1366
1368
  "bundles[].shipping_method"
1367
1369
  ],
1368
1370
  "optionalWhenSupported": [
1369
- "shipping_methods.free",
1371
+ "bundles[].vouchers",
1370
1372
  "cart_summary_variant",
1371
1373
  "order_bump_variant",
1372
1374
  "upsell_offer",
@@ -1375,8 +1377,8 @@
1375
1377
  ],
1376
1378
  "demoOnlyValues": [
1377
1379
  "packages.main_package=1",
1378
- "shipping_methods.standard=2",
1379
- "shipping_methods.free=1",
1380
+ "shipping_methods.standard=1",
1381
+ "bundles[].vouchers=FREESHIP",
1380
1382
  "packages.prepurchase_1=7",
1381
1383
  "packages.prepurchase_2=9"
1382
1384
  ],
@@ -1384,12 +1386,13 @@
1384
1386
  "packages.*",
1385
1387
  "shipping_methods.*",
1386
1388
  "bundles[].shipping_method",
1389
+ "bundles[].vouchers",
1387
1390
  "order_bump.*.package_id",
1388
1391
  "upsell_offer.package_id",
1389
1392
  "upsell_bundle_tiers[].items_json"
1390
1393
  ],
1391
1394
  "removeWhenUnsupported": [
1392
- "bundles[].shipping_method when no tier-specific shipping exists",
1395
+ "bundles[].vouchers when no tier-specific shipping price exists",
1393
1396
  "order bump variants when prepurchase packages are absent"
1394
1397
  ]
1395
1398
  },
@@ -1585,8 +1588,8 @@
1585
1588
  },
1586
1589
  "verification": {
1587
1590
  "sdk_version": "0.4.43",
1588
- "verified_at": "2026-10-21T04:05:46Z",
1589
- "evidence": "sdk-0.4.43-2026-10-21.2",
1591
+ "verified_at": "2026-10-24T04:58:52Z",
1592
+ "evidence": "sdk-0.4.43-2026-10-24",
1590
1593
  "status": "certified",
1591
1594
  "source": "template-verification.json"
1592
1595
  }
@@ -1888,8 +1891,8 @@
1888
1891
  },
1889
1892
  "verification": {
1890
1893
  "sdk_version": "0.4.43",
1891
- "verified_at": "2026-10-21T04:05:46Z",
1892
- "evidence": "sdk-0.4.43-2026-10-21.2",
1894
+ "verified_at": "2026-10-24T04:58:52Z",
1895
+ "evidence": "sdk-0.4.43-2026-10-24",
1893
1896
  "status": "certified",
1894
1897
  "source": "template-verification.json"
1895
1898
  }
@@ -1980,7 +1983,7 @@
1980
1983
  "variant_slots[].shipping_method"
1981
1984
  ],
1982
1985
  "optionalWhenSupported": [
1983
- "shipping_methods.free",
1986
+ "variant_slots[].vouchers",
1984
1987
  "checkout_step.selector_layout",
1985
1988
  "swiper_slides",
1986
1989
  "swiper_thumbs",
@@ -1993,8 +1996,8 @@
1993
1996
  ],
1994
1997
  "demoOnlyValues": [
1995
1998
  "packages.main_package=1",
1996
- "shipping_methods.standard=2",
1997
- "shipping_methods.free=1",
1999
+ "shipping_methods.standard=1",
2000
+ "variant_slots[].vouchers=FREESHIP",
1998
2001
  "upsell_offer.package_id=1",
1999
2002
  "upsell_offer.package_id=3",
2000
2003
  "upsell_mv_tiers vouchers_json=[\"UP80TSHIRT\"]",
@@ -2005,6 +2008,7 @@
2005
2008
  "packages.main_package",
2006
2009
  "shipping_methods.*",
2007
2010
  "variant_slots[].shipping_method",
2011
+ "variant_slots[].vouchers",
2008
2012
  "upsell_offer.*",
2009
2013
  "upsell_mv_tiers[].vouchers_json",
2010
2014
  "slot_personalization",
@@ -2013,7 +2017,7 @@
2013
2017
  ],
2014
2018
  "removeWhenUnsupported": [
2015
2019
  "variant slot rows unsupported by the target spec/API",
2016
- "free shipping row if no free method exists",
2020
+ "variant_slots[].vouchers when no tier-specific shipping price exists",
2017
2021
  "upsell pages not present in the funnel"
2018
2022
  ]
2019
2023
  },
@@ -2229,8 +2233,8 @@
2229
2233
  },
2230
2234
  "verification": {
2231
2235
  "sdk_version": "0.4.43",
2232
- "verified_at": "2026-10-21T04:05:46Z",
2233
- "evidence": "sdk-0.4.43-2026-10-21.2",
2236
+ "verified_at": "2026-10-24T04:58:52Z",
2237
+ "evidence": "sdk-0.4.43-2026-10-24",
2234
2238
  "status": "certified",
2235
2239
  "source": "template-verification.json"
2236
2240
  }
@@ -2334,7 +2338,7 @@
2334
2338
  "variant_slots[].shipping_method"
2335
2339
  ],
2336
2340
  "optionalWhenSupported": [
2337
- "shipping_methods.free",
2341
+ "variant_slots[].vouchers",
2338
2342
  "checkout_step.selector_layout",
2339
2343
  "selector_order_bump_variant",
2340
2344
  "order_bump_variant",
@@ -2357,8 +2361,8 @@
2357
2361
  ],
2358
2362
  "demoOnlyValues": [
2359
2363
  "packages.main_package=1",
2360
- "shipping_methods.standard=2",
2361
- "shipping_methods.free=1",
2364
+ "shipping_methods.standard=1",
2365
+ "variant_slots[].vouchers=FREESHIP",
2362
2366
  "checkout_step.selector_layout=grid",
2363
2367
  "selector_order_bump_variant=check03",
2364
2368
  "order_bump_variant=check01",
@@ -2372,6 +2376,7 @@
2372
2376
  "packages.main_package",
2373
2377
  "shipping_methods.*",
2374
2378
  "variant_slots[].shipping_method",
2379
+ "variant_slots[].vouchers",
2375
2380
  "upsell_offer.*",
2376
2381
  "upsell_mv_tiers[].vouchers_json",
2377
2382
  "slot_personalization",
@@ -2381,7 +2386,7 @@
2381
2386
  "removeWhenUnsupported": [
2382
2387
  "variant slot rows unsupported by the target spec/API",
2383
2388
  "selector_order_bump_variant when no selector-area bump package exists",
2384
- "free shipping row if no free method exists",
2389
+ "variant_slots[].vouchers when no tier-specific shipping price exists",
2385
2390
  "upsell pages not present in the funnel"
2386
2391
  ]
2387
2392
  },
@@ -2668,8 +2673,8 @@
2668
2673
  },
2669
2674
  "verification": {
2670
2675
  "sdk_version": "0.4.43",
2671
- "verified_at": "2026-10-21T04:05:46Z",
2672
- "evidence": "sdk-0.4.43-2026-10-21.2",
2676
+ "verified_at": "2026-10-24T04:58:52Z",
2677
+ "evidence": "sdk-0.4.43-2026-10-24",
2673
2678
  "status": "certified",
2674
2679
  "source": "template-verification.json"
2675
2680
  }
@@ -2761,7 +2766,7 @@
2761
2766
  "select_step.selector_id"
2762
2767
  ],
2763
2768
  "optionalWhenSupported": [
2764
- "shipping_methods.free",
2769
+ "variant_slots[].vouchers",
2765
2770
  "checkout_step.selector_id",
2766
2771
  "select_step.selector_layout",
2767
2772
  "swiper_slides",
@@ -2774,8 +2779,8 @@
2774
2779
  ],
2775
2780
  "demoOnlyValues": [
2776
2781
  "packages.main_package=1",
2777
- "shipping_methods.standard=2",
2778
- "shipping_methods.free=1",
2782
+ "shipping_methods.standard=1",
2783
+ "variant_slots[].vouchers=FREESHIP",
2779
2784
  "upsell_offer.package_id=1",
2780
2785
  "upsell_offer.package_id=3",
2781
2786
  "upsell_mv_tiers vouchers_json=[\"UP80TSHIRT\"]",
@@ -2786,6 +2791,7 @@
2786
2791
  "packages.main_package",
2787
2792
  "shipping_methods.*",
2788
2793
  "variant_slots[].shipping_method",
2794
+ "variant_slots[].vouchers",
2789
2795
  "upsell_offer.*",
2790
2796
  "upsell_mv_tiers[].vouchers_json",
2791
2797
  "slot_personalization",
@@ -2794,7 +2800,7 @@
2794
2800
  ],
2795
2801
  "removeWhenUnsupported": [
2796
2802
  "variant slot rows unsupported by the target spec/API",
2797
- "free shipping row if no free method exists",
2803
+ "variant_slots[].vouchers when no tier-specific shipping price exists",
2798
2804
  "upsell pages not present in the funnel"
2799
2805
  ]
2800
2806
  },
@@ -3024,8 +3030,8 @@
3024
3030
  },
3025
3031
  "verification": {
3026
3032
  "sdk_version": "0.4.43",
3027
- "verified_at": "2026-10-21T04:05:46Z",
3028
- "evidence": "sdk-0.4.43-2026-10-21.2",
3033
+ "verified_at": "2026-10-24T04:58:52Z",
3034
+ "evidence": "sdk-0.4.43-2026-10-24",
3029
3035
  "status": "certified",
3030
3036
  "source": "template-verification.json"
3031
3037
  }
@@ -3314,8 +3320,8 @@
3314
3320
  },
3315
3321
  "verification": {
3316
3322
  "sdk_version": "0.4.43",
3317
- "verified_at": "2026-10-21T04:05:46Z",
3318
- "evidence": "sdk-0.4.43-2026-10-21.2",
3323
+ "verified_at": "2026-10-24T04:58:52Z",
3324
+ "evidence": "sdk-0.4.43-2026-10-24",
3319
3325
  "status": "certified",
3320
3326
  "source": "template-verification.json"
3321
3327
  }
@@ -3505,5 +3511,5 @@
3505
3511
  },
3506
3512
  "_synced_from_repo": "NextCommerceCo/campaign-cart-starter-templates",
3507
3513
  "_synced_from_ref": "main",
3508
- "_synced_from_sha": "b38343e85a05ec2fe949e13e783057d4c717a8d6"
3514
+ "_synced_from_sha": "5711e22345756c655f41c0ae71444478e4a8999e"
3509
3515
  }