@nextcommerce/campaigns-os 1.59.0 → 1.61.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 (96) hide show
  1. package/CHANGELOG.md +242 -0
  2. package/README.md +11 -10
  3. package/agents/claude/CLAUDE.md +15 -0
  4. package/agents/codex/AGENTS.md +15 -0
  5. package/agents/copilot/copilot-instructions.md +15 -0
  6. package/agents/cursor/campaigns-os.mdc +15 -0
  7. package/compatibility.json +1 -1
  8. package/contracts/brand-theme-target-tokens.next-core.v0.json +95 -25
  9. package/contracts/campaign-cart-sdk-support-policy.v0.json +2 -2
  10. package/contracts/commerce-surface-catalog.json +27 -26
  11. package/contracts/release-ledger.json +1311 -0
  12. package/contracts/supported-surface.json +4 -4
  13. package/contracts/template-brand-contract.shared-commerce.v0.json +1 -1
  14. package/docs/access-model.md +20 -0
  15. package/docs/brand-theme-bridge.md +62 -3
  16. package/docs/build-packet.md +42 -13
  17. package/docs/campaign-build-brief.md +17 -1
  18. package/docs/campaigns-os-build-flow.md +62 -3
  19. package/docs/campaignspec-authoring-examples.md +116 -0
  20. package/docs/design-source-package.md +46 -2
  21. package/docs/entry-points.md +188 -0
  22. package/docs/local-setup.md +1 -1
  23. package/docs/orientation-contract-reference.md +1 -1
  24. package/docs/polish-evidence.md +41 -3
  25. package/docs/qa-and-test-orders.md +50 -2
  26. package/docs/quickstart.md +438 -0
  27. package/docs/readback.md +119 -6
  28. package/docs/runtime-readiness.md +1 -1
  29. package/docs/sdk-storage-compatibility.md +3 -1
  30. package/docs/skills-revision.md +10 -10
  31. package/docs/source-adapters.md +213 -0
  32. package/package.json +10 -3
  33. package/schemas/campaign-design-source-package.v0.schema.json +8 -0
  34. package/schemas/campaigns-os-readback.v2.schema.json +177 -1
  35. package/schemas/source-html-manifest.v0.schema.json +3 -0
  36. package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
  37. package/skills/campaign-readback-classification/SKILL.md +3 -3
  38. package/skills/campaign-run-evidence/SKILL.md +3 -3
  39. package/skills/contribution-intake/SKILL.md +3 -3
  40. package/skills/next-campaigns-build/SKILL.md +5 -5
  41. package/skills/next-campaigns-os/SKILL.md +4 -3
  42. package/skills/next-campaigns-os-setup/SKILL.md +3 -3
  43. package/skills/next-campaigns-polish/SKILL.md +12 -16
  44. package/skills/next-campaigns-qa/SKILL.md +3 -3
  45. package/skills/next-campaigns-sdk-bump/SKILL.md +286 -0
  46. package/skills.json +18 -10
  47. package/src/brand-theme.mjs +413 -8
  48. package/src/brief-summary.mjs +6 -2
  49. package/src/browser-launch.mjs +2 -1
  50. package/src/build-brief.mjs +9 -2
  51. package/src/built-site-scope.mjs +7 -14
  52. package/src/campaign-identity.mjs +6 -4
  53. package/src/cli.mjs +89 -73
  54. package/src/design-file-reference.mjs +93 -0
  55. package/src/design-source-package.mjs +55 -1
  56. package/src/doctor/checks.mjs +14 -77
  57. package/src/doctor/ci.mjs +112 -0
  58. package/src/doctor/inspect.mjs +20 -8
  59. package/src/doctor/next-step.mjs +6 -3
  60. package/src/doctor/source-provenance.mjs +74 -0
  61. package/src/evidence-debt.mjs +119 -0
  62. package/src/orchestration-stage-contract.mjs +6 -3
  63. package/src/page-kit-build-summary.mjs +21 -1
  64. package/src/page-kit-campaign-config.mjs +19 -0
  65. package/src/parity/compare.mjs +316 -0
  66. package/src/parity/design-access.mjs +54 -0
  67. package/src/parity/element-parity-record.mjs +569 -0
  68. package/src/parity/probe.mjs +205 -0
  69. package/src/parity/snapshot.mjs +110 -0
  70. package/src/png-header.mjs +87 -0
  71. package/src/polish-browser.mjs +186 -9
  72. package/src/polish-captures-record.mjs +52 -0
  73. package/src/polish-deadline.mjs +14 -0
  74. package/src/polish-gate.mjs +26 -11
  75. package/src/polish-node.mjs +81 -7
  76. package/src/polish-visual-fixtures.mjs +74 -0
  77. package/src/polish-visual.mjs +606 -0
  78. package/src/progress-node.mjs +2 -1
  79. package/src/qa-analytics-correctness.mjs +14 -2
  80. package/src/qa-analytics-parity.mjs +94 -5
  81. package/src/qa-node.mjs +39 -6
  82. package/src/qc-accept.mjs +148 -6
  83. package/src/qc-check-registry.mjs +37 -1
  84. package/src/qc-results.mjs +404 -16
  85. package/src/readback.mjs +57 -13
  86. package/src/sdk-attribute-index.mjs +11 -11
  87. package/src/sdk-markup.mjs +77 -2
  88. package/src/sdk-storage-compatibility.mjs +94 -4
  89. package/src/source-html-manifest.mjs +7 -0
  90. package/src/source-html-server.mjs +129 -0
  91. package/src/spec-derive.mjs +24 -9
  92. package/src/stage-ledger.mjs +27 -1
  93. package/src/stage-record.mjs +63 -9
  94. package/src/theme-gate.mjs +2 -2
  95. package/src/visual-diff.mjs +296 -0
  96. package/src/visual-reference.mjs +126 -0
