@nextcommerce/campaigns-os 1.54.0 → 1.57.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 (111) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/CONTEXT.md +19 -5
  3. package/README.md +26 -12
  4. package/campaign-spec/README.md +15 -1
  5. package/campaign-spec/dist/checkout-flow.d.ts +90 -0
  6. package/campaign-spec/dist/checkout-flow.js +229 -0
  7. package/campaign-spec/dist/index.d.ts +1 -0
  8. package/campaign-spec/dist/index.js +6 -1
  9. package/campaign-spec/dist/normalize.d.ts +3 -2
  10. package/campaign-spec/dist/normalize.js +7 -5
  11. package/campaign-spec/dist/routing.d.ts +6 -3
  12. package/campaign-spec/dist/routing.js +6 -3
  13. package/campaign-spec/dist/rules/checkout-forward-target.d.ts +17 -0
  14. package/campaign-spec/dist/rules/checkout-forward-target.js +48 -0
  15. package/campaign-spec/dist/rules/checkout-step-reaches-checkout.d.ts +12 -0
  16. package/campaign-spec/dist/rules/checkout-step-reaches-checkout.js +37 -0
  17. package/campaign-spec/dist/rules/exit-intent-validation.js +2 -1
  18. package/campaign-spec/dist/rules/index.d.ts +4 -0
  19. package/campaign-spec/dist/rules/index.js +12 -0
  20. package/campaign-spec/dist/rules/one-checkout-per-path.d.ts +25 -0
  21. package/campaign-spec/dist/rules/one-checkout-per-path.js +84 -0
  22. package/campaign-spec/dist/rules/pre-payment-forward-target.d.ts +12 -0
  23. package/campaign-spec/dist/rules/pre-payment-forward-target.js +42 -0
  24. package/campaign-spec/dist/rules/promo-code-input-validation.js +2 -1
  25. package/campaign-spec/dist/rules/schema-version.d.ts +9 -4
  26. package/campaign-spec/dist/rules/schema-version.js +10 -5
  27. package/campaign-spec/dist/types.d.ts +20 -3
  28. package/campaign-spec/dist/types.js +3 -2
  29. package/compatibility.json +2 -2
  30. package/contracts/fixtures/runtime-recipe/accept/current.json +6 -1
  31. package/contracts/fixtures/runtime-recipe/accept/minimal.json +6 -1
  32. package/contracts/fixtures/runtime-recipe/dist-states.json +1 -1
  33. package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +6 -1
  34. package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +6 -1
  35. package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +6 -1
  36. package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +6 -1
  37. package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +6 -1
  38. package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +1 -1
  39. package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +6 -1
  40. package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +6 -1
  41. package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +6 -1
  42. package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +5 -0
  43. package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +6 -1
  44. package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +6 -1
  45. package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +6 -1
  46. package/contracts/release-ledger.json +1146 -0
  47. package/contracts/runtime-recipe.campaigns-os-node-v1.json +6 -1
  48. package/contracts/supported-surface.json +9 -5
  49. package/docs/brand-theme-bridge.md +10 -0
  50. package/docs/build-packet.md +86 -26
  51. package/docs/campaigns-os-build-flow.md +1 -1
  52. package/docs/effects.md +2 -5
  53. package/docs/local-setup.md +1 -1
  54. package/docs/orientation-contract-reference.md +1 -1
  55. package/docs/qa-and-test-orders.md +133 -29
  56. package/docs/runtime-readiness.md +8 -3
  57. package/docs/skills-revision.md +10 -10
  58. package/docs/supported-surface.md +1 -1
  59. package/docs/versioning.md +4 -1
  60. package/docs/workflow-findings-sidecar.md +57 -2
  61. package/package.json +4 -1
  62. package/schemas/campaign-runtime-build-context.v0.schema.json +3 -1
  63. package/schemas/campaign-runtime-build-packet.v0.schema.json +3 -1
  64. package/schemas/campaign-spec.v5.schema.json +586 -0
  65. package/schemas/campaigns-os-run-record.v0.schema.json +20 -15
  66. package/skills/campaign-lifecycle-orientation/SKILL.md +10 -8
  67. package/skills/campaign-readback-classification/SKILL.md +3 -3
  68. package/skills/campaign-run-evidence/SKILL.md +3 -3
  69. package/skills/contribution-intake/SKILL.md +3 -3
  70. package/skills/next-campaigns-build/SKILL.md +6 -4
  71. package/skills/next-campaigns-os/SKILL.md +3 -3
  72. package/skills/next-campaigns-os-setup/SKILL.md +3 -3
  73. package/skills/next-campaigns-polish/SKILL.md +21 -6
  74. package/skills/next-campaigns-qa/SKILL.md +5 -4
  75. package/skills.json +10 -10
  76. package/src/adapter-decision-contract.mjs +27 -13
  77. package/src/brand-theme.mjs +44 -8
  78. package/src/build-brief.mjs +14 -5
  79. package/src/campaign-identity.mjs +33 -4
  80. package/src/cli-helpers.mjs +129 -0
  81. package/src/cli.mjs +156 -111
  82. package/src/commercial-journey.mjs +25 -2
  83. package/src/consent.mjs +3 -2
  84. package/src/design-source-package.mjs +1 -1
  85. package/src/design-source-publication.mjs +8 -8
  86. package/src/deviation.mjs +2 -1
  87. package/src/doctor/checks.mjs +212 -19
  88. package/src/doctor/inspect.mjs +4 -2
  89. package/src/doctor/next-step.mjs +46 -16
  90. package/src/doctor-sidecar.mjs +5 -3
  91. package/src/gate-actions.mjs +21 -1
  92. package/src/input-refresh.mjs +6 -6
  93. package/src/invocation.mjs +4 -3
  94. package/src/lifecycle.mjs +88 -6
  95. package/src/local-preview-policy.mjs +116 -12
  96. package/src/login.mjs +2 -1
  97. package/src/polish-browser.mjs +8 -2
  98. package/src/polish-node.mjs +9 -4
  99. package/src/polish-readability.mjs +2 -2
  100. package/src/qa-browser.mjs +741 -120
  101. package/src/qa-cart-entry.mjs +107 -20
  102. package/src/qa-node.mjs +179 -72
  103. package/src/qa-test-order-topology.mjs +118 -1
  104. package/src/qa-verdict-discovery.mjs +21 -4
  105. package/src/qa-verdict-publish.mjs +2 -1
  106. package/src/run-record.mjs +46 -34
  107. package/src/runtime-state-ignore.mjs +7 -5
  108. package/src/sdk-markup.mjs +41 -1
  109. package/src/source-html-intake.mjs +46 -4
  110. package/src/stage-ledger.mjs +18 -4
  111. package/src/stage-record.mjs +53 -16
