@nextcommerce/campaigns-os 1.43.2 → 1.47.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 (80) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +798 -5103
  3. package/README.md +33 -12
  4. package/agents/claude/CLAUDE.md +1 -1
  5. package/agents/codex/AGENTS.md +1 -1
  6. package/agents/copilot/copilot-instructions.md +1 -1
  7. package/agents/cursor/campaigns-os.mdc +1 -1
  8. package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +2 -2
  9. package/campaign-spec/dist/rules/analytics-contract-shape.js +2 -2
  10. package/campaign-spec/dist/rules/store-profile-shape.d.ts +5 -1
  11. package/campaign-spec/dist/rules/store-profile-shape.js +8 -10
  12. package/campaign-spec/dist/types.d.ts +2 -2
  13. package/compatibility.json +1 -1
  14. package/contracts/archive/CHANGELOG.2026-09-30.md +5111 -0
  15. package/contracts/archive/release-ledger.2026-09-30.json +5068 -0
  16. package/contracts/commerce-surface-catalog.json +1204 -129
  17. package/contracts/effects.v1.json +1179 -116
  18. package/contracts/orientation-reason-codes.v1.json +7 -0
  19. package/contracts/release-ledger.json +2515 -5919
  20. package/contracts/supported-surface.json +7 -4
  21. package/contracts/template-brand-contract.shared-commerce.v0.json +3 -3
  22. package/contracts/template-slot-manifest.shared-content-core.v0.json +403 -0
  23. package/docs/brand-theme-bridge.md +81 -0
  24. package/docs/build-packet.md +180 -23
  25. package/docs/campaigns-os-build-flow.md +3 -3
  26. package/docs/design-source-package.md +73 -0
  27. package/docs/effects.md +50 -8
  28. package/docs/gateway-login.md +3 -0
  29. package/docs/local-setup.md +7 -4
  30. package/docs/orientation-contract-reference.md +42 -2
  31. package/docs/polish-evidence.md +74 -0
  32. package/docs/qa-and-test-orders.md +118 -14
  33. package/docs/release-ledger-authoring-guide.md +64 -4
  34. package/docs/runtime-readiness.md +1 -1
  35. package/docs/sdk-storage-compatibility.md +1 -1
  36. package/docs/skills-revision.md +10 -10
  37. package/docs/supported-surface.md +2 -2
  38. package/docs/versioning.md +4 -1
  39. package/package.json +1 -1
  40. package/schemas/campaigns-os-release-ledger.v1.schema.json +32 -2
  41. package/schemas/campaigns-os-tooling-orientation.v1.schema.json +1 -0
  42. package/skills/campaign-lifecycle-orientation/SKILL.md +16 -5
  43. package/skills/campaign-readback-classification/SKILL.md +3 -3
  44. package/skills/campaign-run-evidence/SKILL.md +7 -6
  45. package/skills/contribution-intake/SKILL.md +3 -3
  46. package/skills/next-campaigns-build/SKILL.md +7 -6
  47. package/skills/next-campaigns-os/SKILL.md +7 -7
  48. package/skills/next-campaigns-os/references/session-intake.md +9 -3
  49. package/skills/next-campaigns-os-setup/SKILL.md +5 -5
  50. package/skills/next-campaigns-polish/SKILL.md +28 -9
  51. package/skills/next-campaigns-qa/SKILL.md +7 -4
  52. package/skills.json +10 -10
  53. package/src/brand-theme.mjs +320 -20
  54. package/src/built-script-syntax.mjs +116 -15
  55. package/src/built-site-scope.mjs +16 -4
  56. package/src/cli.mjs +280 -46
  57. package/src/commercial-parity.mjs +48 -2
  58. package/src/deviation.mjs +13 -1
  59. package/src/diagnostic.mjs +6 -2
  60. package/src/doctor/checks.mjs +319 -81
  61. package/src/doctor/inspect.mjs +55 -13
  62. package/src/doctor/source-provenance.mjs +184 -0
  63. package/src/invocation.mjs +4 -0
  64. package/src/live-campaign-refs.mjs +466 -0
  65. package/src/login.mjs +2 -2
  66. package/src/page-kit-store-profile.mjs +69 -12
  67. package/src/page-kit-sync.mjs +31 -12
  68. package/src/progress-node.mjs +3 -1
  69. package/src/qa-analytics-parity.mjs +37 -2
  70. package/src/qa-binding-evidence.mjs +4 -2
  71. package/src/qa-browser.mjs +612 -40
  72. package/src/qa-commercial-parity.mjs +48 -5
  73. package/src/qa-node.mjs +122 -7
  74. package/src/qa-test-order-topology.mjs +148 -0
  75. package/src/sdk-markup.mjs +32 -7
  76. package/src/sdk-storage-compatibility.mjs +3 -2
  77. package/src/source-html-intake.mjs +116 -0
  78. package/src/stage-record.mjs +551 -0
  79. package/src/tooling-setup.mjs +9 -0
  80. package/src/upsell-selector-scope.mjs +112 -2
