@nextcommerce/campaigns-os 1.33.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 (310) hide show
  1. package/AGENTS.md +204 -0
  2. package/CHANGELOG.md +5002 -0
  3. package/CONTEXT.md +685 -0
  4. package/LICENSE +202 -0
  5. package/NOTICE +4 -0
  6. package/README.md +368 -0
  7. package/agents/claude/CLAUDE.md +32 -0
  8. package/agents/codex/AGENTS.md +27 -0
  9. package/agents/copilot/copilot-instructions.md +14 -0
  10. package/agents/cursor/campaigns-os.mdc +13 -0
  11. package/bin/campaigns-os.mjs +38 -0
  12. package/campaign-spec/README.md +138 -0
  13. package/campaign-spec/dist/analytics-vocabulary.d.ts +47 -0
  14. package/campaign-spec/dist/analytics-vocabulary.js +74 -0
  15. package/campaign-spec/dist/index.d.ts +40 -0
  16. package/campaign-spec/dist/index.js +77 -0
  17. package/campaign-spec/dist/normalize.d.ts +22 -0
  18. package/campaign-spec/dist/normalize.js +41 -0
  19. package/campaign-spec/dist/routing.d.ts +190 -0
  20. package/campaign-spec/dist/routing.js +263 -0
  21. package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +42 -0
  22. package/campaign-spec/dist/rules/analytics-contract-shape.js +303 -0
  23. package/campaign-spec/dist/rules/assembly-hints-shape.d.ts +42 -0
  24. package/campaign-spec/dist/rules/assembly-hints-shape.js +191 -0
  25. package/campaign-spec/dist/rules/campaign-metadata.d.ts +14 -0
  26. package/campaign-spec/dist/rules/campaign-metadata.js +40 -0
  27. package/campaign-spec/dist/rules/checkout-has-success-url.d.ts +31 -0
  28. package/campaign-spec/dist/rules/checkout-has-success-url.js +66 -0
  29. package/campaign-spec/dist/rules/cycle-detection.d.ts +12 -0
  30. package/campaign-spec/dist/rules/cycle-detection.js +141 -0
  31. package/campaign-spec/dist/rules/design-source-shape.d.ts +29 -0
  32. package/campaign-spec/dist/rules/design-source-shape.js +142 -0
  33. package/campaign-spec/dist/rules/downsell-without-upsell.d.ts +11 -0
  34. package/campaign-spec/dist/rules/downsell-without-upsell.js +40 -0
  35. package/campaign-spec/dist/rules/exit-intent-validation.d.ts +23 -0
  36. package/campaign-spec/dist/rules/exit-intent-validation.js +147 -0
  37. package/campaign-spec/dist/rules/funnel-count.d.ts +9 -0
  38. package/campaign-spec/dist/rules/funnel-count.js +37 -0
  39. package/campaign-spec/dist/rules/funnel-hypothesis-length.d.ts +22 -0
  40. package/campaign-spec/dist/rules/funnel-hypothesis-length.js +60 -0
  41. package/campaign-spec/dist/rules/funnel-identity.d.ts +15 -0
  42. package/campaign-spec/dist/rules/funnel-identity.js +57 -0
  43. package/campaign-spec/dist/rules/funnel-weight-sum.d.ts +20 -0
  44. package/campaign-spec/dist/rules/funnel-weight-sum.js +66 -0
  45. package/campaign-spec/dist/rules/index.d.ts +69 -0
  46. package/campaign-spec/dist/rules/index.js +131 -0
  47. package/campaign-spec/dist/rules/offer-ref-integrity.d.ts +13 -0
  48. package/campaign-spec/dist/rules/offer-ref-integrity.js +59 -0
  49. package/campaign-spec/dist/rules/package-pricing-sanity.d.ts +12 -0
  50. package/campaign-spec/dist/rules/package-pricing-sanity.js +42 -0
  51. package/campaign-spec/dist/rules/page-count.d.ts +11 -0
  52. package/campaign-spec/dist/rules/page-count.js +31 -0
  53. package/campaign-spec/dist/rules/page-id-uniqueness.d.ts +14 -0
  54. package/campaign-spec/dist/rules/page-id-uniqueness.js +47 -0
  55. package/campaign-spec/dist/rules/promo-code-input-validation.d.ts +8 -0
  56. package/campaign-spec/dist/rules/promo-code-input-validation.js +126 -0
  57. package/campaign-spec/dist/rules/promo-codes-shape.d.ts +30 -0
  58. package/campaign-spec/dist/rules/promo-codes-shape.js +187 -0
  59. package/campaign-spec/dist/rules/route-field-ignored-for-page-type.d.ts +33 -0
  60. package/campaign-spec/dist/rules/route-field-ignored-for-page-type.js +81 -0
  61. package/campaign-spec/dist/rules/route-target-resolves.d.ts +32 -0
  62. package/campaign-spec/dist/rules/route-target-resolves.js +112 -0
  63. package/campaign-spec/dist/rules/schema-version.d.ts +21 -0
  64. package/campaign-spec/dist/rules/schema-version.js +53 -0
  65. package/campaign-spec/dist/rules/sdk-version.d.ts +26 -0
  66. package/campaign-spec/dist/rules/sdk-version.js +96 -0
  67. package/campaign-spec/dist/rules/shipping-countries-shape.d.ts +10 -0
  68. package/campaign-spec/dist/rules/shipping-countries-shape.js +30 -0
  69. package/campaign-spec/dist/rules/shipping-methods-present.d.ts +10 -0
  70. package/campaign-spec/dist/rules/shipping-methods-present.js +26 -0
  71. package/campaign-spec/dist/rules/store-profile-shape.d.ts +30 -0
  72. package/campaign-spec/dist/rules/store-profile-shape.js +127 -0
  73. package/campaign-spec/dist/rules/thank-you-requirement.d.ts +16 -0
  74. package/campaign-spec/dist/rules/thank-you-requirement.js +50 -0
  75. package/campaign-spec/dist/rules/unknown-top-level-fields.d.ts +28 -0
  76. package/campaign-spec/dist/rules/unknown-top-level-fields.js +114 -0
  77. package/campaign-spec/dist/rules/upsell-has-packages.d.ts +9 -0
  78. package/campaign-spec/dist/rules/upsell-has-packages.js +35 -0
  79. package/campaign-spec/dist/rules/upsell-routing-complete.d.ts +10 -0
  80. package/campaign-spec/dist/rules/upsell-routing-complete.js +45 -0
  81. package/campaign-spec/dist/rules/upsell-without-checkout.d.ts +11 -0
  82. package/campaign-spec/dist/rules/upsell-without-checkout.js +44 -0
  83. package/campaign-spec/dist/rules/variant-labels-shape.d.ts +28 -0
  84. package/campaign-spec/dist/rules/variant-labels-shape.js +86 -0
  85. package/campaign-spec/dist/sdk-version-parse.d.ts +43 -0
  86. package/campaign-spec/dist/sdk-version-parse.js +62 -0
  87. package/campaign-spec/dist/types.d.ts +674 -0
  88. package/campaign-spec/dist/types.js +40 -0
  89. package/campaign-spec/package.json +12 -0
  90. package/compatibility.json +25 -0
  91. package/contracts/agent-relevant-change-policy.v1.json +111 -0
  92. package/contracts/brand-theme-source-defaults.figma-sections-export.v0.json +32 -0
  93. package/contracts/brand-theme-target-tokens.next-core.v0.json +65 -0
  94. package/contracts/campaign-cart-checkout-field-contract.v0.json +45 -0
  95. package/contracts/campaign-cart-sdk-support-policy.v0.json +11 -0
  96. package/contracts/commerce-surface-catalog.json +2452 -0
  97. package/contracts/fixtures/orientation/canonicalization/v1.json +34 -0
  98. package/contracts/fixtures/orientation/envelope/current.json +95 -0
  99. package/contracts/fixtures/orientation/envelope/freshness_unknown.json +97 -0
  100. package/contracts/fixtures/orientation/envelope/legacy_baseline.json +95 -0
  101. package/contracts/fixtures/orientation/envelope/orientation_available.json +136 -0
  102. package/contracts/fixtures/orientation/envelope/recovered_interrupted_update.json +136 -0
  103. package/contracts/fixtures/orientation/envelope/refused.json +100 -0
  104. package/contracts/fixtures/orientation/envelope/restart_required.json +137 -0
  105. package/contracts/fixtures/orientation/envelope/updated.json +135 -0
  106. package/contracts/fixtures/orientation/hostile-target/README.md +61 -0
  107. package/contracts/fixtures/orientation/hostile-target/manifest.json +82 -0
  108. package/contracts/fixtures/orientation/hostile-target/repo/CHANGELOG.md +18 -0
  109. package/contracts/fixtures/orientation/hostile-target/repo/bin/intended.mjs +12 -0
  110. package/contracts/fixtures/orientation/hostile-target/repo/bin/tripwire.mjs +15 -0
  111. package/contracts/fixtures/orientation/hostile-target/repo/contracts/release-ledger.json +48 -0
  112. package/contracts/fixtures/orientation/hostile-target/repo/contracts/supported-surface.json +17 -0
  113. package/contracts/fixtures/orientation/hostile-target/repo/docs/example-contract.md +13 -0
  114. package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-checkout +5 -0
  115. package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-merge +5 -0
  116. package/contracts/fixtures/orientation/hostile-target/repo/hooks/pre-commit +5 -0
  117. package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/package.json +15 -0
  118. package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/tripwire.mjs +9 -0
  119. package/contracts/fixtures/orientation/hostile-target/repo/package.json +20 -0
  120. package/contracts/fixtures/orientation/hostile-target/repo/schemas/example.v0.schema.json +15 -0
  121. package/contracts/fixtures/orientation/release-gate/cases.json +1073 -0
  122. package/contracts/fixtures/runtime-recipe/accept/current.json +299 -0
  123. package/contracts/fixtures/runtime-recipe/accept/minimal.json +294 -0
  124. package/contracts/fixtures/runtime-recipe/dist-states.json +51 -0
  125. package/contracts/fixtures/runtime-recipe/manifest.json +85 -0
  126. package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +299 -0
  127. package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +297 -0
  128. package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +299 -0
  129. package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +299 -0
  130. package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +299 -0
  131. package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +251 -0
  132. package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +299 -0
  133. package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +299 -0
  134. package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +310 -0
  135. package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +299 -0
  136. package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +299 -0
  137. package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +299 -0
  138. package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +299 -0
  139. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/assembly-report.json +180 -0
  140. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/build-context.json +115 -0
  141. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/doctor-output.json +29 -0
  142. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/qa-verdict.json +27 -0
  143. package/contracts/fixtures/sidecar-bundle/production-shaped/campaign-runtime.build.json +141 -0
  144. package/contracts/migration-sidecar-bundle.v0.json +149 -0
  145. package/contracts/orientation-limits.v1.json +41 -0
  146. package/contracts/orientation-reason-codes.v1.json +196 -0
  147. package/contracts/private-template-sources.json +8 -0
  148. package/contracts/release-ledger.json +4598 -0
  149. package/contracts/reserved-skill-names.json +13 -0
  150. package/contracts/runtime-recipe.campaigns-os-node-v1.json +299 -0
  151. package/contracts/supported-surface.json +180 -0
  152. package/contracts/template-brand-contract.apollo-mv-single-step.v0.json +27 -0
  153. package/contracts/template-brand-contract.apollo.v0.json +27 -0
  154. package/contracts/template-brand-contract.demeter.v0.json +27 -0
  155. package/contracts/template-brand-contract.olympus-mv-single-step.v0.json +27 -0
  156. package/contracts/template-brand-contract.olympus-mv-two-step.v0.json +28 -0
  157. package/contracts/template-brand-contract.olympus.v0.json +27 -0
  158. package/contracts/template-brand-contract.shared-commerce.v0.json +190 -0
  159. package/contracts/template-brand-contract.shop-single-step.v0.json +27 -0
  160. package/contracts/template-brand-contract.shop-three-step.v0.json +29 -0
  161. package/contracts/template-slot-manifest.apollo-mv-single-step.v0.json +14 -0
  162. package/contracts/template-slot-manifest.apollo.v0.json +12 -0
  163. package/contracts/template-slot-manifest.demeter.v0.json +30 -0
  164. package/contracts/template-slot-manifest.olympus-mv-single-step.v0.json +14 -0
  165. package/contracts/template-slot-manifest.olympus-mv-two-step.v0.json +15 -0
  166. package/contracts/template-slot-manifest.olympus.v0.json +12 -0
  167. package/contracts/template-slot-manifest.shared-content-core.v0.json +4254 -0
  168. package/contracts/template-slot-manifest.shop-single-step.v0.json +47 -0
  169. package/contracts/template-slot-manifest.shop-three-step.v0.json +33 -0
  170. package/docs/brand-theme-bridge.md +159 -0
  171. package/docs/build-packet.md +1300 -0
  172. package/docs/campaign-build-brief.md +145 -0
  173. package/docs/campaign-standardization-report.md +329 -0
  174. package/docs/campaigns-os-build-flow.md +117 -0
  175. package/docs/design-source-package.md +784 -0
  176. package/docs/legacy-migration.md +58 -0
  177. package/docs/migration-sidecar-bundle.md +139 -0
  178. package/docs/orientation-contract-reference.md +1220 -0
  179. package/docs/polish-evidence.md +502 -0
  180. package/docs/qa-and-test-orders.md +1691 -0
  181. package/docs/release-ledger-authoring-guide.md +274 -0
  182. package/docs/runtime-readiness.md +211 -0
  183. package/docs/supported-surface.md +83 -0
  184. package/docs/versioning.md +55 -0
  185. package/docs/workflow-findings-sidecar.md +588 -0
  186. package/package.json +135 -0
  187. package/prompts/first-build.md +27 -0
  188. package/prompts/friction-log.md +30 -0
  189. package/schemas/campaign-build-brief.v1.schema.json +149 -0
  190. package/schemas/campaign-design-source-package.v0.schema.json +697 -0
  191. package/schemas/campaign-runtime-assembly-report.v0.schema.json +390 -0
  192. package/schemas/campaign-runtime-build-context.v0.schema.json +339 -0
  193. package/schemas/campaign-runtime-build-packet.v0.schema.json +452 -0
  194. package/schemas/campaign-spec.v4.schema.json +582 -0
  195. package/schemas/campaigns-os-doctor-output.v0.schema.json +43 -0
  196. package/schemas/campaigns-os-legacy-migration-inventory.v0.schema.json +112 -0
  197. package/schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json +38 -0
  198. package/schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json +57 -0
  199. package/schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json +150 -0
  200. package/schemas/campaigns-os-qa-verdict.v0.schema.json +438 -0
  201. package/schemas/campaigns-os-release-ledger.v1.schema.json +162 -0
  202. package/schemas/campaigns-os-run-record.v0.schema.json +343 -0
  203. package/schemas/campaigns-os-runtime-recipe.v1.schema.json +313 -0
  204. package/schemas/campaigns-os-sidecar-bundle-conformance.v0.schema.json +78 -0
  205. package/schemas/campaigns-os-tooling-orientation.v1.schema.json +381 -0
  206. package/schemas/campaigns-os-workflow-finding.v0.schema.json +134 -0
  207. package/schemas/source-html-manifest.v0.schema.json +252 -0
  208. package/skills/next-campaigns-build/SKILL.md +73 -0
  209. package/skills/next-campaigns-os/SKILL.md +101 -0
  210. package/skills/next-campaigns-os/references/session-intake.md +160 -0
  211. package/skills/next-campaigns-os-setup/SKILL.md +22 -0
  212. package/skills/next-campaigns-polish/SKILL.md +145 -0
  213. package/skills/next-campaigns-qa/SKILL.md +92 -0
  214. package/skills.json +56 -0
  215. package/skills.sh +64 -0
  216. package/src/adapter-decision-contract.mjs +333 -0
  217. package/src/brand-theme.mjs +1151 -0
  218. package/src/browser-launch.mjs +79 -0
  219. package/src/build-brief.mjs +781 -0
  220. package/src/built-site-scope.mjs +312 -0
  221. package/src/campaign-ecosystem.mjs +734 -0
  222. package/src/campaign-identity.mjs +405 -0
  223. package/src/campaign-workspace.mjs +127 -0
  224. package/src/checkpoint-waiver.mjs +302 -0
  225. package/src/cli.mjs +13442 -0
  226. package/src/commercial-journey.mjs +1119 -0
  227. package/src/commercial-parity.mjs +965 -0
  228. package/src/consent.mjs +347 -0
  229. package/src/content-residue.mjs +322 -0
  230. package/src/deadline.mjs +81 -0
  231. package/src/design-source-package.mjs +2604 -0
  232. package/src/deviation.mjs +107 -0
  233. package/src/doctor-check-registry.mjs +49 -0
  234. package/src/doctor-sidecar.mjs +106 -0
  235. package/src/finding-cause.mjs +557 -0
  236. package/src/findings.mjs +326 -0
  237. package/src/fs-identity.mjs +68 -0
  238. package/src/gate-actions.mjs +105 -0
  239. package/src/html-scan.mjs +53 -0
  240. package/src/install-mode.mjs +272 -0
  241. package/src/legacy-migration.d.ts +128 -0
  242. package/src/legacy-migration.mjs +510 -0
  243. package/src/lifecycle.mjs +338 -0
  244. package/src/local-proof.mjs +401 -0
  245. package/src/map-pin-writeback.mjs +210 -0
  246. package/src/orchestration-stage-contract.mjs +81 -0
  247. package/src/package-install-fixture.mjs +33 -0
  248. package/src/page-kit-build-summary.mjs +175 -0
  249. package/src/page-kit-campaign-config.mjs +57 -0
  250. package/src/page-kit-sdk-version.mjs +392 -0
  251. package/src/page-kit-store-profile.mjs +369 -0
  252. package/src/page-kit-sync.mjs +162 -0
  253. package/src/polish-browser.mjs +867 -0
  254. package/src/polish-capture.mjs +1094 -0
  255. package/src/polish-deadline.mjs +51 -0
  256. package/src/polish-gate.mjs +739 -0
  257. package/src/polish-node.mjs +639 -0
  258. package/src/polish-page-load.mjs +1100 -0
  259. package/src/private-template-source.mjs +237 -0
  260. package/src/proof-policy.mjs +82 -0
  261. package/src/qa-analytics-correctness.mjs +307 -0
  262. package/src/qa-analytics-errors.mjs +38 -0
  263. package/src/qa-analytics-parity.mjs +699 -0
  264. package/src/qa-binding-evidence.mjs +140 -0
  265. package/src/qa-browser.mjs +6608 -0
  266. package/src/qa-cart-entry.mjs +406 -0
  267. package/src/qa-commercial-parity.mjs +641 -0
  268. package/src/qa-node.mjs +3620 -0
  269. package/src/qa-order-bump.mjs +381 -0
  270. package/src/qa-parity-capture.mjs +428 -0
  271. package/src/qa-parity-fixture.mjs +359 -0
  272. package/src/qa-publish.mjs +362 -0
  273. package/src/qa-purchase-data-layer.mjs +263 -0
  274. package/src/qa-route-probe.mjs +272 -0
  275. package/src/qa-sidecar.mjs +188 -0
  276. package/src/qa-test-order-topology.mjs +207 -0
  277. package/src/qa-url-privacy.mjs +13 -0
  278. package/src/qa-verdict-discovery.mjs +192 -0
  279. package/src/qa-verdict-publish.mjs +105 -0
  280. package/src/qa-verdict.mjs +287 -0
  281. package/src/remit.mjs +388 -0
  282. package/src/repo-scan.mjs +83 -0
  283. package/src/route-identity.mjs +133 -0
  284. package/src/run-record-closeout.mjs +229 -0
  285. package/src/run-record.mjs +839 -0
  286. package/src/run-session.mjs +226 -0
  287. package/src/runtime-state-ignore.mjs +113 -0
  288. package/src/sdk-attribute-index.mjs +212 -0
  289. package/src/sdk-markup.mjs +358 -0
  290. package/src/sdk-meta-tags.mjs +52 -0
  291. package/src/shell-token.mjs +7 -0
  292. package/src/sidecar-bundle.mjs +399 -0
  293. package/src/source-asset-crawl.mjs +469 -0
  294. package/src/source-html-intake.mjs +627 -0
  295. package/src/source-html-manifest.mjs +276 -0
  296. package/src/source-prep.mjs +284 -0
  297. package/src/spec-derive-store.mjs +431 -0
  298. package/src/spec-derive.mjs +514 -0
  299. package/src/spec-fetch.mjs +66 -0
  300. package/src/spec-hash.mjs +48 -0
  301. package/src/spec-identity.mjs +27 -0
  302. package/src/stage-ledger.mjs +509 -0
  303. package/src/standardization-report.mjs +1297 -0
  304. package/src/template-brand-contract.mjs +473 -0
  305. package/src/template-freshness.mjs +196 -0
  306. package/src/template-reference.mjs +81 -0
  307. package/src/template-slot-manifest.mjs +150 -0
  308. package/src/text-safety.mjs +62 -0
  309. package/src/theme-gate.mjs +185 -0
  310. package/src/upsell-selector-scope.mjs +299 -0