@@ -0,0 +1,42 @@
1
+ /**
2
+ * PrePaymentForwardTarget — a `select` or `checkout_step` page takes no
3
+ * payment, so it must not forward straight to a post-payment page (an upsell,
4
+ * a downsell or a thank-you page). Doing so skips the Checkout: the shopper
5
+ * reaches an offer or a receipt with no order placed (campaigns-os#641).
6
+ *
7
+ * Only the resolved forward link is judged — routing.ts already ignores a
8
+ * `success_url` or `on_accept` on these types, and RouteFieldIgnoredForPageType
9
+ * warns about those separately.
10
+ */
11
+ import { forwardRouteTarget } from "../routing.js";
12
+ import { POST_PAYMENT_PAGE_TYPES, forwardTargetPage, isPrePaymentPage } from "../checkout-flow.js";
13
+ export const PrePaymentForwardTarget = {
14
+ id: 'PrePaymentForwardTarget',
15
+ severity: 'error',
16
+ tags: ['fast', 'structure', 'spec-only'],
17
+ check(spec) {
18
+ const violations = [];
19
+ spec.funnels.forEach((funnel, funnelIdx) => {
20
+ const pages = funnel.pages ?? [];
21
+ pages.forEach((page, pageIdx) => {
22
+ if (!isPrePaymentPage(page))
23
+ return;
24
+ const target = forwardTargetPage(pages, page);
25
+ if (!target)
26
+ return;
27
+ const targetType = typeof target.type === 'string' ? target.type : '';
28
+ if (!POST_PAYMENT_PAGE_TYPES.includes(targetType))
29
+ return;
30
+ violations.push({
31
+ ruleId: 'PrePaymentForwardTarget',
32
+ severity: 'error',
33
+ message: `"${page.type}" page "${page.id}" forwards to "${target.id}" (type "${targetType}") without passing a Checkout. ` +
34
+ 'Route it (via next_page) to the next checkout step or to the Checkout that takes payment.',
35
+ path: `/funnels/${funnelIdx}/pages/${pageIdx}/next_page`,
36
+ data: { pageId: page.id, pageType: page.type, target: forwardRouteTarget(page), targetPageId: target.id, targetType },
37
+ });
38
+ });
39
+ });
40
+ return violations;
41
+ },
42
+ };
@@ -4,6 +4,7 @@
4
4
  * intent because both surfaces apply a voucher/code-backed Offer through the
5
5
  * same Campaign Cart SDK/Campaigns API pricing path.
6
6
  */