package/README.md CHANGED
@@ -45,9 +45,9 @@ steps, in this order:
45
45
 
46
46
  ```bash
47
47
  mkdir "<route>" && cd "<route>"
48
- npm init -y && npm i next-campaign-page-kit
48
+ npm init -y && npm i --save-exact next-campaign-page-kit
49
49
  npx campaign-init --non-interactive --template <family> --slug "<route>" --name "<campaign name>"
50
- npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.37.3
50
+ npm install --save-dev --save-exact @nextcommerce/campaigns-os@<version>
51
51
  npx --no-install campaigns-os tooling status --platform claude
52
52
  npx --no-install campaigns-os install-skills --platform claude
53
53
  mkdir -p source
@@ -57,7 +57,7 @@ The toolkit is also published to npm as `@nextcommerce/campaigns-os`, so the
57
57
  CLI can be installed once, globally, instead of pinned per campaign:
58
58
 
59
59
  ```bash
60
- npm install -g @nextcommerce/campaigns-os@1.37.3
60
+ npm install -g @nextcommerce/campaigns-os@<version>
61
61
  campaigns-os tooling status --platform claude
62
62
  campaigns-os install-skills --platform claude
63
63
  ```
@@ -73,9 +73,11 @@ runs the full check in an unprivileged job and publishes the verified tarball
73
73
  with provenance from a second, environment-gated job.
74
74
 
75
75
  For an existing page-kit campaign, skip the first three lines and `cd` into it
76
- (its `package.json` already declares `next-campaign-page-kit`). `1.37.3` is
77
- an exact published example; choose the release you reviewed, never a floating
78
- dist-tag for a reproducible build. Commit `package.json` and `package-lock.json`.
76
+ (its `package.json` already declares `next-campaign-page-kit`). `<version>` is
77
+ the exact release you reviewed. The newest published release is npm's `latest`
78
+ (`npm view @nextcommerce/campaigns-os version`), and
79
+ `contracts/release-ledger.json` and `CHANGELOG.md` list every release. Pin the
80
+ exact version, never a floating dist-tag, for a reproducible build. Commit `package.json` and `package-lock.json`.
79
81
  `tooling diagnose` requires 1.35.0 or later and `demo` requires 1.37.0 or later.
80
82
  A Git source pin remains supported when using an unreleased reviewed commit:
81
83
  `npm install --save-dev --save-exact "github:NextCommerceCo/campaigns-os#<full-sha>"`.
@@ -94,7 +96,10 @@ check registry currency or establish trust. On a fresh profile, preflight exits
94
96
  use `--platform codex` for a Codex-only profile. Without `--platform`, status
95
97
  checks every supported agent profile.
96
98
  `install-skills` writes `~/.claude/skills` (`--platform codex` writes
97
- `~/.codex/skills`), replacing same-name folders; restart the agent after.
99
+ `~/.codex/skills`), replacing same-name folders, and lists each `SKILL.md` it
100
+ wrote under `Read now`. A running agent does not load skills installed after it
101
+ started, so read those files now in the same session; a new session loads them
102
+ on its own.
98
103
  Run commands from the campaign folder: `npx` selects its local installation
99
104
  even when another global copy is on PATH. A global-only installation prints
100
105
  bare commands when its binary matches PATH, or an explicit `node` invocation
@@ -232,10 +237,25 @@ Then ask your AI tool to continue from the emitted handoff. Fresh target repos u
232
237
 
233
238
  The current source adapter is `html_funnel`: bring prepared HTML/CSS/assets for the campaign pages, plus a CampaignSpec exported from Campaign Map Builder or authored by the coding agent through the [local-spec entry](docs/build-packet.md#local-spec-entry).
234
239
 
235
- For raw AI-generated or exported static HTML, "prepared" means page-kit-ready
236
- source, not a browser document dropped in unchanged and not a wholesale Liquid
237
- rewrite. Page Kit source is HTML with YAML frontmatter and optional Liquid
238
- helpers. Convert standalone HTML into the target page format first: remove outer
240
+ Standalone HTML mockups: keep them whole and set
241
+ `wrapper_policy: preserve_document_wrappers` in the source-html manifest at
242
+ `<source>/.campaigns-os/source-html-manifest.json` (or pass
243
+ `--wrapper-policy preserve_document_wrappers` to `start`). Source screenshot
244
+ proof must be of the standalone document, so a page kept whole needs no
245
+ conversion; doctor reports its document wrappers as a warning that names the
246
+ decision. For pages without a Figma `design_source`, no exporter is required:
247
+ a hand-written manifest is enough (when any active page's `design_source` is
248
+ Figma, the manifest must pass the Figma provenance gate, which needs the
249
+ exporter's handoff manifest), for example
250
+ `{"schema_version": "source-html-manifest/v0", "wrapper_policy": "preserve_document_wrappers", "pages": [{"page_id": "landing", "path": "landing.html"}]}`
251
+ (schema: `schemas/source-html-manifest.v0.schema.json`; see
252
+ [Selecting the wrapper policy at intake](docs/source-adapters.md#selecting-the-wrapper-policy-at-intake)).
253
+
254
+ Otherwise, for raw AI-generated or exported static HTML, "prepared" means
255
+ page-kit-ready source, not a browser document dropped in unchanged and not a
256
+ wholesale Liquid rewrite. Page Kit source is HTML with YAML frontmatter and
257
+ optional Liquid helpers. Convert standalone HTML into the target page format
258
+ first: remove outer
239
259
  `<html>`, `<head>`, and `<body>` wrappers, add page frontmatter, move shared
240
260
  CSS/assets into the campaign asset tree when useful, root links/assets with
241
261
  `campaign_link` and `campaign_asset` when needed, and keep landing/presell
@@ -295,7 +315,8 @@ for a package install the pinned commit is the freshness answer, and there is
295
315
  no npm dist-tag to compare against. Neither mode makes agent skills current on
296
316
  its own: when skills are stale, run the refresh command the status output
297
317
  prints (it names each stale platform, through the same prefix you ran
298
- `tooling status` with) and restart local agent sessions. Without `--platform`,
318
+ `tooling status` with), then read the `SKILL.md` files it lists under `Read now`
319
+ in the running session (new sessions load them on their own). Without `--platform`,
299
320
  status checks only the platforms where Campaigns OS skills are installed.
300
321
 
301
322
  Run `campaigns-os qa install-browser` (`npm run qa:install-browser` from a
@@ -25,7 +25,7 @@ Core rules:
25
25
  - Do not copy Olympus-style `shipping_methods` frontmatter into `shop-three-step`; it uses dynamic shipping through `window.next.getShippingMethods()`.
26
26
  - Run build/lint checks and record evidence in the assembly report, then hand off to polish. 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>`.