@@ -0,0 +1,14 @@
1
+ # Campaigns OS Instructions
2
+
3
+ When this repository contains Campaigns OS artifacts, use them as the build handoff:
4
+
5
+ - `campaign-runtime.build.json` defines the CampaignSpec, source adapter, target output, template family, deploy target, SDK origin state, and QA proof depth.
6
+ - `.campaign-runtime/build-context.json` records page mappings and setup/build handoff details.
7
+ - `.campaign-runtime/assembly-report.json` records stage evidence and blockers.
8
+ - `.campaign-runtime/theme/theme-report.json`, when present, is optional brand-theme evidence. Generated `brand-theme.css` must load after `next-core.css`; missing or low-confidence theme is a warning/skipped reason, not permission to edit SDK-owned runtime surfaces.
9
+
10
+ CampaignSpec validation is owned by the public `@nextcommerce/campaigns-os/campaign-spec` rules surfaced through doctor `spec.validation` findings; use structured rule/path detail when available.
11
+
12
+ Preserve Campaign Cart SDK-owned commerce surfaces. Replace starter demo refs from CampaignSpec/API. Prepared AI/exported HTML must be converted into page-kit-ready source first: keep page-owned body markup, strip document wrappers, add YAML frontmatter, move shared CSS/assets into the campaign structure, and use Liquid helpers only for page-kit links/assets/includes. Landing/presell pages can preserve source design; checkout/upsell/downsell/receipt should use starter-template commerce surfaces as SDK contract references while campaign/source owns visual chrome. Copy starter template families atomically with dependent pages, `_includes/`, `_layouts/`, `assets/css/`, and `assets/js/`; do not copy only checkout/receipt pages. Do not claim launch readiness until build, polish, deploy, and QA evidence are recorded.
13
+
14
+ Before Polish becomes terminal or hands off to deploy/QA, install the package-owned Playwright browser with `npm run qa:install-browser`, serve the current build, and run `campaigns-os polish capture --packet campaign-runtime.build.json --base-url <served-current-build-url>`. After that checkpoint clears, QA must use the Campaigns OS Node/npm runner: run `campaigns-os qa resolve`, then run `campaigns-os qa run --browser --test-order common` against the tested URL. Typed-card test-order proof must use `campaigns-os qa run --test-order <common|checkout|decline|accept|both|full|explicit-path>` through the tested checkout and rendered upsell controls; global test cards bypass the gateway and create no transactions, so no permission/approval is needed. `common` runs the checkout baseline, first-offer accept and decline, and a deduplicated shortest real receipt path when that adds coverage (at most four orders). `full` walks every actual terminal path in the selected checkout topology; cycles, missing routes, and reachable nonterminals block exhaustive proof before browser launch. The accidental-flood cap remains `6`, and an overflow names the exact explicit `--max-test-orders` raise. Analytics correctness is two-phase: the campaign-root visit inventories declared providers/tags only, and the same canonical typed-card run proves Purchase only from the topology-recognized final receipt document after `--analytics-settle`. Missing/unrecognized receipts are manual-review warnings; recognized receipt no-signal and capture/settle errors block. Only the genuine recognized-receipt/no-signal failure is waivable; capture/settle errors are not. Checkout/upsell signals remain whole-journey parity evidence and cannot satisfy a silent receipt. Do not use `next.getCartData().cartLines` as cart-populated proof; use typed-card order read-back, `cart:updated` payload `items` / `summary.lines`, and rendered bundle DOM evidence. Localhost on any port is a Campaigns App Development domain for SDK QA with analytics suppressed; non-localhost preview/production origins still need SDK origin allowlist confirmation. Do not use external browser skills, SDK test-mode events, or direct backend orders as launch proof. Campaigns OS proof is not merchant launch readiness; before launch, confirm production storefront URL, live payment methods, shipping markets, legal/support URLs, analytics expectations, and merchant-side configuration.
@@ -0,0 +1,13 @@
1
+ ---
2
+ description: Campaigns OS target campaign guidance
3
+ globs:
4
+ - "campaign-runtime.build.json"
5
+ - ".campaign-runtime/**/*.json"
6
+ alwaysApply: false
7
+ ---
8
+
9
+ Read Campaigns OS artifacts before editing campaign pages. Treat CampaignSpec/API values as live commerce truth, starter-template contracts as SDK surface truth, and designed HTML/assets as visual/content intent. Treat CampaignSpec validation as owned by the public `@nextcommerce/campaigns-os/campaign-spec` rules surfaced through doctor `spec.validation` findings; use structured rule/path detail when available. If `context.theme` or `.campaign-runtime/theme/theme-report.json` exists, use it as optional brand-theme evidence; generated `brand-theme.css` must load after `next-core.css`, and missing/low-confidence theme is a warning or skipped reason, not permission to edit SDK-owned runtime surfaces.
10
+
11
+ Do not carry over demo package, shipping, voucher, payment, tracking, footer, or SEO values. Prepared AI/exported HTML must be converted into page-kit-ready source first: keep page-owned body markup, strip document wrappers, add YAML frontmatter, move shared CSS/assets into the campaign structure, and use Liquid helpers only for page-kit links/assets/includes. Preserve prepared landing/presell source HTML when it is a real standalone design. For checkout/upsell/downsell/receipt, use starter-template commerce surfaces as SDK contract references while campaign/source owns visual chrome. Copy starter template families atomically with dependent pages, `_includes/`, `_layouts/`, `assets/css/`, and `assets/js/`; do not copy only checkout/receipt pages. Emit SDK routing meta tags as campaign-root paths such as `/campaign-slug/upsell/`. Preserve SDK-owned checkout/cart/upsell/receipt surfaces. For `shop-three-step`, shipping is dynamic via `window.next.getShippingMethods()`.
12
+
13
+ Before Polish becomes terminal or hands off to deploy/QA, install the package-owned Playwright browser with `npm run qa:install-browser`, serve the current build, and run `campaigns-os polish capture --packet campaign-runtime.build.json --base-url <served-current-build-url>`. After that checkpoint clears, QA uses the Campaigns OS Node/npm runner: `campaigns-os qa resolve`, then `campaigns-os qa run --browser --test-order common` against the tested URL. Test-order proof uses `campaigns-os qa run --test-order <common|checkout|decline|accept|both|full|explicit-path>` through the tested checkout and rendered upsell controls; global test cards bypass the gateway and create no transactions, so no permission/approval is needed. `common` runs the checkout baseline, first-offer accept and decline, and a deduplicated shortest real receipt path when that adds coverage (at most four orders). `full` walks every actual terminal path in the selected checkout topology; cycles, missing routes, and reachable nonterminals block exhaustive proof before browser launch. The accidental-flood cap remains `6`, and an overflow names the exact explicit `--max-test-orders` raise. Analytics correctness is two-phase: the campaign-root visit inventories declared providers/tags only, and the same canonical typed-card run proves Purchase only from the topology-recognized final receipt document after `--analytics-settle`. Missing/unrecognized receipts are manual-review warnings; recognized receipt no-signal and capture/settle errors block. Only the genuine recognized-receipt/no-signal failure is waivable; capture/settle errors are not. Checkout/upsell signals remain whole-journey parity evidence and cannot satisfy a silent receipt. Do not use `next.getCartData().cartLines` as cart-populated proof; use typed-card order read-back, `cart:updated` payload `items` / `summary.lines`, and rendered bundle DOM evidence. Localhost on any port is a Campaigns App Development domain for SDK QA with analytics suppressed; non-localhost preview/production origins still need SDK origin allowlist confirmation. External browser skills, SDK test-mode events, and direct backend orders are not launch proof. Campaigns OS proof is not merchant launch readiness; before launch, confirm production storefront URL, live payment methods, shipping markets, legal/support URLs, analytics expectations, and merchant-side configuration.
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/cli.mjs";
4
+
5
+ // Filesystem errno codes worth a friendly, path-named message instead of the
6
+ // raw Node error string. Each maps `where` (": <path>" when known, else "")
7
+ // to a full message. Only ENOENT gets the "run start first" packet hint — for
8
+ // permission/type errors that guidance would be wrong, and EACCES is not
9
+ // read-specific (a write to an unwritable target raises it too).
10
+ const FS_ERRNO_MESSAGE = {
11
+ ENOENT: (where) =>
12
+ `file not found${where}. Check the path; ` +
13
+ "run `campaigns-os start ...` first if you have not generated the packet yet.",
14
+ EACCES: (where) => `permission denied accessing${where}. Check the file's permissions.`,
15
+ EPERM: (where) => `operation not permitted on${where}. Check the file's permissions.`,
16
+ EISDIR: (where) => `expected a file but found a directory${where}. Check the path.`,
17
+ ENOTDIR: (where) => `a path segment is not a directory${where}. Check the path.`,
18
+ };
19
+
20
+ main(process.argv.slice(2)).catch((error) => {
21
+ // Rewrite raw Node filesystem errno errors (e.g. a mistyped or unreadable
22
+ // --packet path) into a clearer message, instead of leaking bare errno
23
+ // strings like `ENOENT: no such file or directory, open '...'`.
24
+ const buildMessage = error && error.code ? FS_ERRNO_MESSAGE[error.code] : null;
25
+ if (buildMessage) {
26
+ // `error.path` is present for open-time failures (ENOENT/EACCES/EPERM/
27
+ // ENOTDIR) but absent for read-time ones (EISDIR reading a directory), so
28
+ // name the path only when we have it.
29
+ const where = typeof error.path === "string" ? `: ${error.path}` : "";
30
+ console.error(`campaigns-os: ${buildMessage(where)}`);
31
+ process.exit(1);
32
+ }
33
+ // main() normally rejects with an Error, but guard against a non-Error throw
34
+ // (a string, number, or Promise.reject("boom")) so the user never sees
35
+ // `campaigns-os: undefined`.
36
+ console.error(`campaigns-os: ${String(error?.message ?? error)}`);
37
+ process.exit(1);
38
+ });
@@ -0,0 +1,138 @@
1
+ # campaign-spec
2
+
3
+ The CampaignSpec contract layer: a normalize phase, a composable rule registry,
4
+ and a canonical fixture corpus. Read `../CONTEXT.md` first for vocabulary
5
+ (`CampaignSpec`, `Rule`, `Violation`, `Tag`, `Corpus`).
6
+
7
+ This module is the single, public source of truth for CampaignSpec validation.
8
+ The Campaigns OS CLI doctor runs these rules during spec validation, and any
9
+ campaign authoring UI (such as a Map Builder bundle) can import the same registry
10
+ so internal teams and third-party agencies validate against identical rules. The
11
+ rules are pure TypeScript over a normalized spec with no heavy dependencies.
12
+
13
+ ## Layout
14
+
15
+ ```
16
+ campaign-spec/
17
+ index.ts # public interface
18
+ types.ts # CampaignSpec, Rule, Violation, Tag, Severity
19
+ normalize.ts # v4.3 authoring → canonical v4.2 funnels[] shape
20
+ rules/
21
+ index.ts # preset RuleSet constants
22
+ cycle-detection.ts # one file per rule
23
+ ...
24
+ fixtures/
25
+ index.ts # corpus loader
26
+ *.json # specs
27
+ expected/*.json # expected violations per spec
28
+ test/
29
+ rules/*.test.ts # per-rule unit tests
30
+ corpus.test.ts # corpus contract test
31
+ ```
32
+
33
+ ## Public interface
34
+
35
+ ```ts
36
+ import {
37
+ // Types
38
+ type CampaignSpec, type Rule, type Violation, type Tag,
39
+ // Phases
40
+ normalize, runRules, validateSpec,
41
+ // Presets
42
+ allRules, fastRules, specOnlyRules,
43
+ } from './campaign-spec'
44
+
45
+ // Backwards-compat (= runRules(allRules, normalize(spec)))
46
+ const violations = validateSpec(spec)
47
+
48
+ // Composable
49
+ const violations = runRules(normalize(spec), fastRules)
50
+
51
+ // Custom selection
52
+ const violations = runRules(
53
+ normalize(spec),
54
+ allRules.filter(r => r.tags.includes('structure') && r.id !== 'CycleDetection'),
55
+ )
56
+ ```
57
+
58
+ ## Rule shape
59
+
60
+ ```ts
61
+ type Rule = {
62
+ id: string // unique, stable; appears in Violation.ruleId
63
+ severity: 'error' | 'warning' // default; per-violation can override
64
+ tags: Tag[] // closed set, see types.ts
65
+ check(spec: CampaignSpec): Violation[]
66
+ }
67
+ ```
68
+
69
+ Rules are **pure** over a normalized spec. No context bag. No live data
70
+ dependency. Mode flags ("partial spec mid-edit", "fast mode") are tag filters,
71
+ not context flags. Rule parameters are bound at registration time.
72
+
73
+ ## Violation shape
74
+
75
+ ```ts
76
+ type Violation = {
77
+ ruleId: string
78
+ severity: 'error' | 'warning'
79
+ message: string
80
+ path: string // JSON Pointer: /funnels/0/pages/2/route
81
+ data?: Record<string, unknown>
82
+ }
83
+ ```
84
+
85
+ `path` enables field-level UI without rule-specific wiring. `data` carries
86
+ structured detail.
87
+
88
+ ## Adding a rule
89
+
90
+ 1. Create `rules/your-rule.ts`. Export a `Rule` value.
91
+ 2. Add it to the `allRules` array in `rules/index.ts`. If it belongs in
92
+ `fastRules` or `specOnlyRules`, add it to those too.
93
+ 3. Add a unit test in `test/rules/your-rule.test.ts` that loads a focused
94
+ fixture and asserts the violations.
95
+ 4. If the rule needs a new fixture, add `fixtures/<name>.json` and
96
+ `fixtures/expected/<name>.expected.json`. The corpus contract test will
97
+ pick it up automatically.
98
+
99
+ ## Adding a tag
100
+
101
+ Edit the `Tag` union in `types.ts` and the tag inventory in `../CONTEXT.md`.
102
+ The closed taxonomy is intentional — closed sets are documented; open sets
103
+ drift.
104
+
105
+ ## Tests
106
+
107
+ The suite runs on `node --test` (no bun dependency); `test/harness.ts` is a thin
108
+ adapter mapping the matchers used onto `node:assert`.
109
+
110
+ ```bash
111
+ # from the campaigns-os package root
112
+ npm run check:spec
113
+ # or directly
114
+ node --test "campaign-spec/test/**/*.test.ts"
115
+ ```
116
+
117
+ Three surfaces:
118
+
119
+ - **Per-rule** (`test/rules/*.test.ts`): focused fixtures, focused assertions.
120
+ - **Corpus contract** (`test/corpus.test.ts`): every fixture asserted against
121
+ its expected violations across all rules. Drift lights up exactly which
122
+ fixture diffed.
123
+ - **Compiled bundle** (`test/dist-smoke.test.ts`): imports the built
124
+ `dist/index.js` to prove `npm run build:spec` produced a working ESM module
125
+ with the full public surface (run `build:spec` first).
126
+
127
+ ## Consuming from a browser bundle
128
+
129
+ A campaign authoring UI can bundle this module (e.g. with esbuild as a browser
130
+ IIFE) and expose the public interface on `window` to run export-time validation
131
+ client-side. The rules have no Node-only or live-data dependencies, so the same
132
+ registry that backs the CLI doctor runs unchanged in the browser.
133
+
134
+ ## See also
135
+
136
+ - `../CONTEXT.md` — domain vocabulary.
137
+ - v4.1 spec topology is intentionally unsupported: `normalize()` rejects
138
+ `funnel_pages` input rather than silently wrapping it.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Analytics dl_* event vocabulary — SYNCED SNAPSHOT from the Campaign Cart SDK.
3
+ *
4
+ * SOURCE OF TRUTH: campaign-cart `src/utils/analytics/schemas/events.ts`
5
+ * (`DL_EVENTS`), carried via its generated `events.manifest.json`.
6
+ * Synced from SDK v0.4.30 (manifest event list unchanged since v0.4.28).
7
+ *
8
+ * Why a snapshot, not an import: campaigns-os is the public toolkit and takes
9
+ * no dependency on the browser SDK bundle (wrong direction, heavy). This module
10
+ * is the canonical CONSUMABLE the validator (AnalyticsContractShape) and the Map
11
+ * Builder picker (via the campaign-spec.js shim) both read, so they validate /
12
+ * autocomplete against exactly one list (cf. ADR-003, one rule registry).
13
+ *
14
+ * RESYNC when the SDK adds/removes a dl_* event: copy the manifest events array
15
+ * here verbatim and bump the SDK version line above. The accompanying test
16
+ * (analytics-vocabulary.test.ts) guards internal consistency.
17
+ *
18
+ * The vocabulary is the SDK FIRABLE SUPERSET (~35), not just the schema-bearing
19
+ * events: blockedEvents matches by exact event name against everything the SDK
20
+ * dispatches, so any fired event must be a known/blockable member.
21
+ */
22
+ export type DlEventCategory = 'ecommerce' | 'user' | 'upsell' | 'cart' | 'navigation' | 'engagement';
23
+ export interface DlEventDefinition {
24
+ /** Exact dataLayer event name the SDK pushes — matched verbatim by blockedEvents. */
25
+ name: string;
26
+ /** Coarse grouping for picker UIs. */
27
+ category: DlEventCategory;
28
+ /** True when the SDK defines a field-level validation schema for this event. */
29
+ hasSchema: boolean;
30
+ /** Human label for picker UIs and repair prompts. */
31
+ description: string;
32
+ }
33
+ /** SDK version whose manifest this snapshot was last checked against. */
34
+ export declare const CAMPAIGN_CART_ANALYTICS_VOCABULARY_SDK_VERSION = "0.4.30";
35
+ /**
36
+ * First Campaign Cart SDK version that stamps campaign_* and ncsid-derived
37
+ * campaign_session_id identifiers on every analytics event.
38
+ */
39
+ export declare const CAMPAIGN_CART_ANALYTICS_IDENTITY_MIN_SDK_VERSION = "0.4.30";
40
+ /** The canonical vocabulary, category-grouped (mirrors the SDK manifest order). */
41
+ export declare const DL_EVENTS: readonly DlEventDefinition[];
42
+ /** Flat list of canonical event names. */
43
+ export declare const DL_EVENT_NAMES: readonly string[];
44
+ /** O(1) membership set for validation. */
45
+ export declare const DL_EVENT_NAME_SET: ReadonlySet<string>;
46
+ /** True when name is a known canonical SDK dl_* event. */
47
+ export declare function isKnownDlEvent(name: string): boolean;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Analytics dl_* event vocabulary — SYNCED SNAPSHOT from the Campaign Cart SDK.
3
+ *
4
+ * SOURCE OF TRUTH: campaign-cart `src/utils/analytics/schemas/events.ts`
5
+ * (`DL_EVENTS`), carried via its generated `events.manifest.json`.
6
+ * Synced from SDK v0.4.30 (manifest event list unchanged since v0.4.28).
7
+ *
8
+ * Why a snapshot, not an import: campaigns-os is the public toolkit and takes
9
+ * no dependency on the browser SDK bundle (wrong direction, heavy). This module
10
+ * is the canonical CONSUMABLE the validator (AnalyticsContractShape) and the Map
11
+ * Builder picker (via the campaign-spec.js shim) both read, so they validate /
12
+ * autocomplete against exactly one list (cf. ADR-003, one rule registry).
13
+ *
14
+ * RESYNC when the SDK adds/removes a dl_* event: copy the manifest events array
15
+ * here verbatim and bump the SDK version line above. The accompanying test
16
+ * (analytics-vocabulary.test.ts) guards internal consistency.
17
+ *
18
+ * The vocabulary is the SDK FIRABLE SUPERSET (~35), not just the schema-bearing
19
+ * events: blockedEvents matches by exact event name against everything the SDK
20
+ * dispatches, so any fired event must be a known/blockable member.
21
+ */
22
+ /** SDK version whose manifest this snapshot was last checked against. */
23
+ export const CAMPAIGN_CART_ANALYTICS_VOCABULARY_SDK_VERSION = '0.4.30';
24
+ /**
25
+ * First Campaign Cart SDK version that stamps campaign_* and ncsid-derived
26
+ * campaign_session_id identifiers on every analytics event.
27
+ */
28
+ export const CAMPAIGN_CART_ANALYTICS_IDENTITY_MIN_SDK_VERSION = '0.4.30';
29
+ /** The canonical vocabulary, category-grouped (mirrors the SDK manifest order). */
30
+ export const DL_EVENTS = [
31
+ { name: 'dl_view_item_list', category: 'ecommerce', hasSchema: true, description: 'Product list / collection impression' },
32
+ { name: 'dl_view_item', category: 'ecommerce', hasSchema: true, description: 'Product detail view' },
33
+ { name: 'dl_select_item', category: 'ecommerce', hasSchema: true, description: 'Product clicked from a list' },
34
+ { name: 'dl_view_search_results', category: 'ecommerce', hasSchema: true, description: 'Search results viewed' },
35
+ { name: 'dl_search', category: 'ecommerce', hasSchema: false, description: 'Search performed (Meta Search)' },
36
+ { name: 'dl_add_to_cart', category: 'ecommerce', hasSchema: true, description: 'Item added to cart' },
37
+ { name: 'dl_remove_from_cart', category: 'ecommerce', hasSchema: true, description: 'Item removed from cart' },
38
+ { name: 'dl_add_to_wishlist', category: 'ecommerce', hasSchema: false, description: 'Item added to wishlist' },
39
+ { name: 'dl_view_cart', category: 'ecommerce', hasSchema: true, description: 'Cart viewed' },
40
+ { name: 'dl_begin_checkout', category: 'ecommerce', hasSchema: true, description: 'Checkout started' },
41
+ { name: 'dl_add_shipping_info', category: 'ecommerce', hasSchema: true, description: 'Shipping info added' },
42
+ { name: 'dl_add_payment_info', category: 'ecommerce', hasSchema: true, description: 'Payment info added' },
43
+ { name: 'dl_purchase', category: 'ecommerce', hasSchema: true, description: 'Main order purchase' },
44
+ { name: 'dl_refund', category: 'ecommerce', hasSchema: false, description: 'Order refunded (adapter-mapped)' },
45
+ { name: 'dl_view_promotion', category: 'ecommerce', hasSchema: false, description: 'Promotion impression' },
46
+ { name: 'dl_select_promotion', category: 'ecommerce', hasSchema: false, description: 'Promotion clicked' },
47
+ { name: 'dl_user_data', category: 'user', hasSchema: true, description: 'User + cart context (fired first)' },
48
+ { name: 'dl_sign_up', category: 'user', hasSchema: true, description: 'Account sign-up' },
49
+ { name: 'dl_login', category: 'user', hasSchema: true, description: 'Account login' },
50
+ { name: 'dl_subscribe', category: 'user', hasSchema: true, description: 'Subscription created' },
51
+ { name: 'dl_start_trial', category: 'user', hasSchema: false, description: 'Trial started (Meta StartTrial)' },
52
+ { name: 'dl_viewed_upsell', category: 'upsell', hasSchema: true, description: 'Upsell offer viewed' },
53
+ { name: 'dl_accepted_upsell', category: 'upsell', hasSchema: true, description: 'Upsell accepted' },
54
+ { name: 'dl_skipped_upsell', category: 'upsell', hasSchema: true, description: 'Upsell skipped' },
55
+ { name: 'dl_upsell_purchase', category: 'upsell', hasSchema: true, description: 'Accepted upsell in GA4 purchase format' },
56
+ { name: 'dl_cart_updated', category: 'cart', hasSchema: false, description: 'Cart contents changed' },
57
+ { name: 'dl_package_swapped', category: 'cart', hasSchema: false, description: 'Package variant swapped' },
58
+ { name: 'dl_page_view', category: 'navigation', hasSchema: false, description: 'SDK page view' },
59
+ { name: 'dl_route_changed', category: 'navigation', hasSchema: false, description: 'Funnel route changed' },
60
+ { name: 'dl_scroll_depth', category: 'engagement', hasSchema: false, description: 'Scroll-depth milestone reached' },
61
+ { name: 'dl_exit_intent_shown', category: 'engagement', hasSchema: false, description: 'Exit-intent offer shown' },
62
+ { name: 'dl_exit_intent_accepted', category: 'engagement', hasSchema: false, description: 'Exit-intent offer accepted' },
63
+ { name: 'dl_exit_intent_dismissed', category: 'engagement', hasSchema: false, description: 'Exit-intent offer dismissed' },
64
+ { name: 'dl_exit_intent_closed', category: 'engagement', hasSchema: false, description: 'Exit-intent modal closed' },
65
+ { name: 'dl_exit_intent_action', category: 'engagement', hasSchema: false, description: 'Exit-intent CTA/action clicked' },
66
+ ];
67
+ /** Flat list of canonical event names. */
68
+ export const DL_EVENT_NAMES = DL_EVENTS.map((e) => e.name);
69
+ /** O(1) membership set for validation. */
70
+ export const DL_EVENT_NAME_SET = new Set(DL_EVENT_NAMES);
71
+ /** True when name is a known canonical SDK dl_* event. */
72
+ export function isKnownDlEvent(name) {
73
+ return DL_EVENT_NAME_SET.has(name);
74
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * campaign-spec — the CampaignSpec contract layer.
3
+ *
4
+ * Public interface for the spec validation module — the single source
5
+ * of truth for CampaignSpec validation. The Campaigns OS CLI doctor and
6
+ * any campaign authoring UI (e.g. a Map Builder browser bundle built
7
+ * from this module) import from here, so internal teams and third-party
8
+ * agencies validate against the same rules.
9
+ *
10
+ * Read ../CONTEXT.md for vocabulary (CampaignSpec, Rule, Violation, Tag,
11
+ * Corpus, normalize) and ./README.md for usage patterns.
12
+ */
13
+ import type { CampaignSpec, RuleSet, Violation } from './types.ts';
14
+ export type { CampaignSpec, Rule, RuleSet, Violation, Tag, Severity, Fixture, Page, PageType, Funnel, Offer, Campaign, DesignSource, DesignSourceBreakpoints, TemplateFamilyHint, UpsellTemplatePattern, UpsellMvTiers, VariantLabels, PromoCode, AnalyticsContract, AnalyticsMode, AnalyticsProvider, OutOfBandPixel, ManualEvent, ContentParam, TrackingParams, AnalyticsParams, UtmTransfer, } from './types.ts';
15
+ export { normalize, NormalizeError } from './normalize.ts';
16
+ export { FORWARD_ROUTE_FIELDS, ACCEPT_ROUTE_FIELD, DECLINE_ROUTE_FIELD, ROUTE_FIELDS, PAYMENT_BEARING_PAGE_TYPES, OFFER_BEARING_PAGE_TYPES, forwardRouteTarget, acceptRouteTarget, declineRouteTarget, hasForwardRoute, applicableForwardFields, inapplicableForwardFields, describeForwardField, outgoingEdgeIds, } from './routing.ts';
17
+ export type { ForwardFieldApplicability } from './routing.ts';
18
+ export { allRules, fastRules, specOnlyRules } from './rules/index.ts';
19
+ export { SUPPORTED_SCHEMA_VERSIONS } from './rules/schema-version.ts';
20
+ export { RELEASED_SDK_VERSION_PATTERN, parseSdkVersion, isReleasedSdkVersion, describeSdkVersionRejection, } from './sdk-version-parse.ts';
21
+ export type { SdkVersionParseResult, SdkVersionRejectionReason, } from './sdk-version-parse.ts';
22
+ export { CAMPAIGN_CART_ANALYTICS_IDENTITY_MIN_SDK_VERSION, CAMPAIGN_CART_ANALYTICS_VOCABULARY_SDK_VERSION, DL_EVENTS, DL_EVENT_NAMES, DL_EVENT_NAME_SET, isKnownDlEvent, } from './analytics-vocabulary.ts';
23
+ export type { DlEventCategory, DlEventDefinition, } from './analytics-vocabulary.ts';
24
+ /**
25
+ * Run a RuleSet against a normalized spec. Returns the flat list of
26
+ * violations across all rules; callers choose their failure policy (throw on
27
+ * any error, collect for UI, etc.).
28
+ *
29
+ * const violations = runRules(normalize(spec), allRules)
30
+ * if (violations.some(v => v.severity === 'error')) { ... }
31
+ */
32
+ export declare function runRules(spec: CampaignSpec, rules: RuleSet): Violation[];
33
+ /**
34
+ * Backwards-compatible entry point: normalize → run all rules.
35
+ *
36
+ * Equivalent to `runRules(normalize(input), allRules)`. Catches NormalizeError
37
+ * and surfaces it as a single error-severity Violation so legacy callers that
38
+ * expect a flat array don't need to handle exceptions.
39
+ */
40
+ export declare function validateSpec(input: unknown): Violation[];
@@ -0,0 +1,77 @@
1
+ /**
2
+ * campaign-spec — the CampaignSpec contract layer.
3
+ *
4
+ * Public interface for the spec validation module — the single source
5
+ * of truth for CampaignSpec validation. The Campaigns OS CLI doctor and
6
+ * any campaign authoring UI (e.g. a Map Builder browser bundle built
7
+ * from this module) import from here, so internal teams and third-party
8
+ * agencies validate against the same rules.
9
+ *
10
+ * Read ../CONTEXT.md for vocabulary (CampaignSpec, Rule, Violation, Tag,
11
+ * Corpus, normalize) and ./README.md for usage patterns.
12
+ */
13
+ import { normalize, NormalizeError } from "./normalize.js";
14
+ import { allRules } from "./rules/index.js";
15
+ export { normalize, NormalizeError } from "./normalize.js";
16
+ // Outgoing-edge resolution — the single source of truth for "where does this
17
+ // page go". Source intake, cycle detection and the QA topology extractor all
18
+ // consume these rather than keeping their own page-type tables.
19
+ export { FORWARD_ROUTE_FIELDS, ACCEPT_ROUTE_FIELD, DECLINE_ROUTE_FIELD, ROUTE_FIELDS, PAYMENT_BEARING_PAGE_TYPES, OFFER_BEARING_PAGE_TYPES, forwardRouteTarget, acceptRouteTarget, declineRouteTarget, hasForwardRoute, applicableForwardFields, inapplicableForwardFields, describeForwardField, outgoingEdgeIds, } from "./routing.js";
20
+ export { allRules, fastRules, specOnlyRules } from "./rules/index.js";
21
+ // Supported schema_version matrix — single source for the SchemaVersion rule
22
+ // and any consumer that needs to present or gate on the supported lineages.
23
+ // A sync test pins it to the schemas/campaign-spec.v4.schema.json enum.
24
+ export { SUPPORTED_SCHEMA_VERSIONS } from "./rules/schema-version.js";
25
+ // Strict released-SDK-version parser — shared with the downstream Page Kit
26
+ // SDK-version checkpoint (src/page-kit-sdk-version.mjs) so authoring-time
27
+ // validation and build-time gating reject exactly the same values.
28
+ export { RELEASED_SDK_VERSION_PATTERN, parseSdkVersion, isReleasedSdkVersion, describeSdkVersionRejection, } from "./sdk-version-parse.js";
29
+ // Canonical dl_* analytics event vocabulary — synced from the Campaign Cart SDK.
30
+ // The AnalyticsContractShape rule validates blockedEvents against it; the Map
31
+ // Builder picker (via the campaign-spec.js shim) autocompletes from it.
32
+ export { CAMPAIGN_CART_ANALYTICS_IDENTITY_MIN_SDK_VERSION, CAMPAIGN_CART_ANALYTICS_VOCABULARY_SDK_VERSION, DL_EVENTS, DL_EVENT_NAMES, DL_EVENT_NAME_SET, isKnownDlEvent, } from "./analytics-vocabulary.js";
33
+ // ── Phases ─────────────────────────────────────────────────────────────────
34
+ /**
35
+ * Run a RuleSet against a normalized spec. Returns the flat list of
36
+ * violations across all rules; callers choose their failure policy (throw on
37
+ * any error, collect for UI, etc.).
38
+ *
39
+ * const violations = runRules(normalize(spec), allRules)
40
+ * if (violations.some(v => v.severity === 'error')) { ... }
41
+ */
42
+ export function runRules(spec, rules) {
43
+ const out = [];
44
+ for (const rule of rules) {
45
+ for (const violation of rule.check(spec)) {
46
+ out.push(violation);
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ /**
52
+ * Backwards-compatible entry point: normalize → run all rules.
53
+ *
54
+ * Equivalent to `runRules(normalize(input), allRules)`. Catches NormalizeError
55
+ * and surfaces it as a single error-severity Violation so legacy callers that
56
+ * expect a flat array don't need to handle exceptions.
57
+ */
58
+ export function validateSpec(input) {
59
+ let spec;
60
+ try {
61
+ spec = normalize(input);
62
+ }
63
+ catch (err) {
64
+ if (err instanceof NormalizeError) {
65
+ return [
66
+ {
67
+ ruleId: 'Normalize',
68
+ severity: 'error',
69
+ message: err.message,
70
+ path: '',
71
+ },
72
+ ];
73
+ }
74
+ throw err;
75
+ }
76
+ return runRules(spec, allRules);
77
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * normalize — the single phase that takes an authoring CampaignSpec and emits
3
+ * the canonical v4.2 funnels[] shape that rules operate on.
4
+ *
5
+ * Today this is near-empty: v4.3 already uses funnels[]. The phase exists so
6
+ * future authoring evolutions have one place to land their migration.
7
+ *
8
+ * v4.1 (funnel_pages[]) is intentionally NOT supported — see
9
+ * ../docs/adr/002-drop-v41-spec-support.md. Inputs without `funnels[]` fail
10
+ * the structural assertion below.
11
+ */
12
+ import type { CampaignSpec } from './types.ts';
13
+ /**
14
+ * Thrown when input is structurally unrecognizable as a CampaignSpec.
15
+ * Distinct from rule violations: a violation means "this spec is wrong";
16
+ * a NormalizeError means "this isn't a spec at all (or it's an unsupported
17
+ * legacy version)."
18
+ */
19
+ export declare class NormalizeError extends Error {
20
+ constructor(message: string);
21
+ }
22
+ export declare function normalize(input: unknown): CampaignSpec;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * normalize — the single phase that takes an authoring CampaignSpec and emits
3
+ * the canonical v4.2 funnels[] shape that rules operate on.
4
+ *
5
+ * Today this is near-empty: v4.3 already uses funnels[]. The phase exists so
6
+ * future authoring evolutions have one place to land their migration.
7
+ *
8
+ * v4.1 (funnel_pages[]) is intentionally NOT supported — see
9
+ * ../docs/adr/002-drop-v41-spec-support.md. Inputs without `funnels[]` fail
10
+ * the structural assertion below.
11
+ */
12
+ /**
13
+ * Thrown when input is structurally unrecognizable as a CampaignSpec.
14
+ * Distinct from rule violations: a violation means "this spec is wrong";
15
+ * a NormalizeError means "this isn't a spec at all (or it's an unsupported
16
+ * legacy version)."
17
+ */
18
+ export class NormalizeError extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = 'NormalizeError';
22
+ }
23
+ }
24
+ export function normalize(input) {
25
+ if (input == null || typeof input !== 'object') {
26
+ throw new NormalizeError('CampaignSpec must be an object.');
27
+ }
28
+ const obj = input;
29
+ // v4.1 detection: top-level funnel_pages without funnels[] means a legacy
30
+ // spec. We reject explicitly to make the migration visible.
31
+ if (!Array.isArray(obj.funnels) && Array.isArray(obj.funnel_pages)) {
32
+ throw new NormalizeError('CampaignSpec uses legacy v4.1 funnel_pages topology. v4.1 is not supported (ADR-002). ' +
33
+ 'Migrate the spec to v4.2+ funnels[] before validating.');
34
+ }
35
+ if (!Array.isArray(obj.funnels)) {
36
+ throw new NormalizeError('CampaignSpec is missing funnels[]. Expected canonical v4.2+ topology.');
37
+ }
38
+ // Future migrations (v5 → v4, etc.) land here. Today the shape is already
39
+ // canonical, so pass through.
40
+ return obj;
41
+ }