7
+ import { isCheckoutFlowPage } from "../checkout-flow.js";
7
8
  function isTruthyString(value) {
8
9
  return typeof value === 'string' && value.trim().length > 0;
9
10
  }
@@ -39,7 +40,7 @@ export const PromoCodeInputValidation = {
39
40
  const code = promo.offer_code;
40
41
  const refOffer = ref ? index.byRefId.get(String(ref)) : null;
41
42
  const codeOffer = code ? index.byCode.get(String(code).toUpperCase()) : null;
42
- if (page.type !== 'checkout') {
43
+ if (!isCheckoutFlowPage(page)) {
43
44
  violations.push({
44
45
  ruleId: 'PromoCodeInputValidation',
45
46
  severity: 'warning',
@@ -9,13 +9,18 @@
9
9
  * lineage errors here at authoring/export time instead of surfacing as
10
10
  * downstream shape drift.
11
11
  *
12
- * SUPPORTED_SCHEMA_VERSIONS must stay identical to the schema_version enum in
13
- * schemas/campaign-spec.v4.schema.json (and compatibility.json's campaign_spec
14
- * range). The rule stays pure (no filesystem), so the constant duplicates the
12
+ * SUPPORTED_SCHEMA_VERSIONS must stay identical to the schema_version enums of
13
+ * schemas/campaign-spec.v4.schema.json and schemas/campaign-spec.v5.schema.json,
14
+ * concatenated (and compatibility.json's campaign_spec range). The rule stays pure (no filesystem), so the constant duplicates the
15
15
  * schema enum — the sync test in ../test/rules/schema-version.test.ts is the
16
16
  * drift gate, following the UnknownTopLevelFields pattern.
17
17
  */
18
18
  import type { Rule } from '../types.ts';
19
- /** Supported CampaignSpec lineage versions — keep in sync with the v4 JSON Schema enum. */
19
+ /**
20
+ * Supported CampaignSpec lineage versions — the v4 JSON Schema enum followed by
21
+ * the v5 one, in that order. A 4.2/4.3 spec is upgraded to v5 on read
22
+ * (checkout-flow.ts), so this rule sees `5.0` for it after normalize(); the v4
23
+ * entries stay so a caller running the rule on an un-normalized spec agrees.
24
+ */
20
25
  export declare const SUPPORTED_SCHEMA_VERSIONS: readonly string[];
21
26
  export declare const SchemaVersion: Rule;
@@ -9,14 +9,19 @@
9
9
  * lineage errors here at authoring/export time instead of surfacing as
10
10
  * downstream shape drift.
11
11
  *
12
- * SUPPORTED_SCHEMA_VERSIONS must stay identical to the schema_version enum in
13
- * schemas/campaign-spec.v4.schema.json (and compatibility.json's campaign_spec
14
- * range). The rule stays pure (no filesystem), so the constant duplicates the
12
+ * SUPPORTED_SCHEMA_VERSIONS must stay identical to the schema_version enums of
13
+ * schemas/campaign-spec.v4.schema.json and schemas/campaign-spec.v5.schema.json,
14
+ * concatenated (and compatibility.json's campaign_spec range). The rule stays pure (no filesystem), so the constant duplicates the
15
15
  * schema enum — the sync test in ../test/rules/schema-version.test.ts is the
16
16
  * drift gate, following the UnknownTopLevelFields pattern.
17
17
  */
18
- /** Supported CampaignSpec lineage versions — keep in sync with the v4 JSON Schema enum. */
19
- export const SUPPORTED_SCHEMA_VERSIONS = Object.freeze(['4.2', '4.3']);
18
+ /**
19
+ * Supported CampaignSpec lineage versions — the v4 JSON Schema enum followed by
20
+ * the v5 one, in that order. A 4.2/4.3 spec is upgraded to v5 on read
21
+ * (checkout-flow.ts), so this rule sees `5.0` for it after normalize(); the v4
22
+ * entries stay so a caller running the rule on an un-normalized spec agrees.
23
+ */
24
+ export const SUPPORTED_SCHEMA_VERSIONS = Object.freeze(['4.2', '4.3', '5.0']);
20
25
  export const SchemaVersion = {
21
26
  id: 'SchemaVersion',
22
27
  severity: 'error',
@@ -2,8 +2,9 @@
2
2
  * Type surface for the CampaignSpec contract layer.
3
3
  *
4
4
  * The CampaignSpec is the central authoring contract of this repo (see
5
- * ../CONTEXT.md). v4.3 is the authoring shape; v4.2 funnels[] is the canonical
6
- * internal shape that rules operate on (after normalize()).
5
+ * ../CONTEXT.md). v5.0 is the authoring shape; funnels[] is the canonical
6
+ * internal shape that rules operate on (after normalize(), which upgrades a
7
+ * v4.2/v4.3 spec to the v5 page vocabulary — see checkout-flow.ts).
7
8
  *
8
9
  * Types here are intentionally permissive on optional fields — the rule
9
10
  * registry catches missing/malformed fields, rather than the type system
@@ -63,7 +64,23 @@ export type PageType = 'presell' | 'landing'
63
64
  * Not to be confused with the page ID "select", which several family
64
65
  * contracts use for this page and which is a separate namespace.
65
66
  */
66
- | 'select' | 'checkout' | 'upsell' | 'downsell' | 'thankyou';
67
+ | 'select'
68
+ /**
69
+ * A page with a checkout form that collects details and moves on without
70
+ * placing an order: the information and shipping steps of a split checkout
71
+ * (campaigns-os#641). Routes forward through `next_page`; it takes no
72
+ * payment, so `success_url` and `on_accept` are ignored here as on `select`.
73
+ * Renders as SDK page type `checkout`; its form carries
74
+ * `data-next-checkout-step` and `data-next-step-number`.
75
+ */
76
+ | 'checkout_step'
77
+ /**
78
+ * The page that takes payment. Exactly one on every path from an entry page
79
+ * to an upsell. Routes forward through `success_url` (or `next_page`) to an
80
+ * upsell, downsell or thank-you page. A campaign may hold several, one per
81
+ * path.
82
+ */
83
+ | 'checkout' | 'upsell' | 'downsell' | 'thankyou';
67
84
  /**
68
85
  * Offer condition/benefit blocks, as exported by the Map Builder. Both share
69
86
  * the { type, value, description } shape (e.g. condition
@@ -2,8 +2,9 @@
2
2
  * Type surface for the CampaignSpec contract layer.
3
3
  *
4
4
  * The CampaignSpec is the central authoring contract of this repo (see
5
- * ../CONTEXT.md). v4.3 is the authoring shape; v4.2 funnels[] is the canonical
6
- * internal shape that rules operate on (after normalize()).
5
+ * ../CONTEXT.md). v5.0 is the authoring shape; funnels[] is the canonical
6
+ * internal shape that rules operate on (after normalize(), which upgrades a
7
+ * v4.2/v4.3 spec to the v5 page vocabulary — see checkout-flow.ts).
7
8
  *
8
9
  * Types here are intentionally permissive on optional fields — the rule
9
10
  * registry catches missing/malformed fields, rather than the type system
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "package": "@nextcommerce/campaigns-os",
3
- "version": "1.54.0",
3
+ "version": "1.57.0",
4
4
  "status": "developer-preview",
5
5
  "contracts": {
6
- "campaign_spec": "4.2-4.3",
6
+ "campaign_spec": "4.2-5.0",
7
7
  "build_packet": "campaign-runtime-build-packet/v0",
8
8
  "build_context": "campaign-runtime-build-context/v0",
9
9
  "assembly_report": "campaign-runtime-assembly-report/v0",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v1",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": true,
9
9
  "unperformable_check_disposition": "failed",
@@ -106,6 +106,7 @@
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
108
  "campaign-spec/analytics-vocabulary.ts",
109
+ "campaign-spec/checkout-flow.ts",
109
110
  "campaign-spec/index.ts",
110
111
  "campaign-spec/normalize.ts",
111
112
  "campaign-spec/package.json",
@@ -113,7 +114,9 @@
113
114
  "campaign-spec/rules/analytics-contract-shape.ts",
114
115
  "campaign-spec/rules/assembly-hints-shape.ts",
115
116
  "campaign-spec/rules/campaign-metadata.ts",
117
+ "campaign-spec/rules/checkout-forward-target.ts",
116
118
  "campaign-spec/rules/checkout-has-success-url.ts",
119
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
117
120
  "campaign-spec/rules/cycle-detection.ts",
118
121
  "campaign-spec/rules/design-source-shape.ts",
119
122
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -124,9 +127,11 @@
124
127
  "campaign-spec/rules/funnel-weight-sum.ts",
125
128
  "campaign-spec/rules/index.ts",
126
129
  "campaign-spec/rules/offer-ref-integrity.ts",
130
+ "campaign-spec/rules/one-checkout-per-path.ts",
127
131
  "campaign-spec/rules/package-pricing-sanity.ts",
128
132
  "campaign-spec/rules/page-count.ts",
129
133
  "campaign-spec/rules/page-id-uniqueness.ts",
134
+ "campaign-spec/rules/pre-payment-forward-target.ts",
130
135
  "campaign-spec/rules/promo-code-input-validation.ts",
131
136
  "campaign-spec/rules/promo-codes-shape.ts",
132
137
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": "campaigns-os-runtime-recipe/v1",
3
3
  "recipe_kind": "campaigns-os-node-v1",
4
- "recipe_revision": "1.0.2",
4
+ "recipe_revision": "1.0.3",
5
5
  "refusal_reason_code": "runtime_recipe_refused",
6
6
  "fail_closed": true,
7
7
  "unperformable_check_disposition": "failed",
@@ -101,6 +101,7 @@
101
101
  "fingerprint_algorithm": "sha256",
102
102
  "files": [
103
103
  "campaign-spec/analytics-vocabulary.ts",
104
+ "campaign-spec/checkout-flow.ts",
104
105
  "campaign-spec/index.ts",
105
106
  "campaign-spec/normalize.ts",
106
107
  "campaign-spec/package.json",
@@ -108,7 +109,9 @@
108
109
  "campaign-spec/rules/analytics-contract-shape.ts",
109
110
  "campaign-spec/rules/assembly-hints-shape.ts",
110
111
  "campaign-spec/rules/campaign-metadata.ts",
112
+ "campaign-spec/rules/checkout-forward-target.ts",
111
113
  "campaign-spec/rules/checkout-has-success-url.ts",
114
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
112
115
  "campaign-spec/rules/cycle-detection.ts",
113
116
  "campaign-spec/rules/design-source-shape.ts",
114
117
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -119,9 +122,11 @@
119
122
  "campaign-spec/rules/funnel-weight-sum.ts",
120
123
  "campaign-spec/rules/index.ts",
121
124
  "campaign-spec/rules/offer-ref-integrity.ts",
125
+ "campaign-spec/rules/one-checkout-per-path.ts",
122
126
  "campaign-spec/rules/package-pricing-sanity.ts",
123
127
  "campaign-spec/rules/page-count.ts",
124
128
  "campaign-spec/rules/page-id-uniqueness.ts",
129
+ "campaign-spec/rules/pre-payment-forward-target.ts",
125
130
  "campaign-spec/rules/promo-code-input-validation.ts",
126
131
  "campaign-spec/rules/promo-codes-shape.ts",
127
132
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -2,7 +2,7 @@
2
2
  "_note": "Declarative descriptions of the prepared-runtime states an output-check implementation must distinguish. A test builds each state from the accepted recipe rather than from paths written down here, so adding a module to the input set cannot leave a fixture describing an inventory that no longer exists.",
3
3
  "schema": "campaigns-os-runtime-recipe-dist-states/v1",
4
4
  "directory": "campaign-spec/dist",
5
- "expected_file_count": 76,
5
+ "expected_file_count": 86,
6
6
  "states": [
7
7
  {
8
8
  "id": "healthy",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v1",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": false,
9
9
  "unperformable_check_disposition": "failed",
@@ -106,6 +106,7 @@
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
108
  "campaign-spec/analytics-vocabulary.ts",
109
+ "campaign-spec/checkout-flow.ts",
109
110
  "campaign-spec/index.ts",
110
111
  "campaign-spec/normalize.ts",
111
112
  "campaign-spec/package.json",
@@ -113,7 +114,9 @@
113
114
  "campaign-spec/rules/analytics-contract-shape.ts",
114
115
  "campaign-spec/rules/assembly-hints-shape.ts",
115
116
  "campaign-spec/rules/campaign-metadata.ts",
117
+ "campaign-spec/rules/checkout-forward-target.ts",
116
118
  "campaign-spec/rules/checkout-has-success-url.ts",
119
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
117
120
  "campaign-spec/rules/cycle-detection.ts",
118
121
  "campaign-spec/rules/design-source-shape.ts",
119
122
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -124,9 +127,11 @@
124
127
  "campaign-spec/rules/funnel-weight-sum.ts",
125
128
  "campaign-spec/rules/index.ts",
126
129
  "campaign-spec/rules/offer-ref-integrity.ts",
130
+ "campaign-spec/rules/one-checkout-per-path.ts",
127
131
  "campaign-spec/rules/package-pricing-sanity.ts",
128
132
  "campaign-spec/rules/page-count.ts",
129
133
  "campaign-spec/rules/page-id-uniqueness.ts",
134
+ "campaign-spec/rules/pre-payment-forward-target.ts",
130
135
  "campaign-spec/rules/promo-code-input-validation.ts",
131
136
  "campaign-spec/rules/promo-codes-shape.ts",
132
137
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v1",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": true,
9
9
  "unperformable_check_disposition": "failed",
@@ -104,6 +104,7 @@
104
104
  "fingerprint_algorithm": "sha256",
105
105
  "files": [
106
106
  "campaign-spec/analytics-vocabulary.ts",
107
+ "campaign-spec/checkout-flow.ts",
107
108
  "campaign-spec/index.ts",
108
109
  "campaign-spec/normalize.ts",
109
110
  "campaign-spec/package.json",
@@ -111,7 +112,9 @@
111
112
  "campaign-spec/rules/analytics-contract-shape.ts",
112
113
  "campaign-spec/rules/assembly-hints-shape.ts",
113
114
  "campaign-spec/rules/campaign-metadata.ts",
115
+ "campaign-spec/rules/checkout-forward-target.ts",
114
116
  "campaign-spec/rules/checkout-has-success-url.ts",
117
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
115
118
  "campaign-spec/rules/cycle-detection.ts",
116
119
  "campaign-spec/rules/design-source-shape.ts",
117
120
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -122,9 +125,11 @@
122
125
  "campaign-spec/rules/funnel-weight-sum.ts",
123
126
  "campaign-spec/rules/index.ts",
124
127
  "campaign-spec/rules/offer-ref-integrity.ts",
128
+ "campaign-spec/rules/one-checkout-per-path.ts",
125
129
  "campaign-spec/rules/package-pricing-sanity.ts",
126
130
  "campaign-spec/rules/page-count.ts",
127
131
  "campaign-spec/rules/page-id-uniqueness.ts",
132
+ "campaign-spec/rules/pre-payment-forward-target.ts",
128
133
  "campaign-spec/rules/promo-code-input-validation.ts",
129
134
  "campaign-spec/rules/promo-codes-shape.ts",
130
135
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v1",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": true,
9
9
  "unperformable_check_disposition": "failed",
@@ -106,6 +106,7 @@
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
108
  "campaign-spec/analytics-vocabulary.ts",
109
+ "campaign-spec/checkout-flow.ts",
109
110
  "campaign-spec/index.ts",
110
111
  "campaign-spec/normalize.ts",
111
112
  "campaign-spec/package.json",
@@ -113,7 +114,9 @@
113
114
  "campaign-spec/rules/analytics-contract-shape.ts",
114
115
  "campaign-spec/rules/assembly-hints-shape.ts",
115
116
  "campaign-spec/rules/campaign-metadata.ts",
117
+ "campaign-spec/rules/checkout-forward-target.ts",
116
118
  "campaign-spec/rules/checkout-has-success-url.ts",
119
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
117
120
  "campaign-spec/rules/cycle-detection.ts",
118
121
  "campaign-spec/rules/design-source-shape.ts",
119
122
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -124,9 +127,11 @@
124
127
  "campaign-spec/rules/funnel-weight-sum.ts",
125
128
  "campaign-spec/rules/index.ts",
126
129
  "campaign-spec/rules/offer-ref-integrity.ts",
130
+ "campaign-spec/rules/one-checkout-per-path.ts",
127
131
  "campaign-spec/rules/package-pricing-sanity.ts",
128
132
  "campaign-spec/rules/page-count.ts",
129
133
  "campaign-spec/rules/page-id-uniqueness.ts",
134
+ "campaign-spec/rules/pre-payment-forward-target.ts",
130
135
  "campaign-spec/rules/promo-code-input-validation.ts",
131
136
  "campaign-spec/rules/promo-codes-shape.ts",
132
137
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v1",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": true,
9
9
  "unperformable_check_disposition": "failed",
@@ -106,6 +106,7 @@
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
108
  "campaign-spec/analytics-vocabulary.ts",
109
+ "campaign-spec/checkout-flow.ts",
109
110
  "campaign-spec/index.ts",
110
111
  "campaign-spec/normalize.ts",
111
112
  "campaign-spec/package.json",
@@ -113,7 +114,9 @@
113
114
  "campaign-spec/rules/analytics-contract-shape.ts",
114
115
  "campaign-spec/rules/assembly-hints-shape.ts",
115
116
  "campaign-spec/rules/campaign-metadata.ts",
117
+ "campaign-spec/rules/checkout-forward-target.ts",
116
118
  "campaign-spec/rules/checkout-has-success-url.ts",
119
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
117
120
  "campaign-spec/rules/cycle-detection.ts",
118
121
  "campaign-spec/rules/design-source-shape.ts",
119
122
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -124,9 +127,11 @@
124
127
  "campaign-spec/rules/funnel-weight-sum.ts",
125
128
  "campaign-spec/rules/index.ts",
126
129
  "campaign-spec/rules/offer-ref-integrity.ts",
130
+ "campaign-spec/rules/one-checkout-per-path.ts",
127
131
  "campaign-spec/rules/package-pricing-sanity.ts",
128
132
  "campaign-spec/rules/page-count.ts",
129
133
  "campaign-spec/rules/page-id-uniqueness.ts",
134
+ "campaign-spec/rules/pre-payment-forward-target.ts",
130
135
  "campaign-spec/rules/promo-code-input-validation.ts",
131
136
  "campaign-spec/rules/promo-codes-shape.ts",
132
137
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v1",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": true,
9
9
  "unperformable_check_disposition": "failed",
@@ -106,6 +106,7 @@
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
108
  "campaign-spec/analytics-vocabulary.ts",
109
+ "campaign-spec/checkout-flow.ts",
109
110
  "campaign-spec/index.ts",
110
111
  "campaign-spec/normalize.ts",
111
112
  "campaign-spec/package.json",
@@ -113,7 +114,9 @@
113
114
  "campaign-spec/rules/analytics-contract-shape.ts",
114
115
  "campaign-spec/rules/assembly-hints-shape.ts",
115
116
  "campaign-spec/rules/campaign-metadata.ts",
117
+ "campaign-spec/rules/checkout-forward-target.ts",
116
118
  "campaign-spec/rules/checkout-has-success-url.ts",
119
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
117
120
  "campaign-spec/rules/cycle-detection.ts",
118
121
  "campaign-spec/rules/design-source-shape.ts",
119
122
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -124,9 +127,11 @@
124
127
  "campaign-spec/rules/funnel-weight-sum.ts",
125
128
  "campaign-spec/rules/index.ts",
126
129
  "campaign-spec/rules/offer-ref-integrity.ts",
130
+ "campaign-spec/rules/one-checkout-per-path.ts",
127
131
  "campaign-spec/rules/package-pricing-sanity.ts",
128
132
  "campaign-spec/rules/page-count.ts",
129
133
  "campaign-spec/rules/page-id-uniqueness.ts",
134
+ "campaign-spec/rules/pre-payment-forward-target.ts",
130
135
  "campaign-spec/rules/promo-code-input-validation.ts",
131
136
  "campaign-spec/rules/promo-codes-shape.ts",
132
137
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v1",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": true,
9
9
  "unperformable_check_disposition": "failed",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v2",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": true,
9
9
  "unperformable_check_disposition": "failed",
@@ -106,6 +106,7 @@
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
108
  "campaign-spec/analytics-vocabulary.ts",
109
+ "campaign-spec/checkout-flow.ts",
109
110
  "campaign-spec/index.ts",
110
111
  "campaign-spec/normalize.ts",
111
112
  "campaign-spec/package.json",
@@ -113,7 +114,9 @@
113
114
  "campaign-spec/rules/analytics-contract-shape.ts",
114
115
  "campaign-spec/rules/assembly-hints-shape.ts",
115
116
  "campaign-spec/rules/campaign-metadata.ts",
117
+ "campaign-spec/rules/checkout-forward-target.ts",
116
118
  "campaign-spec/rules/checkout-has-success-url.ts",
119
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
117
120
  "campaign-spec/rules/cycle-detection.ts",
118
121
  "campaign-spec/rules/design-source-shape.ts",
119
122
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -124,9 +127,11 @@
124
127
  "campaign-spec/rules/funnel-weight-sum.ts",
125
128
  "campaign-spec/rules/index.ts",
126
129
  "campaign-spec/rules/offer-ref-integrity.ts",
130
+ "campaign-spec/rules/one-checkout-per-path.ts",
127
131
  "campaign-spec/rules/package-pricing-sanity.ts",
128
132
  "campaign-spec/rules/page-count.ts",
129
133
  "campaign-spec/rules/page-id-uniqueness.ts",
134
+ "campaign-spec/rules/pre-payment-forward-target.ts",
130
135
  "campaign-spec/rules/promo-code-input-validation.ts",
131
136
  "campaign-spec/rules/promo-codes-shape.ts",
132
137
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v1",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": true,
9
9
  "unperformable_check_disposition": "failed",
@@ -106,6 +106,7 @@
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
108
  "campaign-spec/analytics-vocabulary.ts",
109
+ "campaign-spec/checkout-flow.ts",
109
110
  "campaign-spec/index.ts",
110
111
  "campaign-spec/normalize.ts",
111
112
  "campaign-spec/package.json",
@@ -113,7 +114,9 @@
113
114
  "campaign-spec/rules/analytics-contract-shape.ts",
114
115
  "campaign-spec/rules/assembly-hints-shape.ts",
115
116
  "campaign-spec/rules/campaign-metadata.ts",
117
+ "campaign-spec/rules/checkout-forward-target.ts",
116
118
  "campaign-spec/rules/checkout-has-success-url.ts",
119
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
117
120
  "campaign-spec/rules/cycle-detection.ts",
118
121
  "campaign-spec/rules/design-source-shape.ts",
119
122
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -124,9 +127,11 @@
124
127
  "campaign-spec/rules/funnel-weight-sum.ts",
125
128
  "campaign-spec/rules/index.ts",
126
129
  "campaign-spec/rules/offer-ref-integrity.ts",
130
+ "campaign-spec/rules/one-checkout-per-path.ts",
127
131
  "campaign-spec/rules/package-pricing-sanity.ts",
128
132
  "campaign-spec/rules/page-count.ts",
129
133
  "campaign-spec/rules/page-id-uniqueness.ts",
134
+ "campaign-spec/rules/pre-payment-forward-target.ts",
130
135
  "campaign-spec/rules/promo-code-input-validation.ts",
131
136
  "campaign-spec/rules/promo-codes-shape.ts",
132
137
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -3,7 +3,7 @@
3
3
  "_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
4
4
  "schema": "campaigns-os-runtime-recipe/v1",
5
5
  "recipe_kind": "campaigns-os-node-v1",
6
- "recipe_revision": "1.0.2",
6
+ "recipe_revision": "1.0.3",
7
7
  "refusal_reason_code": "runtime_recipe_refused",
8
8
  "fail_closed": true,
9
9
  "unperformable_check_disposition": "failed",
@@ -106,6 +106,7 @@
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
108
  "campaign-spec/analytics-vocabulary.ts",
109
+ "campaign-spec/checkout-flow.ts",
109
110
  "campaign-spec/index.ts",
110
111
  "campaign-spec/normalize.ts",
111
112
  "campaign-spec/package.json",
@@ -113,7 +114,9 @@
113
114
  "campaign-spec/rules/analytics-contract-shape.ts",
114
115
  "campaign-spec/rules/assembly-hints-shape.ts",
115
116
  "campaign-spec/rules/campaign-metadata.ts",
117
+ "campaign-spec/rules/checkout-forward-target.ts",
116
118
  "campaign-spec/rules/checkout-has-success-url.ts",
119
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
117
120
  "campaign-spec/rules/cycle-detection.ts",
118
121
  "campaign-spec/rules/design-source-shape.ts",
119
122
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -124,9 +127,11 @@
124
127
  "campaign-spec/rules/funnel-weight-sum.ts",
125
128
  "campaign-spec/rules/index.ts",
126
129
  "campaign-spec/rules/offer-ref-integrity.ts",
130
+ "campaign-spec/rules/one-checkout-per-path.ts",
127
131
  "campaign-spec/rules/package-pricing-sanity.ts",
128
132
  "campaign-spec/rules/page-count.ts",
129
133
  "campaign-spec/rules/page-id-uniqueness.ts",
134
+ "campaign-spec/rules/pre-payment-forward-target.ts",
130
135
  "campaign-spec/rules/promo-code-input-validation.ts",
131
136
  "campaign-spec/rules/promo-codes-shape.ts",
132
137
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",
@@ -106,6 +106,7 @@
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
108
  "campaign-spec/analytics-vocabulary.ts",
109
+ "campaign-spec/checkout-flow.ts",
109
110
  "campaign-spec/index.ts",
110
111
  "campaign-spec/normalize.ts",
111
112
  "campaign-spec/package.json",
@@ -113,7 +114,9 @@
113
114
  "campaign-spec/rules/analytics-contract-shape.ts",
114
115
  "campaign-spec/rules/assembly-hints-shape.ts",
115
116
  "campaign-spec/rules/campaign-metadata.ts",
117
+ "campaign-spec/rules/checkout-forward-target.ts",
116
118
  "campaign-spec/rules/checkout-has-success-url.ts",
119
+ "campaign-spec/rules/checkout-step-reaches-checkout.ts",
117
120
  "campaign-spec/rules/cycle-detection.ts",
118
121
  "campaign-spec/rules/design-source-shape.ts",
119
122
  "campaign-spec/rules/downsell-without-upsell.ts",
@@ -124,9 +127,11 @@
124
127
  "campaign-spec/rules/funnel-weight-sum.ts",
125
128
  "campaign-spec/rules/index.ts",
126
129
  "campaign-spec/rules/offer-ref-integrity.ts",
130
+ "campaign-spec/rules/one-checkout-per-path.ts",
127
131
  "campaign-spec/rules/package-pricing-sanity.ts",
128
132
  "campaign-spec/rules/page-count.ts",
129
133
  "campaign-spec/rules/page-id-uniqueness.ts",
134
+ "campaign-spec/rules/pre-payment-forward-target.ts",
130
135
  "campaign-spec/rules/promo-code-input-validation.ts",
131
136
  "campaign-spec/rules/promo-codes-shape.ts",
132
137
  "campaign-spec/rules/route-field-ignored-for-page-type.ts",