27
27
  - After that checkpoint clears, QA uses the Campaigns OS Node/npm runner: run `campaigns-os qa resolve --packet campaign-runtime.build.json`, then run `campaigns-os qa run --packet campaign-runtime.build.json --base-url <url> --browser --test-order common`.
28
- - Typed-card test-order proof uses `campaigns-os qa run --test-order <common|checkout|decline|accept|both|full|explicit-path>` through the deployed checkout and rendered upsell controls. `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. Coverage is the only control — there is no permission/approval step.
28
+ - Typed-card test-order proof uses `campaigns-os qa run --test-order <common|checkout|decline|accept|both|full|explicit-path>` through the deployed checkout and rendered upsell controls. `common` runs every actual terminal path when they fit under the flood cap (`--max-test-orders`, 6 by default); above the cap it runs the checkout baseline, first-offer accept and decline, and a deduplicated shortest real receipt path, then adds one decline path per offer or downsell page not yet declined, up to the cap, and names any page left out. A page counts as covered only when an order clicks its decline; the `browser-test-order:upsell-action-coverage` verdict row warns naming each page whose decline no order clicked. `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. Coverage is the only control — there is no permission/approval step.
29
29
  - 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.
30
30
  - Test-order proof must use the canonical Playwright typed-card path through the tested checkout: select the rendered cart, fill customer/shipping fields, type the sandbox card into active hosted payment iframes, click the real submit button, then click rendered SDK upsell accept/decline controls and verify receipt/order evidence.