@@ -0,0 +1,438 @@
1
+ # Quickstart
2
+
3
+ Read [activation, access, and evidence](activation-and-evidence.md) before
4
+ interpreting an installed toolkit, saved Map, demo, or preview as campaign proof.
5
+
6
+ This path is optimized for a developer using Claude Code or another AI coding tool with a prepared campaign design.
7
+
8
+ For Claude Code, [local campaign setup](local-setup.md) combines the pinned
9
+ dependency install, skills, connected project instructions and QA browser in
10
+ one setup entry. It preserves authored source and project instructions. The
11
+ detailed/manual paths below remain supported.
12
+
13
+ ## Install without a clone
14
+
15
+ Requirements: Node `>=20.19.0` and npm 10 or 11 (Node 22 ships npm 10). The
16
+ primary way to run Campaigns OS is pinned as a devDependency of the campaign
17
+ folder — a page-kit project — and run through `npx --no-install campaigns-os …`
18
+ from that folder. Nothing is cloned, nothing goes on PATH, and the pin is
19
+ committed in `package.json`, so CI and the deploy host install the same package
20
+ bytes. `--no-install` is part of the command: `campaigns-os` is only the bin
21
+ name of `@nextcommerce/campaigns-os`, so in a folder without the pinned copy a
22
+ plain `npx campaigns-os` looks that name up on the registry and, with no
23
+ terminal to ask, installs what it finds. With the flag, npx fails instead.
24
+
25
+ Do the steps in this order:
26
+
27
+ 1. **Orient.** Read [`AGENTS.md`](../AGENTS.md), `contracts/supported-surface.json`,
28
+ `contracts/release-ledger.json` and `CHANGELOG.md` on GitHub at one commit.
29
+ Follow its canonical reading order. That is a read of declarative data,
30
+ not a run of toolkit code. Keep the commit sha.
31
+ 2. **Pin the toolkit, preflight, and install skills** at the exact reviewed release version.
32
+ 3. **Start** at the exact reviewed release version.
33
+
34
+ New campaign folder:
35
+
36
+ ```bash
37
+ mkdir "<route>" && cd "<route>"
38
+ npm init -y && npm i --save-exact next-campaign-page-kit@0.3.0
39
+ npx campaign-init --non-interactive --template <family> --slug "<route>" --name "<campaign name>"
40
+ ```
41
+
42
+ Existing page-kit campaign: `cd` into it (its `package.json` declares
43
+ `next-campaign-page-kit`). Then, in the campaign folder:
44
+
45
+ ```bash
46
+ npm install --save-dev --save-exact @nextcommerce/campaigns-os@<version>
47
+ npx --no-install campaigns-os tooling status --platform claude
48
+ ```
49
+
50
+ `<version>` is the exact release you reviewed. The newest published release is
51
+ npm's `latest` (`npm view @nextcommerce/campaigns-os version`), and
52
+ `contracts/release-ledger.json` and `CHANGELOG.md` list every release. Verify
53
+ its tag/provenance against the source commit; do not use a floating dist-tag. Commit both `package.json` and `package-lock.json`. An unreleased
54
+ reviewed commit may instead be pinned with
55
+ `npm install --save-dev --save-exact "github:NextCommerceCo/campaigns-os#<full-sha>"`.
56
+ The package install runs its own lifecycle build; it is separate from the
57
+ checkout-only runtime preparation recipe and makes no claim under that recipe.
58
+ Diagnostics and corrected global invocation rendering require 1.35.0 or later;
59
+ `demo` requires 1.37.0 or later.
60
+
61
+ For an optional visual walkthrough,
62
+ `npx --no-install campaigns-os demo --target ./apollo-sample`
63
+ writes an offline sample. Open the printed local
64
+ `landing/index.html` file. This offline sample has no live commerce or campaign
65
+ proof. Preserve sample edits and create a separate new Page Kit folder for the
66
+ real campaign. See [offline sample preview](demo-preview.md).
67
+
68
+ Global use is also supported, with an exact release:
69
+
70
+ ```bash
71
+ npm install -g @nextcommerce/campaigns-os@<version>
72
+ campaigns-os tooling status --platform claude
73
+ campaigns-os install-skills --platform claude
74
+ ```
75
+
76
+ Global Git-source installation is not the supported path: npm's nested build
77
+ can inherit global mode. Global registry installations ship without Chromium;
78
+ run `campaigns-os qa install-browser` once. If optional Playwright was omitted,
79
+ ordinary commands still work and browser commands explain the missing package.
80
+ When another install shadows the global binary, status prints an explicit
81
+ invocation of the inspected copy. In a campaign folder,
82
+ `npx --no-install campaigns-os` selects the project-local dependency ahead of
83
+ the global binary on PATH.
84
+
85
+ `tooling status` is the preflight for "am I current?". It names the install
86
+ mode and checks package identity, CLI entrypoint, and installed Campaigns OS
87
+ skills. It also reports local gateway login metadata across saved store bindings,
88
+ without a `--store` flag or a remote validity check. No credential values are
89
+ shown; unavailable storage is reported without guessing that the user is logged
90
+ out. See [gateway login](gateway-login.md).
91
+
92
+ - From a campaign folder it reports `Install mode: package install
93
+ (node_modules), pinned at <version> @ <sha>`; git freshness is
94
+ `not_applicable` because the pinned commit is the freshness answer, and
95
+ there is no npm dist-tag to compare against. An npm release reports its version even when a source commit is not derivable.
96
+ Commands are spelled `npx --no-install campaigns-os <command>`; if a
97
+ different install of the toolkit is on PATH, it says so and points you back
98
+ to `npx`.
99
+ - From a git checkout it reports `Install mode: git checkout at <path>` plus
100
+ branch, upstream, ahead/behind, and whether the tree is dirty.
101
+
102
+ Exit code 2 from `tooling status` means attention is required — on a fresh
103
+ profile that is the not-yet-installed skills — and the output prints the exact
104
+ refresh command for the mode you ran it in. Pass the same `--platform` to
105
+ status that you install skills for; without it, status checks every agent
106
+ profile and stays at exit 2 until each is installed.
107
+
108
+ Everything the toolkit prints for you to run (`next`, gate remediations, the
109
+ browser-missing hints) is spelled for the install it came from: `npx
110
+ --no-install campaigns-os …` from a campaign folder (releases before 1.41.2
111
+ print it without `--no-install`; add the flag when you copy one), bare
112
+ `campaigns-os …` from a global installation whose binary matches PATH, and the
113
+ documented checkout translation below.
114
+
115
+ ## Contributor / local checkout
116
+
117
+ To change the toolkit, run `npm run check`, or work against an unpushed commit,
118
+ use a checkout:
119
+
120
+ ```bash
121
+ git clone https://github.com/NextCommerceCo/campaigns-os.git
122
+ cd campaigns-os
123
+ npm install
124
+ npm run campaigns-os -- --help
125
+ npm run campaigns-os -- tooling status
126
+ ```
127
+
128
+ Every `npm run campaigns-os -- <command>` example in this repository is the
129
+ checkout form. From a campaign folder the same command is `npx --no-install
130
+ campaigns-os <command>`; the arguments are identical. `npm run
131
+ qa:install-browser`, a checkout script, is `npx --no-install campaigns-os qa
132
+ install-browser` from a campaign folder (`npx playwright install chromium` is
133
+ equivalent only when it resolves to the toolkit's own Playwright; prefer
134
+ `qa install-browser` on pins that have it). A fresh `git pull`
135
+ does not refresh copied agent skills in either mode; `tooling status` tells
136
+ you when they are stale.
137
+
138
+ ## Install Skills
139
+
140
+ After installing or updating the CLI, refresh the Campaigns OS skills in Claude Code:
141
+
142
+ ```bash
143
+ npx --no-install campaigns-os install-skills --platform claude
144
+ ```
145
+
146
+ This syncs bundled `skills/*` directories from the installed package into `~/.claude/skills/<skill-name>/` (`--platform codex` writes `~/.codex/skills`), replacing same-name folders, and reports which skills were created, updated, or unchanged. It lists each `SKILL.md` it wrote under `Read now`: read those files now in the same session, because a running agent does not load skills installed after it started (a new session loads them on its own). Preview changes without writing files:
147
+
148
+ ```bash
149
+ npx --no-install campaigns-os install-skills --dry-run
150
+ ```
151
+
152
+ Use `--platform` for other local agent profiles:
153
+
154
+ ```bash
155
+ npx --no-install campaigns-os install-skills --platform codex
156
+ npx --no-install campaigns-os install-skills --platform agents
157
+ npx --no-install campaigns-os install-skills --platform all --dry-run
158
+ ```
159
+
160
+ If `tooling status` reports stale skills, run the refresh command it prints —
161
+ it names each stale platform, through the same prefix — and read the
162
+ `SKILL.md` files it lists under `Read now` in the running session so the new
163
+ instructions are loaded (new sessions load them on their own). The build after `start` is
164
+ agent-driven — `next` names the skill for each stage — so install skills before
165
+ the first `start`.
166
+
167
+ From a local checkout, `./skills.sh status` previews every known local target
168
+ and `./skills.sh install codex` refreshes Codex skills. Use `--target <dir>` to
169
+ sync into another skills directory for testing or a managed profile.
170
+
171
+ ## Inputs
172
+
173
+ You need:
174
+
175
+ - A Campaigns App campaign with shipping methods, payment methods, an API key, and Offers for its price tiers. The Offers bring in the campaign's products (packages in the API); see the setup note below.
176
+ - CampaignSpec JSON exported from a saved Map or authored by the coding agent through the [local-spec entry](build-packet.md#local-spec-entry), including `campaign.store_url` for page-kit `campaigns.json`. Local-spec identity requires a reviewed 1.43.0-or-later release.
177
+ - Prepared HTML/CSS/assets for the campaign pages.
178
+ - A target `next-campaign-page-kit` repo or local directory.
179
+ - A starter template family decision, usually `olympus` unless `demeter` or `shop-single-step` better matches the campaign shape.
180
+
181
+ If the checkout should have an exit-intent offer or typed promo-code box,
182
+ configure it in Campaign Map Builder before export, or declare it in the local
183
+ spec using verified campaign Offer values, so the checkout page carries
184
+ the mapped `exit_intent.offer_ref_id` / `exit_intent.offer_code` or
185
+ `promo_code_input.offer_ref_id` / `promo_code_input.offer_code`.
186
+
187
+ Campaigns API keys are public, browser-side, domain-allowlisted keys. If your CampaignSpec includes `campaign.campaigns_api_key`, `doctor` uses it directly and does not require a `CAMPAIGNS_API_KEY` shell env var.
188
+
189
+ The Store Profile is campaign metadata entered by the operator or derived from the store with `spec derive --from-store` (see "Create The Packet" below), not Campaigns API data. Before the target is scaffolded, `doctor` requires only `campaign.store_url`; `store_name`, `store_terms`, `store_privacy`, `store_contact`, `store_returns`, `store_shipping`, `store_phone`, and `store_phone_tel` are optional storefront/legal metadata used by templates when present. Once the target's `campaigns.json` entry exists, `page_kit.store_profile` checks every one of those fields the CampaignSpec provides against the target: a field the spec carries that the target lacks, or carries with a different value, blocks (the spec is the authority; run `npx --no-install campaigns-os page-kit sync --packet <campaign-runtime.build.json>` to write the spec's values into the target entry, or fix the spec, then re-run `doctor`), a field present only in the target warns as `target_only` — unless the value is starter demo residue (a placeholder storefront URL or phone number), which blocks as `demo_residue` whatever the spec says — and a field absent from both is clean. An optional field (any of the eight besides `store_url`) the spec sets to `""` says the merchant has none: `page-kit sync` blanks a recognised starter demo value (the placeholder storefront URLs and phone number; the starter's demo store name is not recognised and stays a `target_only` warning) and doctor reads the blank field as `intentionally_empty`, while a real, non-demo target value is left as it is (still a `target_only` warning, and sync reports the `""` as not applied; remove the value by hand if the merchant has none); an absent or null field still means "not provided". `campaign.store_url` stays required: `store_url: ""` still fails doctor's `spec.store_profile` check. So a spec that fills all nine fields makes all nine required after scaffold. A discrepancy the spec cannot yet resolve can be recorded with `campaigns-os checkpoint waive --packet <campaign-runtime.build.json> --gate page_kit.store_profile --reason "<why>" --waived-by "<named human>" --review-condition "<trigger>"` (or `--expires-at <ISO timestamp>` instead of the review condition; see [docs/build-packet.md](./build-packet.md), "Page Kit Store Profile checkpoint").
190
+
191
+ In the Campaigns App dashboard ([Campaigns App docs](https://docs.nextcommerce.com/docs/apps/campaigns-app), which name these screens and tabs), setup takes two steps: **Create Campaign** (name, currency, shipping, payments), then **Add Offer** on the campaign's **Offers** tab. Picking store products in the Offer's condition adds each selected variant to the campaign as one product at its store price when the Offer is saved, and later Offers reuse it. Pages reference those products by package ID, which the campaign's **Products** tab shows on each row. Offers set the customer's final price: Buy 1, Buy 2 and Buy 3 are separate Offers over the same products. Do not create separate `1x` / `2x` / `3x` products to express tier pricing, and do not rely on Retail Price/Quantity fields unless the campaign already uses that legacy setting (**Enable Package Retail Price & Quantity**). A campaign set up over the Admin API still creates its packages first and points Offers at their package IDs.
192
+
193
+ For synthetic or AI-generated evaluation campaigns, create or reuse a known test
194
+ store/API key before checkout work. A purely static source page can validate the
195
+ landing-page assembly path, but SDK checkout, package, shipping, payment, and
196
+ receipt surfaces need Campaigns App data; without it, checkout can remain in a
197
+ loading state. Record the test store/key choice in the Build Packet or
198
+ CampaignSpec so QA knows whether runtime checkout proof is expected or blocked.
199
+
200
+ For partial campaign work, map only the pages being built with
201
+ `source_html.pages[].path` and give intentionally untouched CampaignSpec pages a
202
+ clear `skip_reason`. Doctor will surface those pages under `derived.scope`,
203
+ label mapped routes as previewable, and keep checkout launch/test-order proof
204
+ blocked when runtime pages are out of scope.
205
+
206
+ `source_html.pages[].path` is the source/provenance path. Use
207
+ `source_html.pages[].page_kit` for the Page Kit target file, public route, CPK
208
+ `page_type`, and frontmatter projection. This matters for mixed sources where a
209
+ producer path such as `checkout/index.html` should still target
210
+ `src/<slug>/checkout.html` unless the CampaignSpec route requires a permalink.
211
+
212
+ ## Prepare Raw HTML Source
213
+
214
+ **Standalone HTML mockups: keep them whole, set
215
+ `wrapper_policy: preserve_document_wrappers`.** Put it in the source-html
216
+ manifest at `<source>/.campaigns-os/source-html-manifest.json` (or pass
217
+ `--wrapper-policy preserve_document_wrappers` to `start` / `prepare-build`).
218
+ Source screenshot proof must be of the standalone document, so a page kept whole
219
+ needs no conversion, and doctor reports its document wrappers as a warning that
220
+ names the decision. For pages without a Figma `design_source`, no exporter is
221
+ required: for hand-written HTML, write the manifest yourself. When any active
222
+ page's `design_source` is Figma, the manifest must pass the Figma provenance
223
+ gate (`source_html.producer_provenance`), which needs the exporter's handoff
224
+ manifest. A minimal one (schema:
225
+ `schemas/source-html-manifest.v0.schema.json`):
226
+
227
+ ```json
228
+ {
229
+ "schema_version": "source-html-manifest/v0",
230
+ "wrapper_policy": "preserve_document_wrappers",
231
+ "pages": [{ "page_id": "landing", "path": "landing.html" }]
232
+ }
233
+ ```
234
+
235
+ Decide this before capturing screenshots or computing `source_hash`; the rest
236
+ of this section is the conversion for source that is not kept whole.
237
+
238
+ Otherwise, `html_funnel` source files should be page-kit-ready source, not full browser
239
+ documents copied verbatim from an AI tool. This is not a wholesale Liquid
240
+ rewrite. Use Liquid only for page-kit helpers such as `campaign_link`,
241
+ `campaign_asset`, and `campaign_include`.
242
+
243
+ The conversion process used in prior builds is:
244
+
245
+ - Strip document-level wrappers: `<!doctype>`, `<html>`, `<head>`, and `<body>`.
246
+ - Add page frontmatter for title, layout, route/meta values, and any source mapping notes.
247
+ - Move shared CSS into the campaign asset tree or an include when it is reused; inline page-specific CSS only when the target page-kit style allows it.
248
+ - Move local images/fonts/assets into the campaign asset tree and root paths with `campaign_asset` when needed. Page Kit publishes `src/<slug>/assets/config.js` as `/<slug>/config.js` and `src/<slug>/assets/products/foo.png` as `/<slug>/products/foo.png`; raw `/assets/...` paths from AI/exported HTML are source paths, not built campaign URLs.
249
+ - Check `context.source.asset_crawl` in `.campaign-runtime/build-context.json` before rewriting assets. `prepare-build` crawls source HTML plus referenced local CSS, records raw refs, resolved source files, missing/out-of-root assets, and `pagekit_asset_path` hints so assembly does not have to rediscover `/assets/...` path behavior page by page.
250
+ - Replace internal links/CTAs with CampaignSpec routes, usually via `campaign_link`.
251
+ - Keep source landing/presell composition and copy intact when it is a real design.
252
+ - Use starter-template SDK contracts for checkout, upsell, downsell, receipt, payment, totals, and submit controls.
253
+ - Run page-kit build and inspect `_site/<slug>/` before handing off to polish.
254
+
255
+ Doctor checks this preparation deterministically: unstripped document wrappers
256
+ and leftover/broken frontmatter block with `source_html.prep.*` error codes,
257
+ and source-file internal links warn. See the "Source preparation check" section
258
+ in [docs/source-adapters.md](./source-adapters.md) for each code and its fix.
259
+ If the source is meant to stay a full browser document (raw HTML handed over
260
+ as-is, or pages whose `source_screenshot` proof must be of the standalone
261
+ document), do not strip it: record the decision instead with
262
+ `--wrapper-policy preserve_document_wrappers` on `start` / `prepare-build`, or
263
+ `wrapper_policy` in the source-html manifest, and the wrapper finding becomes a
264
+ warning that names the decision. Choose before capturing screenshots or
265
+ computing `source_hash`; the "Selecting the wrapper policy at intake" section of
266
+ [docs/source-adapters.md](./source-adapters.md) gives the order of operations.
267
+
268
+ ## Create The Packet
269
+
270
+ > **Heads up — `start` turns on run telemetry, and remit is ON by default.**
271
+ > `start` (and `prepare-build`) opens an ambient Run Session in the target repo.
272
+ > When that session's Run Record is assembled — by a ready `qa run`, by
273
+ > `campaigns-os run end`, or by the automatic closeout of a stale session — it is
274
+ > **remitted to the canonical Next Commerce endpoint,
275
+ > `https://campaign-map.nextcommerce.com`**, without asking again.
276
+ >
277
+ > What is sent: the Run Record (the run's own system signal and workflow
278
+ > findings — stage timings, findings, tool/environment shape), plus the packet's
279
+ > Campaigns API key in the `X-Campaign-Key` header so the record lands in your
280
+ > tenant scope and you can read it back with `campaigns-os telemetry list
281
+ > --packet <json>`. Campaigns API keys are public, browser-side,
282
+ > domain-allowlisted keys by design, so this is attribution, not a secret.
283
+ >
284
+ > Why: those run records are what the toolkit learns from — they are how
285
+ > templates, gates, and guidance get fixed.
286
+ >
287
+ > Three ways out, any of which is enough:
288
+ >
289
+ > - `npx --no-install campaigns-os telemetry off` — machine-level, sticks.
290
+ > - `CAMPAIGNS_OS_TELEMETRY=off` — per shell or per CI job.
291
+ > - `--no-remit` on the remitting command (`qa run`, `run-record`, `run end`).
292
+ >
293
+ > Consent gates remit only. Capture is always local, so opting out costs you
294
+ > nothing locally: sessions, journals and Run Records are still written and
295
+ > the Run Record's `remit_state` reads `skipped`. Turning consent off at the
296
+ > machine or environment level (`telemetry off`, `CAMPAIGNS_OS_TELEMETRY=off`)
297
+ > also makes saved-Map `qa run` default to a local-only verdict unless the campaign is
298
+ > portal-managed or `--post-verdict` is passed; `--no-remit` does not — it
299
+ > skips this command's remit only, and the verdict still publishes. Whatever
300
+ > the consent, a run whose base URL is a local address (`localhost` or any
301
+ > `*.localhost` name, any `127.x.x.x`, `0.0.0.0`, `[::1]` or an IPv4-mapped
302
+ > loopback) keeps its verdict local by default; pass
303
+ > `--post-verdict`, or run `qa publish` afterwards, to send it. To keep
304
+ > one saved-Map verdict local, pass `--no-post-verdict` (or `--local-only`) to `qa run`.
305
+ > A `local_spec_id` packet always keeps its QA verdict and progress local,
306
+ > including with `--post-verdict`; `qa publish` refuses it. Run Record remit
307
+ > still follows the consent controls above.
308
+ > `--no-run-session` on `start` skips opening the session altogether. Full
309
+ > contract: [Run Telemetry](./workflow-findings-sidecar.md).
310
+
311
+ ```bash
312
+ mkdir -p source
313
+ npx --no-install campaigns-os start --map-id <map-id> --target . --source ./source --template-family <family>
314
+ ```
315
+
316
+ `--map-id <id>` starts from a map saved in Campaign Map Builder (add
317
+ `--proxy-base <origin>` when the map was saved on a non-production map store);
318
+ `--spec <campaignspec.json>` starts from a local saved-Map export or an
319
+ agent-authored local CampaignSpec instead. One of the two is required.
320
+ `--target .` is the campaign folder, the
321
+ page-kit project that pins the toolkit; with `--spec` it must already be a
322
+ directory (`Target repo is not a directory` otherwise). `--source` is always
323
+ required: the folder of prepared HTML/CSS/assets for the pages you are
324
+ building, with a source manifest carrying desktop and mobile screenshot proof
325
+ for each designed page — see [Design Source Package](./design-source-package.md).
326
+ It must exist even when every page is template stock; those pages are declared
327
+ out of source scope (manifest `skip_reason`, or CampaignSpec
328
+ `build_scope.mode: "partial"`), never left out of `--source`.
329
+
330
+ `start` creates the packet, context, report, doctor output, and target-repo agent context. It does not edit campaign pages, deploy, run QA, or place test orders.
331
+
332
+ `start` finishes by running doctor, and on a fresh target doctor's first verdict
333
+ is normally `BLOCKED` with a list of what to supply — missing screenshot proof,
334
+ demo values to replace, a scaffold to run. That list is the intake checklist,
335
+ not a failed install; work through it and re-run. The demo values are the
336
+ store profile and SDK pin `campaign-init` seeded into `_data/campaigns.json`
337
+ after a fresh scaffold; doctor's required actions print the command that
338
+ replaces them from the CampaignSpec — `npx --no-install campaigns-os page-kit
339
+ sync --packet campaign-runtime.build.json` (`--dry-run` to see the diff first)
340
+ — and after it `page_kit.store_profile` and `page_kit.sdk_version` pass without a waiver.
341
+
342
+ The other direction exists too. Once the campaign is configured, the repo is
343
+ the authority for the SDK pin, the page routes and the analytics ids, and
344
+ `npx --no-install campaigns-os spec derive --packet campaign-runtime.build.json` writes
345
+ those into the local CampaignSpec with a field-by-field diff (`--dry-run`
346
+ first). After a bump in `_data/campaigns.json`, that is the one command that
347
+ brings the spec back in line; doctor's `page_kit.sdk_version.repo_newer`
348
+ warning names it. Add `--write-map` and the pin is also recorded in the saved
349
+ Map's Build hints field, so the Map and its next export stop reading stale.
350
+ The store profile (`campaign.store_*`) comes from the store
351
+ itself: the 1.38.0 candidate uses gateway login for `--from-store <subdomain>`
352
+ within the admitted owned-store private pilot. Existing direct callers must add
353
+ `--store-token-source env:<VAR>` explicitly; there is no implicit environment
354
+ fallback. See [gateway login and migration](gateway-login.md). A successful read
355
+ writes the available store name,
356
+ primary domain, phone and policy-page URLs into the spec too, for `page-kit
357
+ sync` to carry into the repo.
358
+
359
+ It also runs brand-theme discovery in inspect-only mode. When source tokens are
360
+ available, the build context records `context.theme` and the target repo gets
361
+ `.campaign-runtime/theme/theme-report.json`. It does not write
362
+ `brand-theme.css` by default.
363
+
364
+ To inspect or generate the optional commerce-page brand bridge:
365
+
366
+ ```bash
367
+ npx --no-install campaigns-os theme inspect --packet ./campaign-runtime.build.json --json
368
+ npx --no-install campaigns-os theme generate --packet ./campaign-runtime.build.json --json
369
+ ```
370
+
371
+ Use `--theme-policy auto` on `start` / `prepare-build` only when you want
372
+ Campaigns OS to write `brand-theme.css` automatically from high-confidence
373
+ source tokens. Generated CSS is root-variable-only and must be loaded after
374
+ `next-core.css` on checkout, upsell, downsell, and receipt pages.
375
+
376
+ ## Continue In Your AI Tool
377
+
378
+ Run `next` after `start` and after every stage, and do what it prints — the
379
+ commands are already spelled for this install:
380
+
381
+ ```bash
382
+ npx --no-install campaigns-os next --packet ./campaign-runtime.build.json --json
383
+ ```
384
+
385
+ When it names the setup stage, that is:
386
+
387
+ ```bash
388
+ npx --no-install campaigns-os next setup --packet ./campaign-runtime.build.json
389
+ ```
390
+
391
+ If doctor says setup is not required, run:
392
+
393
+ ```bash
394
+ npx --no-install campaigns-os next build --packet ./campaign-runtime.build.json
395
+ ```
396
+
397
+ Paste the generated handoff into your AI tool. From here the build is
398
+ agent-driven: each `next` names the Campaigns OS skill for the stage
399
+ (`next-campaigns-os-setup`, `next-campaigns-build`, `next-campaigns-polish`,
400
+ `next-campaigns-qa`) and the exact commands, which is why the skills are
401
+ installed before the first `start`.
402
+
403
+ ## Gates
404
+
405
+ Build is not launch readiness. A complete run still needs:
406
+
407
+ - page-kit build
408
+ - starter-template/SDK lint from the target repo, for example `npm run lint:sdk`,
409
+ `npm run lint:sdk:promoted`, or `npm run lint:sdk:ci` when those scripts are
410
+ available. There is no separate `campaign-lint` package in the current flow.
411
+ - Campaigns OS Playwright browser install
412
+ - formal polish pass against the served current build
413
+ - mandatory package-owned `polish capture` evidence before Polish becomes
414
+ terminal or deploy/QA begins
415
+ - preview deploy, or a local serve of the built `_site/` output with the packet's
416
+ `deploy.target` set to `local-serve` (`qa policy set --deploy-target local-serve
417
+ --preview-url http://localhost:<port>/<slug>/`); localhost on any port is a
418
+ Development domain, so no SDK origin allowlist entry is needed there
419
+ - Node/npm QA with a saved Map ID or local-spec Build Packet, and the tested URL
420
+ - typed-card test-order proof via `--test-order common` (global test cards bypass the gateway and move no money, but each leaves a real store order record; unless the operator has already said test orders are fine for this campaign, ask once, up front, before placing test orders, then proceed; there is no permission flag)
421
+
422
+ ```bash
423
+ npx --no-install campaigns-os qa install-browser
424
+ npx --no-install campaigns-os polish capture --packet ./campaign-runtime.build.json --base-url <served-current-build-url>
425
+ npx --no-install campaigns-os qa resolve --packet ./campaign-runtime.build.json
426
+ npx --no-install campaigns-os qa run --packet ./campaign-runtime.build.json --base-url https://preview.example.com/campaign/ --browser --test-order common
427
+ ```
428
+
429
+ `npx --no-install campaigns-os qa install-browser` (`npm run qa:install-browser` from a checkout; `npx playwright install chromium` only when it resolves to the toolkit's Playwright)
430
+ is a one-time local setup step after install/update. It installs the Chromium
431
+ binary used by package-owned polish capture and QA.
432
+ Run it before `polish capture`, `--browser`, or `--test-order`; the CLI will tell
433
+ you to run it if the browser binary is missing. The capture producer must point
434
+ at the served current build and complete before Polish hands off to deploy/QA.
435
+
436
+ After the browser is installed, `npm run smoke:polish-capture` is an optional
437
+ real-Chromium package smoke. It needs permission to open a loopback HTTP
438
+ listener and is deliberately excluded from `npm run check` and CI.
package/docs/readback.md CHANGED
@@ -8,7 +8,9 @@ most once, plus two fixed Git metadata files (the nearest `.git` entry at the
8
8
  target or an ancestor, and that Git directory's `logs/HEAD` reflog) for the
9
9
  freshness comparison, and the CampaignSpec and normalized Campaign Build Brief
10
10
  the Build Packet names, whose current content decides each stage's effective
11
- status.
11
+ status. It also reads and hashes regular files in the packet's built output
12
+ at `_site/<public_route_slug>/` to assess build-bound evidence debt. Symlinks
13
+ are skipped by the build fingerprint machinery.
12
14
 
13
15
  The safety contract is the point of the command. The readback **writes nothing**
14
16
  under the target — not even a lifecycle journal entry, which every other command
@@ -21,7 +23,7 @@ For the same reason the readback resolves **no run session** and sweeps no stale
21
23
  one. Its `--packet` names the Build Packet to *project*, not a Build Packet to
22
24
  act on, so an active run session — here, at the target, or bound to some other
23
25
  packet entirely — never changes what this command reads or what it exits with.
24
- That also keeps every read bounded: the only reader of a `--packet` file is the
26
+ The only reader of a `--packet` file is the
25
27
  readback's own 32 MiB-bounded one, which refuses an oversized packet as an
26
28
  `unreadable` artifact row rather than loading it.
27
29
 
@@ -41,8 +43,8 @@ campaigns-os readback --example [--json]
41
43
  The default output is the rendered human view. `--json` emits the same
42
44
  projection as one `campaigns-os-readback/v2` object on stdout so a caller can
43
45
  gate on it; `schemas/campaigns-os-readback.v2.schema.json` is that object's
44
- shape, and this document is its prose twin. The two modes share one computation
45
- path: the JSON serializes what the text view already computes and adds no
46
+ shape, including the additive `evidence_debt` field. The two modes share one
47
+ computation path: the JSON serializes what the text view already computes and adds no
46
48
  interpretation the text view does not also carry.
47
49
 
48
50
  ## Exit codes
@@ -118,6 +120,15 @@ STAGES [assembly report; report status: prepared; effective: prepared]
118
120
  deploy pending
119
121
  qa pending
120
122
 
123
+ EVIDENCE DEBT [the readback's own projection layer: recorded build bindings versus built output]
124
+ polish: unknown — evidence currency is unknown (build_output_missing); absence of proof is not no debt
125
+ recorded: unknown; current: unknown
126
+ qa: unknown — evidence currency is unknown (build_output_missing); absence of proof is not no debt
127
+ recorded: unknown; current: unknown
128
+ changes against assembly manifest: unknown (build_output_missing)
129
+ recorded: unknown; current: unknown
130
+ Page changes use the assembly baseline; older stage manifests are not reconstructed.
131
+
121
132
  BUILD CONTEXT [build context; source adapter: html_funnel, status: prepared]
122
133
 
123
134
  DOCTOR [doctor output; status: ready_with_warnings]
@@ -182,6 +193,23 @@ STAGES [assembly report; report status: completed; effective: prepared]
182
193
  },
183
194
  "skip_cascades": [{ "blocked_by": "polish.evidence_incomplete", "families": ["meta-tags"] }],
184
195
  "divergences": [{ "stage": "polish", "assertion_ids": ["polish.evidence_incomplete"] }],
196
+ "evidence_debt": {
197
+ "polish": {
198
+ "status": "unknown", "matches_current_build": null,
199
+ "recorded_build_fingerprint": null, "current_build_fingerprint": null,
200
+ "reason": "build_output_missing"
201
+ },
202
+ "qa": {
203
+ "status": "unknown", "matches_current_build": null,
204
+ "recorded_build_fingerprint": null, "current_build_fingerprint": null,
205
+ "reason": "build_output_missing"
206
+ },
207
+ "changes": {
208
+ "status": "unknown", "reason": "build_output_missing",
209
+ "recorded_build_fingerprint": null, "current_build_fingerprint": null,
210
+ "changed_pages": [], "unmapped_files": [], "extra": [], "missing": [], "changed": []
211
+ }
212
+ },
185
213
  "clean": false
186
214
  }
187
215
  ```
@@ -397,6 +425,86 @@ in first-seen order:
397
425
  Empty when no QA verdict loaded or none of its assertions were skipped. This
398
426
  grouping is the readback's own projection layer.
399
427
 
428
+ ### `evidence_debt`
429
+
430
+ An additive top-level object in `campaigns-os-readback/v2`, emitted in JSON and
431
+ as an **EVIDENCE DEBT** text section. This assessment reads built bytes; it uses
432
+ no timestamps or reflog entries, starts no process, and writes nothing. It
433
+ does not rerun doctor, Polish, or QA and does not reinterpret their verdicts.
434
+
435
+ `polish` and `qa` each carry:
436
+
437
+ - `status`: the closed set `current`, `owed`, `unknown`;
438
+ - `matches_current_build`: fingerprint equality only (`true` or `false`), or `null`
439
+ when either fingerprint is absent;
440
+ - `recorded_build_fingerprint`: the stage's build binding, or `null`;
441
+ - `current_build_fingerprint`: the fingerprint computed from current built
442
+ output with `computeBuildFingerprint`, or `null`;
443
+ - `reason`: `null` for `current`; `build_fingerprint_changed`,
444
+ `stage_required_by_build`, `stage_required_by_brief`,
445
+ `stage_required_by_spec`, or `stage_required` for `owed`; or an explicit
446
+ reason for `unknown`.
447
+
448
+ Polish uses `stages.polish.source_build_fingerprint`, falling back to
449
+ `stages.polish.evidence.source_build_fingerprint`, with the same precedence as
450
+ the Polish gate. QA uses `stages.qa.evidence.qc_build_fingerprint`, falling back
451
+ to `source_build_fingerprint`, and the same build-binding comparison as `next`.
452
+ It does not require uncommitted capture files to be present.
453
+
454
+ `current` means the stage's binding equals the bytes on disk and the assembly
455
+ fingerprint is available and the stage is not recorded as `required`. `owed`
456
+ means that binding differs from the current build, or the lifecycle recorded
457
+ the stage as `required`, even without a stage fingerprint. Its `required_by`
458
+ selects `stage_required_by_build`, `stage_required_by_brief`, or
459
+ `stage_required_by_spec`; an absent or unrecognized source gives
460
+ `stage_required`. Both comparisons work without a manifest, including reports recorded
461
+ before manifests existed. **`unknown` never means no debt.** Missing or unreadable
462
+ built output or a missing assembly fingerprint still makes the affected stage
463
+ explicitly `unknown`; a missing stage fingerprint does too unless the stage is
464
+ `required`. The unknown stage reason codes are
465
+ `packet_unavailable`, `build_output_location_unknown`, `build_output_missing`,
466
+ `build_output_unreadable`, `assembly_build_fingerprint_missing`, and
467
+ `stage_build_fingerprint_missing`.
468
+ When both stage and disk fingerprints are available, `matches_current_build`
469
+ reports their equality even if a missing assembly fingerprint makes status unknown.
470
+ `matches_current_build: true` together with `status: owed` means the lifecycle
471
+ requires the stage again over matching evidence. Consumers decide whether
472
+ evidence is owed from `status`, never from `matches_current_build`.
473
+
474
+ `changes` compares current output to **the retained assembly manifest**, using
475
+ the existing file drift machinery. It carries:
476
+
477
+ - `status`: `known` or `unknown`, with `reason` (`null` when known);
478
+ - `recorded_build_fingerprint` and `current_build_fingerprint`: the assembly
479
+ manifest baseline and the disk fingerprint;
480
+ - `extra`, `missing`, `changed`: complete file path lists from the manifest
481
+ comparison, without the human drift summary's truncation;
482
+ - `changed_pages`: `{ "page_id": "landing", "built_path": "landing/index.html" }`
483
+ entries for added, removed, or modified HTML in the built-site page map;
484
+ - `unmapped_files`: every differing path outside that map, including shared
485
+ CSS, scripts and assets. These changes still make build-bound evidence owed.
486
+
487
+ Paths are relative to `_site/<public_route_slug>/` and sorted; page IDs use
488
+ `resolveBuiltSiteScope`'s route-derived identities (nested routes use hyphens).
489
+ Removed HTML uses that same route/ID projection. Empty lists with
490
+ `changes.status: unknown` do not establish that no pages changed.
491
+ A missing or unusable manifest makes only `changes.status` unknown, with
492
+ `build_manifest_unavailable` as its reason. A manifest is usable only if its
493
+ entries hash to the assembly's recorded fingerprint.
494
+ If the built page map cannot be resolved (for example, output contains assets
495
+ but no HTML), page changes are unknown with `build_output_pages_unresolved`;
496
+ stage bindings still follow the fingerprint comparison.
497
+
498
+ The assembly baseline is explicit because it need not equal an older stage's
499
+ binding. If a later `record build` replaced that manifest, readback can still
500
+ report older Polish/QA bindings as owed, but cannot reconstruct which pages
501
+ changed from those older builds. An empty **known** list then means only that
502
+ no files differ from the retained assembly manifest. If recording removed a
503
+ stage binding entirely, that stage reads unknown unless its recorded status is
504
+ `required`, which reports owed. `current` is a build-binding
505
+ assessment only; input currency, stage completion, waivers and verdicts remain
506
+ separate.
507
+
400
508
  ### `divergences`
401
509
 
402
510
  Where two artifacts record different states for the same stage: the assembly
@@ -413,7 +521,7 @@ failure lines up with a completed stage. This too is the readback's own layer.
413
521
 
414
522
  ## `clean`
415
523
 
416
- `clean` is true if and only if **all five** of the following hold:
524
+ `clean` is true if and only if **all six** of the following hold:
417
525
 
418
526
  1. **Every artifact the readback found is loaded and recognized.** Formally: no
419
527
  artifact is in state `unreadable` or `unrecognized`. An `absent` artifact
@@ -439,6 +547,11 @@ failure lines up with a completed stage. This too is the readback's own layer.
439
547
  failed to check.
440
548
  4. **`divergences` is empty.**
441
549
  5. **`doctor.error_count` is zero.**
550
+ 6. **Neither `evidence_debt.polish.status` nor `evidence_debt.qa.status` is
551
+ `owed`.** Built output that moved since recorded evidence cannot read clean.
552
+ An `unknown` status does not independently make this integrity flag false;
553
+ it remains unknown, never a claim that no evidence is owed. Callers must
554
+ inspect `evidence_debt` even when `clean` is true.
442
555
 
443
556
  Doctor warnings — of either group — do not affect `clean`. Neither does a
444
557
  blocked QA verdict, a blocked assembly report, or a non-empty `skip_cascades`.
@@ -449,7 +562,7 @@ blocked QA verdict, a blocked assembly report, or a non-empty `skip_cascades`.
449
562
  campaign. It means: the readback read every artifact that was there, understood
450
563
  all of them, can show — for every artifact that recorded an age — that none of
451
564
  them is older than the checkout, found no contradiction between them, and saw no
452
- doctor error. Campaigns OS remains the
565
+ doctor error, and found no known build-bound evidence debt. Campaigns OS remains the
453
566
  lifecycle and verdict authority; the readback never reinterprets a verdict.
454
567
 
455
568
  The practical consequence for a caller: `clean: true` says the artifacts are
@@ -8,7 +8,7 @@
8
8
 
9
9
  How a checkout of this repository at one commit becomes a usable installed runtime, and how a consumer decides whether a prepared one is still trustworthy. Everything below is generated from `contracts/runtime-recipe.campaigns-os-node-v1.json`, which is the only authority for these values.
10
10
 
11
- Recipe kind `campaigns-os-node-v1`, revision `1.0.3`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.59.0`.
11
+ Recipe kind `campaigns-os-node-v1`, revision `1.0.3`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.61.0`.
12
12
 
13
13
  ## What this is
14
14