@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.
- package/AGENTS.md +204 -0
- package/CHANGELOG.md +5002 -0
- package/CONTEXT.md +685 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +368 -0
- package/agents/claude/CLAUDE.md +32 -0
- package/agents/codex/AGENTS.md +27 -0
- package/agents/copilot/copilot-instructions.md +14 -0
- package/agents/cursor/campaigns-os.mdc +13 -0
- package/bin/campaigns-os.mjs +38 -0
- package/campaign-spec/README.md +138 -0
- package/campaign-spec/dist/analytics-vocabulary.d.ts +47 -0
- package/campaign-spec/dist/analytics-vocabulary.js +74 -0
- package/campaign-spec/dist/index.d.ts +40 -0
- package/campaign-spec/dist/index.js +77 -0
- package/campaign-spec/dist/normalize.d.ts +22 -0
- package/campaign-spec/dist/normalize.js +41 -0
- package/campaign-spec/dist/routing.d.ts +190 -0
- package/campaign-spec/dist/routing.js +263 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +42 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.js +303 -0
- package/campaign-spec/dist/rules/assembly-hints-shape.d.ts +42 -0
- package/campaign-spec/dist/rules/assembly-hints-shape.js +191 -0
- package/campaign-spec/dist/rules/campaign-metadata.d.ts +14 -0
- package/campaign-spec/dist/rules/campaign-metadata.js +40 -0
- package/campaign-spec/dist/rules/checkout-has-success-url.d.ts +31 -0
- package/campaign-spec/dist/rules/checkout-has-success-url.js +66 -0
- package/campaign-spec/dist/rules/cycle-detection.d.ts +12 -0
- package/campaign-spec/dist/rules/cycle-detection.js +141 -0
- package/campaign-spec/dist/rules/design-source-shape.d.ts +29 -0
- package/campaign-spec/dist/rules/design-source-shape.js +142 -0
- package/campaign-spec/dist/rules/downsell-without-upsell.d.ts +11 -0
- package/campaign-spec/dist/rules/downsell-without-upsell.js +40 -0
- package/campaign-spec/dist/rules/exit-intent-validation.d.ts +23 -0
- package/campaign-spec/dist/rules/exit-intent-validation.js +147 -0
- package/campaign-spec/dist/rules/funnel-count.d.ts +9 -0
- package/campaign-spec/dist/rules/funnel-count.js +37 -0
- package/campaign-spec/dist/rules/funnel-hypothesis-length.d.ts +22 -0
- package/campaign-spec/dist/rules/funnel-hypothesis-length.js +60 -0
- package/campaign-spec/dist/rules/funnel-identity.d.ts +15 -0
- package/campaign-spec/dist/rules/funnel-identity.js +57 -0
- package/campaign-spec/dist/rules/funnel-weight-sum.d.ts +20 -0
- package/campaign-spec/dist/rules/funnel-weight-sum.js +66 -0
- package/campaign-spec/dist/rules/index.d.ts +69 -0
- package/campaign-spec/dist/rules/index.js +131 -0
- package/campaign-spec/dist/rules/offer-ref-integrity.d.ts +13 -0
- package/campaign-spec/dist/rules/offer-ref-integrity.js +59 -0
- package/campaign-spec/dist/rules/package-pricing-sanity.d.ts +12 -0
- package/campaign-spec/dist/rules/package-pricing-sanity.js +42 -0
- package/campaign-spec/dist/rules/page-count.d.ts +11 -0
- package/campaign-spec/dist/rules/page-count.js +31 -0
- package/campaign-spec/dist/rules/page-id-uniqueness.d.ts +14 -0
- package/campaign-spec/dist/rules/page-id-uniqueness.js +47 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.d.ts +8 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.js +126 -0
- package/campaign-spec/dist/rules/promo-codes-shape.d.ts +30 -0
- package/campaign-spec/dist/rules/promo-codes-shape.js +187 -0
- package/campaign-spec/dist/rules/route-field-ignored-for-page-type.d.ts +33 -0
- package/campaign-spec/dist/rules/route-field-ignored-for-page-type.js +81 -0
- package/campaign-spec/dist/rules/route-target-resolves.d.ts +32 -0
- package/campaign-spec/dist/rules/route-target-resolves.js +112 -0
- package/campaign-spec/dist/rules/schema-version.d.ts +21 -0
- package/campaign-spec/dist/rules/schema-version.js +53 -0
- package/campaign-spec/dist/rules/sdk-version.d.ts +26 -0
- package/campaign-spec/dist/rules/sdk-version.js +96 -0
- package/campaign-spec/dist/rules/shipping-countries-shape.d.ts +10 -0
- package/campaign-spec/dist/rules/shipping-countries-shape.js +30 -0
- package/campaign-spec/dist/rules/shipping-methods-present.d.ts +10 -0
- package/campaign-spec/dist/rules/shipping-methods-present.js +26 -0
- package/campaign-spec/dist/rules/store-profile-shape.d.ts +30 -0
- package/campaign-spec/dist/rules/store-profile-shape.js +127 -0
- package/campaign-spec/dist/rules/thank-you-requirement.d.ts +16 -0
- package/campaign-spec/dist/rules/thank-you-requirement.js +50 -0
- package/campaign-spec/dist/rules/unknown-top-level-fields.d.ts +28 -0
- package/campaign-spec/dist/rules/unknown-top-level-fields.js +114 -0
- package/campaign-spec/dist/rules/upsell-has-packages.d.ts +9 -0
- package/campaign-spec/dist/rules/upsell-has-packages.js +35 -0
- package/campaign-spec/dist/rules/upsell-routing-complete.d.ts +10 -0
- package/campaign-spec/dist/rules/upsell-routing-complete.js +45 -0
- package/campaign-spec/dist/rules/upsell-without-checkout.d.ts +11 -0
- package/campaign-spec/dist/rules/upsell-without-checkout.js +44 -0
- package/campaign-spec/dist/rules/variant-labels-shape.d.ts +28 -0
- package/campaign-spec/dist/rules/variant-labels-shape.js +86 -0
- package/campaign-spec/dist/sdk-version-parse.d.ts +43 -0
- package/campaign-spec/dist/sdk-version-parse.js +62 -0
- package/campaign-spec/dist/types.d.ts +674 -0
- package/campaign-spec/dist/types.js +40 -0
- package/campaign-spec/package.json +12 -0
- package/compatibility.json +25 -0
- package/contracts/agent-relevant-change-policy.v1.json +111 -0
- package/contracts/brand-theme-source-defaults.figma-sections-export.v0.json +32 -0
- package/contracts/brand-theme-target-tokens.next-core.v0.json +65 -0
- package/contracts/campaign-cart-checkout-field-contract.v0.json +45 -0
- package/contracts/campaign-cart-sdk-support-policy.v0.json +11 -0
- package/contracts/commerce-surface-catalog.json +2452 -0
- package/contracts/fixtures/orientation/canonicalization/v1.json +34 -0
- package/contracts/fixtures/orientation/envelope/current.json +95 -0
- package/contracts/fixtures/orientation/envelope/freshness_unknown.json +97 -0
- package/contracts/fixtures/orientation/envelope/legacy_baseline.json +95 -0
- package/contracts/fixtures/orientation/envelope/orientation_available.json +136 -0
- package/contracts/fixtures/orientation/envelope/recovered_interrupted_update.json +136 -0
- package/contracts/fixtures/orientation/envelope/refused.json +100 -0
- package/contracts/fixtures/orientation/envelope/restart_required.json +137 -0
- package/contracts/fixtures/orientation/envelope/updated.json +135 -0
- package/contracts/fixtures/orientation/hostile-target/README.md +61 -0
- package/contracts/fixtures/orientation/hostile-target/manifest.json +82 -0
- package/contracts/fixtures/orientation/hostile-target/repo/CHANGELOG.md +18 -0
- package/contracts/fixtures/orientation/hostile-target/repo/bin/intended.mjs +12 -0
- package/contracts/fixtures/orientation/hostile-target/repo/bin/tripwire.mjs +15 -0
- package/contracts/fixtures/orientation/hostile-target/repo/contracts/release-ledger.json +48 -0
- package/contracts/fixtures/orientation/hostile-target/repo/contracts/supported-surface.json +17 -0
- package/contracts/fixtures/orientation/hostile-target/repo/docs/example-contract.md +13 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-checkout +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-merge +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/pre-commit +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/package.json +15 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/tripwire.mjs +9 -0
- package/contracts/fixtures/orientation/hostile-target/repo/package.json +20 -0
- package/contracts/fixtures/orientation/hostile-target/repo/schemas/example.v0.schema.json +15 -0
- package/contracts/fixtures/orientation/release-gate/cases.json +1073 -0
- package/contracts/fixtures/runtime-recipe/accept/current.json +299 -0
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +294 -0
- package/contracts/fixtures/runtime-recipe/dist-states.json +51 -0
- package/contracts/fixtures/runtime-recipe/manifest.json +85 -0
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +297 -0
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +251 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +310 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +299 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/assembly-report.json +180 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/build-context.json +115 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/doctor-output.json +29 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/qa-verdict.json +27 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/campaign-runtime.build.json +141 -0
- package/contracts/migration-sidecar-bundle.v0.json +149 -0
- package/contracts/orientation-limits.v1.json +41 -0
- package/contracts/orientation-reason-codes.v1.json +196 -0
- package/contracts/private-template-sources.json +8 -0
- package/contracts/release-ledger.json +4598 -0
- package/contracts/reserved-skill-names.json +13 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +299 -0
- package/contracts/supported-surface.json +180 -0
- package/contracts/template-brand-contract.apollo-mv-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.apollo.v0.json +27 -0
- package/contracts/template-brand-contract.demeter.v0.json +27 -0
- package/contracts/template-brand-contract.olympus-mv-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.olympus-mv-two-step.v0.json +28 -0
- package/contracts/template-brand-contract.olympus.v0.json +27 -0
- package/contracts/template-brand-contract.shared-commerce.v0.json +190 -0
- package/contracts/template-brand-contract.shop-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.shop-three-step.v0.json +29 -0
- package/contracts/template-slot-manifest.apollo-mv-single-step.v0.json +14 -0
- package/contracts/template-slot-manifest.apollo.v0.json +12 -0
- package/contracts/template-slot-manifest.demeter.v0.json +30 -0
- package/contracts/template-slot-manifest.olympus-mv-single-step.v0.json +14 -0
- package/contracts/template-slot-manifest.olympus-mv-two-step.v0.json +15 -0
- package/contracts/template-slot-manifest.olympus.v0.json +12 -0
- package/contracts/template-slot-manifest.shared-content-core.v0.json +4254 -0
- package/contracts/template-slot-manifest.shop-single-step.v0.json +47 -0
- package/contracts/template-slot-manifest.shop-three-step.v0.json +33 -0
- package/docs/brand-theme-bridge.md +159 -0
- package/docs/build-packet.md +1300 -0
- package/docs/campaign-build-brief.md +145 -0
- package/docs/campaign-standardization-report.md +329 -0
- package/docs/campaigns-os-build-flow.md +117 -0
- package/docs/design-source-package.md +784 -0
- package/docs/legacy-migration.md +58 -0
- package/docs/migration-sidecar-bundle.md +139 -0
- package/docs/orientation-contract-reference.md +1220 -0
- package/docs/polish-evidence.md +502 -0
- package/docs/qa-and-test-orders.md +1691 -0
- package/docs/release-ledger-authoring-guide.md +274 -0
- package/docs/runtime-readiness.md +211 -0
- package/docs/supported-surface.md +83 -0
- package/docs/versioning.md +55 -0
- package/docs/workflow-findings-sidecar.md +588 -0
- package/package.json +135 -0
- package/prompts/first-build.md +27 -0
- package/prompts/friction-log.md +30 -0
- package/schemas/campaign-build-brief.v1.schema.json +149 -0
- package/schemas/campaign-design-source-package.v0.schema.json +697 -0
- package/schemas/campaign-runtime-assembly-report.v0.schema.json +390 -0
- package/schemas/campaign-runtime-build-context.v0.schema.json +339 -0
- package/schemas/campaign-runtime-build-packet.v0.schema.json +452 -0
- package/schemas/campaign-spec.v4.schema.json +582 -0
- package/schemas/campaigns-os-doctor-output.v0.schema.json +43 -0
- package/schemas/campaigns-os-legacy-migration-inventory.v0.schema.json +112 -0
- package/schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json +38 -0
- package/schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json +57 -0
- package/schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json +150 -0
- package/schemas/campaigns-os-qa-verdict.v0.schema.json +438 -0
- package/schemas/campaigns-os-release-ledger.v1.schema.json +162 -0
- package/schemas/campaigns-os-run-record.v0.schema.json +343 -0
- package/schemas/campaigns-os-runtime-recipe.v1.schema.json +313 -0
- package/schemas/campaigns-os-sidecar-bundle-conformance.v0.schema.json +78 -0
- package/schemas/campaigns-os-tooling-orientation.v1.schema.json +381 -0
- package/schemas/campaigns-os-workflow-finding.v0.schema.json +134 -0
- package/schemas/source-html-manifest.v0.schema.json +252 -0
- package/skills/next-campaigns-build/SKILL.md +73 -0
- package/skills/next-campaigns-os/SKILL.md +101 -0
- package/skills/next-campaigns-os/references/session-intake.md +160 -0
- package/skills/next-campaigns-os-setup/SKILL.md +22 -0
- package/skills/next-campaigns-polish/SKILL.md +145 -0
- package/skills/next-campaigns-qa/SKILL.md +92 -0
- package/skills.json +56 -0
- package/skills.sh +64 -0
- package/src/adapter-decision-contract.mjs +333 -0
- package/src/brand-theme.mjs +1151 -0
- package/src/browser-launch.mjs +79 -0
- package/src/build-brief.mjs +781 -0
- package/src/built-site-scope.mjs +312 -0
- package/src/campaign-ecosystem.mjs +734 -0
- package/src/campaign-identity.mjs +405 -0
- package/src/campaign-workspace.mjs +127 -0
- package/src/checkpoint-waiver.mjs +302 -0
- package/src/cli.mjs +13442 -0
- package/src/commercial-journey.mjs +1119 -0
- package/src/commercial-parity.mjs +965 -0
- package/src/consent.mjs +347 -0
- package/src/content-residue.mjs +322 -0
- package/src/deadline.mjs +81 -0
- package/src/design-source-package.mjs +2604 -0
- package/src/deviation.mjs +107 -0
- package/src/doctor-check-registry.mjs +49 -0
- package/src/doctor-sidecar.mjs +106 -0
- package/src/finding-cause.mjs +557 -0
- package/src/findings.mjs +326 -0
- package/src/fs-identity.mjs +68 -0
- package/src/gate-actions.mjs +105 -0
- package/src/html-scan.mjs +53 -0
- package/src/install-mode.mjs +272 -0
- package/src/legacy-migration.d.ts +128 -0
- package/src/legacy-migration.mjs +510 -0
- package/src/lifecycle.mjs +338 -0
- package/src/local-proof.mjs +401 -0
- package/src/map-pin-writeback.mjs +210 -0
- package/src/orchestration-stage-contract.mjs +81 -0
- package/src/package-install-fixture.mjs +33 -0
- package/src/page-kit-build-summary.mjs +175 -0
- package/src/page-kit-campaign-config.mjs +57 -0
- package/src/page-kit-sdk-version.mjs +392 -0
- package/src/page-kit-store-profile.mjs +369 -0
- package/src/page-kit-sync.mjs +162 -0
- package/src/polish-browser.mjs +867 -0
- package/src/polish-capture.mjs +1094 -0
- package/src/polish-deadline.mjs +51 -0
- package/src/polish-gate.mjs +739 -0
- package/src/polish-node.mjs +639 -0
- package/src/polish-page-load.mjs +1100 -0
- package/src/private-template-source.mjs +237 -0
- package/src/proof-policy.mjs +82 -0
- package/src/qa-analytics-correctness.mjs +307 -0
- package/src/qa-analytics-errors.mjs +38 -0
- package/src/qa-analytics-parity.mjs +699 -0
- package/src/qa-binding-evidence.mjs +140 -0
- package/src/qa-browser.mjs +6608 -0
- package/src/qa-cart-entry.mjs +406 -0
- package/src/qa-commercial-parity.mjs +641 -0
- package/src/qa-node.mjs +3620 -0
- package/src/qa-order-bump.mjs +381 -0
- package/src/qa-parity-capture.mjs +428 -0
- package/src/qa-parity-fixture.mjs +359 -0
- package/src/qa-publish.mjs +362 -0
- package/src/qa-purchase-data-layer.mjs +263 -0
- package/src/qa-route-probe.mjs +272 -0
- package/src/qa-sidecar.mjs +188 -0
- package/src/qa-test-order-topology.mjs +207 -0
- package/src/qa-url-privacy.mjs +13 -0
- package/src/qa-verdict-discovery.mjs +192 -0
- package/src/qa-verdict-publish.mjs +105 -0
- package/src/qa-verdict.mjs +287 -0
- package/src/remit.mjs +388 -0
- package/src/repo-scan.mjs +83 -0
- package/src/route-identity.mjs +133 -0
- package/src/run-record-closeout.mjs +229 -0
- package/src/run-record.mjs +839 -0
- package/src/run-session.mjs +226 -0
- package/src/runtime-state-ignore.mjs +113 -0
- package/src/sdk-attribute-index.mjs +212 -0
- package/src/sdk-markup.mjs +358 -0
- package/src/sdk-meta-tags.mjs +52 -0
- package/src/shell-token.mjs +7 -0
- package/src/sidecar-bundle.mjs +399 -0
- package/src/source-asset-crawl.mjs +469 -0
- package/src/source-html-intake.mjs +627 -0
- package/src/source-html-manifest.mjs +276 -0
- package/src/source-prep.mjs +284 -0
- package/src/spec-derive-store.mjs +431 -0
- package/src/spec-derive.mjs +514 -0
- package/src/spec-fetch.mjs +66 -0
- package/src/spec-hash.mjs +48 -0
- package/src/spec-identity.mjs +27 -0
- package/src/stage-ledger.mjs +509 -0
- package/src/standardization-report.mjs +1297 -0
- package/src/template-brand-contract.mjs +473 -0
- package/src/template-freshness.mjs +196 -0
- package/src/template-reference.mjs +81 -0
- package/src/template-slot-manifest.mjs +150 -0
- package/src/text-safety.mjs +62 -0
- package/src/theme-gate.mjs +185 -0
- 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
|
+
}
|