31
31
  - 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.
@@ -20,7 +20,7 @@ Use this context when working in a target campaign repo with Campaigns OS artifa
20
20
  - For `shop-three-step`, keep dynamic shipping via `window.next.getShippingMethods()` and do not add Olympus-style static `shipping_methods` frontmatter.
21
21
  - Build hands off to polish. 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>`.
22
22
  - After that checkpoint clears, QA uses the Campaigns OS Node/npm runner: run `campaigns-os qa resolve`, then `campaigns-os qa run --browser --test-order common` against the tested URL.
23
- - Typed-card 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 — coverage is the only control. `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. 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, the SDK test-mode event, or hand-built backend API orders as launch proof.
23
+ - Typed-card 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 — coverage is the only control. `common` runs every actual terminal path when they fit under the flood cap (`--max-test-orders`, 6 by default); above the cap it runs the checkout baseline, first-offer accept and decline, and a deduplicated shortest real receipt path, then adds one decline path per offer or downsell page not yet declined, up to the cap, and names any page left out. A page counts as covered only when an order clicks its decline; the `browser-test-order:upsell-action-coverage` verdict row warns naming each page whose decline no order clicked. `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. 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, the SDK test-mode event, or hand-built backend API orders as launch proof.
24
24
  - 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.
25
25
  - 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.
26
26
  - 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.
@@ -11,4 +11,4 @@ CampaignSpec validation is owned by the public `@nextcommerce/campaigns-os/campa
11
11
 
12
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
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.
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 every actual terminal path when they fit under the flood cap (`--max-test-orders`, 6 by default); above the cap it runs the checkout baseline, first-offer accept and decline, and a deduplicated shortest real receipt path, then adds one decline path per offer or downsell page not yet declined, up to the cap, and names any page left out. A page counts as covered only when an order clicks its decline; the `browser-test-order:upsell-action-coverage` verdict row warns naming each page whose decline no order clicked. `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.
@@ -10,4 +10,4 @@ Read Campaigns OS artifacts before editing campaign pages. Treat CampaignSpec/AP
10
10
 
11
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
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.
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 every actual terminal path when they fit under the flood cap (`--max-test-orders`, 6 by default); above the cap it runs the checkout baseline, first-offer accept and decline, and a deduplicated shortest real receipt path, then adds one decline path per offer or downsell page not yet declined, up to the cap, and names any page left out. A page counts as covered only when an order clicks its decline; the `browser-test-order:upsell-action-coverage` verdict row warns naming each page whose decline no order clicked. `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.
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * AnalyticsContractShape — validates the optional top-level `analytics` block
3
3
  * when present. The block declares a campaign's analytics/attribution/param
4
- * contract so doctor + QA can validate against intent (cf. the Chamelo Shield
5
- * `?reviews=n`-has-no-handler QA finding and the Walla Sound Redtrack param
4
+ * contract so doctor + QA can validate against intent (cf. a production
5
+ * `?reviews=n`-has-no-handler QA finding and a production Redtrack param
6
6
  * conflict — both are gaps that had no declared contract to check against).
7
7
  *
8
8
  * The block is fully OPTIONAL — a spec without `analytics` is silent (SDK
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * AnalyticsContractShape — validates the optional top-level `analytics` block
3
3
  * when present. The block declares a campaign's analytics/attribution/param
4
- * contract so doctor + QA can validate against intent (cf. the Chamelo Shield
5
- * `?reviews=n`-has-no-handler QA finding and the Walla Sound Redtrack param
4
+ * contract so doctor + QA can validate against intent (cf. a production
5
+ * `?reviews=n`-has-no-handler QA finding and a production Redtrack param
6
6
  * conflict — both are gaps that had no declared contract to check against).
7
7
  *
8
8
  * The block is fully OPTIONAL — a spec without `analytics` is silent (SDK
@@ -3,7 +3,11 @@
3
3
  * profile fields introduced in Slice 4f.
4
4
  *
5
5
  * 1. campaign.store_phone_tel must be a `tel:`-prefixed URI containing
6
- * a digit-shaped number when present. The build wires this value
6
+ * a digit-shaped number when present. Absent or null means "not
7
+ * provided" and is silent. An empty (or whitespace-only)
8
+ * string is not a defect: for the nine Store Profile fields an explicit
9
+ * "" says the merchant has no such value, and `page-kit sync` blanks
10
+ * the starter's demo value with it. The build wires this value
7
11
  * into `<a href="tel:...">` attributes; a value without the scheme
8
12
  * renders a broken link, and a value containing HTML metacharacters
9
13
  * or alternative schemes (javascript:, data:) is rejected as a
@@ -3,7 +3,11 @@
3
3
  * profile fields introduced in Slice 4f.
4
4
  *
5
5
  * 1. campaign.store_phone_tel must be a `tel:`-prefixed URI containing
6
- * a digit-shaped number when present. The build wires this value
6
+ * a digit-shaped number when present. Absent or null means "not
7
+ * provided" and is silent. An empty (or whitespace-only)
8
+ * string is not a defect: for the nine Store Profile fields an explicit
9
+ * "" says the merchant has no such value, and `page-kit sync` blanks
10
+ * the starter's demo value with it. The build wires this value
7
11
  * into `<a href="tel:...">` attributes; a value without the scheme
8
12
  * renders a broken link, and a value containing HTML metacharacters
9
13
  * or alternative schemes (javascript:, data:) is rejected as a
@@ -42,8 +46,8 @@ export const StoreProfileShape = {
42
46
  check(spec) {
43
47
  const violations = [];
44
48
  const campaign = spec.campaign;
45
- // 1. tel: prefix on store_phone_tel.
46
- if (campaign?.store_phone_tel !== undefined) {
49
+ // 1. tel: prefix on store_phone_tel. Absent or null means "not provided".
50
+ if (campaign?.store_phone_tel !== undefined && campaign?.store_phone_tel !== null) {
47
51
  const value = campaign.store_phone_tel;
48
52
  if (typeof value !== 'string') {
49
53
  violations.push({
@@ -55,13 +59,7 @@ export const StoreProfileShape = {
55
59
  });
56
60
  }
57
61
  else if (!isNonEmptyString(value)) {
58
- violations.push({
59
- ruleId: 'StoreProfileShape',
60
- severity: 'warning',
61
- message: 'campaign.store_phone_tel is set but empty; remove the field or set a tel:-prefixed value (e.g. "tel:+18005551234").',
62
- path: '/campaign/store_phone_tel',
63
- data: { check: 'store-phone-tel-empty' },
64
- });
62
+ // Intentionally empty: the merchant has no phone yet.
65
63
  }
66
64
  else {
67
65
  const trimmed = value.trim();
@@ -496,8 +496,8 @@ export interface Campaign {
496
496
  /**
497
497
  * Analytics & attribution contract (Slice 4g) — what a campaign's analytics,
498
498
  * tag-management, and querystring-param tracking are SUPPOSED to be, so doctor
499
- * + QA can validate them instead of discovering gaps in QA (cf. the Chamelo
500
- * Shield `?reviews=n`-has-no-handler finding and the Walla Sound Redtrack/
499
+ * + QA can validate them instead of discovering gaps in QA (cf. a production
500
+ * `?reviews=n`-has-no-handler finding and a production Redtrack/
501
501
  * campaign.js sub1-6 param conflict).
502
502
  *
503
503
  * Modeled on real production-funnel usage, NOT the idealized "SDK fires the
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@nextcommerce/campaigns-os",
3
- "version": "1.34.0",
3
+ "version": "1.47.0",
4
4
  "status": "developer-preview",
5
5
  "contracts": {
6
6
  "campaign_spec": "4.2-4.3",