@nextcommerce/campaigns-os 1.48.0 → 1.52.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/CHANGELOG.md +539 -0
- package/agents/claude/CLAUDE.md +6 -5
- package/agents/codex/AGENTS.md +6 -5
- package/agents/copilot/copilot-instructions.md +3 -3
- package/agents/cursor/campaigns-os.mdc +3 -3
- package/campaign-spec/dist/rules/campaign-metadata.d.ts +5 -1
- package/campaign-spec/dist/rules/campaign-metadata.js +9 -2
- package/campaign-spec/dist/rules/design-source-shape.js +13 -3
- package/campaign-spec/dist/rules/sdk-version.js +2 -1
- package/compatibility.json +1 -1
- package/contracts/commerce-surface-catalog.json +26 -46
- package/contracts/effects.v1.json +254 -2
- package/contracts/release-ledger.json +1239 -0
- package/contracts/supported-surface.json +4 -4
- package/contracts/template-brand-contract.shared-commerce.v0.json +2 -2
- package/contracts/template-slot-manifest.shared-content-core.v0.json +24 -0
- package/docs/brand-theme-bridge.md +12 -6
- package/docs/build-packet.md +101 -12
- package/docs/campaign-build-brief.md +25 -1
- package/docs/effects.md +6 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/polish-evidence.md +10 -0
- package/docs/qa-and-test-orders.md +66 -11
- package/docs/runtime-readiness.md +1 -1
- package/docs/sdk-storage-compatibility.md +1 -1
- package/docs/skills-revision.md +10 -10
- package/package.json +1 -1
- package/schemas/campaign-runtime-build-packet.v0.schema.json +4 -0
- package/schemas/campaigns-os-qa-verdict.v0.schema.json +8 -3
- package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +3 -3
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +4 -4
- package/skills/next-campaigns-os/SKILL.md +4 -4
- package/skills/next-campaigns-os/references/session-intake.md +7 -3
- package/skills/next-campaigns-os-setup/SKILL.md +3 -3
- package/skills/next-campaigns-polish/SKILL.md +5 -4
- package/skills/next-campaigns-qa/SKILL.md +6 -5
- package/skills.json +10 -10
- package/src/adapter-decision-contract.mjs +1 -1
- package/src/brand-theme.mjs +25 -2
- package/src/build-brief.mjs +68 -21
- package/src/built-site-scope.mjs +39 -6
- package/src/built-smoke-qc.mjs +1117 -0
- package/src/campaign-identity.mjs +36 -2
- package/src/cart-placeholders.mjs +730 -0
- package/src/cli.mjs +320 -42
- package/src/commercial-journey.mjs +65 -4
- package/src/commercial-parity.mjs +6 -1
- package/src/doctor/checks.mjs +291 -24
- package/src/doctor/inspect.mjs +53 -2
- package/src/doctor/next-step.mjs +1 -1
- package/src/install-mode.mjs +0 -8
- package/src/invocation.mjs +5 -2
- package/src/local-preview-policy.mjs +1 -1
- package/src/local-proof.mjs +4 -1
- package/src/polish-browser.mjs +218 -1
- package/src/polish-capture.mjs +1 -1
- package/src/polish-media-weight.mjs +492 -0
- package/src/polish-node.mjs +96 -4
- package/src/progress-node.mjs +5 -1
- package/src/qa-binding-evidence.mjs +21 -0
- package/src/qa-browser.mjs +338 -97
- package/src/qa-content-params.mjs +889 -0
- package/src/qa-node.mjs +114 -14
- package/src/qa-order-bump.mjs +22 -1
- package/src/qa-policy-links.mjs +1019 -0
- package/src/qa-tracking-params.mjs +1389 -0
- package/src/qa-url-privacy.mjs +168 -0
- package/src/qc-accept.mjs +446 -0
- package/src/qc-check-registry.mjs +83 -0
- package/src/qc-results.mjs +1049 -0
- package/src/sdk-attribute-index.mjs +71 -0
- package/src/sdk-markup.mjs +2 -2
- package/src/sdk-storage-compatibility.mjs +63 -3
- package/src/source-prep.mjs +37 -7
- package/src/stage-record.mjs +356 -36
- package/src/theme-gate.mjs +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "The downstream contract manifest. Everything listed here is SUPPORTED SURFACE: consumers (campaigns-agent, campaign-builder, the private ops repo, page-kit campaign repos) may depend on it, and changing it is a deliberate act — hashed entries require a surface_version bump in the same change (check-supported-surface.mjs --base, mirroring the skills.json bump gate), named entries must keep existing at their path, cli_commands must keep resolving in the CLI dispatch, package_exports must stay exported, and every entry must ship in the npm pack (files[] coverage). Anything NOT listed here — src/** internals, scripts/** checkers, examples/**, prompts/**, contracts/** other than this file and the entries named[] below (the orientation contract, the release ledger, and the consumer-facing orientation fixtures) — is implementation: consumers may read it for context but must not build on it, and it can change without notice. Rationale and the compatibility promise: docs/supported-surface.md.",
|
|
3
|
-
"surface_version": "1.
|
|
3
|
+
"surface_version": "1.52.0",
|
|
4
4
|
"package_exports": [
|
|
5
5
|
"./commercial-journey",
|
|
6
6
|
"./commercial-parity",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"sha256": "f310c1b7844d9b1994864d15858ff0c6f17efc1e5285ba193d181bdb2dd4be2f"
|
|
67
67
|
},
|
|
68
68
|
"schemas/campaign-runtime-build-packet.v0.schema.json": {
|
|
69
|
-
"sha256": "
|
|
69
|
+
"sha256": "5002529c5979c4d8fef7e298de82ebdf625ffbd11f5284ba684d96d173788d62"
|
|
70
70
|
},
|
|
71
71
|
"schemas/campaign-spec.v4.schema.json": {
|
|
72
72
|
"sha256": "af56c3de638d3c4e1fd5df8c2d733754d9ad16caa23b5bec92ce3a6f71bd0271"
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"sha256": "3c40fd79f218d1b95f0424f9a2b67db140a306329e2ca02793e2740d3afded59"
|
|
85
85
|
},
|
|
86
86
|
"schemas/campaigns-os-qa-verdict.v0.schema.json": {
|
|
87
|
-
"sha256": "
|
|
87
|
+
"sha256": "8c689d29850b2b894c17438ee4411363033bae6f805820af53b7a04a5693d0e0"
|
|
88
88
|
},
|
|
89
89
|
"schemas/campaigns-os-doctor-output.v0.schema.json": {
|
|
90
90
|
"sha256": "87866e619d2a18f01a208a1682cb14a2079f61df51c642f4e1902b6e6bce0ce5"
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"sha256": "dfc9abed38d456969e47a21f606d308a03bdf036f3466f7d6e47a602747dacf4"
|
|
121
121
|
},
|
|
122
122
|
"contracts/effects.v1.json": {
|
|
123
|
-
"sha256": "
|
|
123
|
+
"sha256": "f49ac6dcbf441c90b12b51be9bc07805f6228f4143784045c5925e3c155a6181"
|
|
124
124
|
},
|
|
125
125
|
"schemas/campaigns-os-effects.v1.schema.json": {
|
|
126
126
|
"sha256": "3eadd22169ab98bc7c2f682af2751267605170581182158d96be035b0cfe44dc"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"asset_pin": {
|
|
57
57
|
"repo": "NextCommerceCo/campaign-cart-starter-templates",
|
|
58
|
-
"sha": "
|
|
58
|
+
"sha": "2c618942391ae398fb4812f36fc93e6632054874",
|
|
59
59
|
"path": "src/<family>/assets/<asset>",
|
|
60
60
|
"note": "asset_sha256 is the sha256 of each shipped starter asset at this commit (the commerce-surface-catalog _synced_from_sha pin; the bytes are identical across every family). A served asset whose bytes hash to the shipped value is the untouched starter asset and counts as residue even though its markup names no method; different bytes mean it was edited in place. check-template-doctrine verifies these hashes against the pinned checkout."
|
|
61
61
|
},
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
],
|
|
76
76
|
"placeholder_text_residue": {
|
|
77
77
|
"page_types": ["checkout", "select", "upsell", "downsell", "receipt", "presell", "landing"],
|
|
78
|
-
"terms": ["Lorem", "lorem ipsum", "Placeholder", "TODO", "Product Name"],
|
|
78
|
+
"terms": ["Lorem", "lorem ipsum", "Placeholder", "TODO", "Product Name", "Benefit one", "Benefit two", "Benefit three", "Benefit four"],
|
|
79
79
|
"rule": "Rendered campaign output must not contain literal template placeholder copy. Every term is replaced from the CampaignSpec/design before QA; QA fails (blocker) on any match in visible page text, regardless of any theme-gate waiver. Matched on word boundaries, case-insensitive. Scope is governed by page_types: the blocker applies only to the listed campaign funnel page types (generic, non-funnel pages whose type is not listed are not checked). Narrow page_types to exempt a page type where placeholder copy is acceptable."
|
|
80
80
|
},
|
|
81
81
|
"computed_style_checks": [
|
|
@@ -1394,6 +1394,14 @@
|
|
|
1394
1394
|
},
|
|
1395
1395
|
"demo_value": "Our formula works with your body's own sleep systems \u2014 not against them. Our synergistic blend lower"
|
|
1396
1396
|
},
|
|
1397
|
+
{
|
|
1398
|
+
"key": "benefits_2_section_id",
|
|
1399
|
+
"kind": "meta",
|
|
1400
|
+
"role": "page_meta",
|
|
1401
|
+
"owner": "template",
|
|
1402
|
+
"description": "Section anchor id, the target of the header nav's in-page link. Template-owned, assembly never touches it: the nav href and this id must stay paired.",
|
|
1403
|
+
"source_policy": "template_static"
|
|
1404
|
+
},
|
|
1397
1405
|
{
|
|
1398
1406
|
"key": "benefits_2_section_heading",
|
|
1399
1407
|
"kind": "copy",
|
|
@@ -1709,6 +1717,14 @@
|
|
|
1709
1717
|
},
|
|
1710
1718
|
"demo_value": "The compounding effect reaches its peak. Consistent deep sleep every night, sustained daytime energy"
|
|
1711
1719
|
},
|
|
1720
|
+
{
|
|
1721
|
+
"key": "ingredients_3_section_id",
|
|
1722
|
+
"kind": "meta",
|
|
1723
|
+
"role": "page_meta",
|
|
1724
|
+
"owner": "template",
|
|
1725
|
+
"description": "Section anchor id, the target of the header nav's in-page link. Template-owned, assembly never touches it: the nav href and this id must stay paired.",
|
|
1726
|
+
"source_policy": "template_static"
|
|
1727
|
+
},
|
|
1712
1728
|
{
|
|
1713
1729
|
"key": "ingredients_3_heading",
|
|
1714
1730
|
"kind": "copy",
|
|
@@ -2531,6 +2547,14 @@
|
|
|
2531
2547
|
"proof_modality": "review",
|
|
2532
2548
|
"needs_merchant_input_able": true
|
|
2533
2549
|
},
|
|
2550
|
+
{
|
|
2551
|
+
"key": "reviews_3_section_id",
|
|
2552
|
+
"kind": "meta",
|
|
2553
|
+
"role": "page_meta",
|
|
2554
|
+
"owner": "template",
|
|
2555
|
+
"description": "Section anchor id, the target of the header nav's in-page link. Template-owned, assembly never touches it: the nav href and this id must stay paired.",
|
|
2556
|
+
"source_policy": "template_static"
|
|
2557
|
+
},
|
|
2534
2558
|
{
|
|
2535
2559
|
"key": "reviews_3_badge_text",
|
|
2536
2560
|
"kind": "copy",
|
|
@@ -151,8 +151,9 @@ When `theme inspect` reports `can_generate: true` and the campaign ships
|
|
|
151
151
|
commerce pages (checkout/upsell/downsell/receipt), the gate **blocks**
|
|
152
152
|
`next polish`, `next deploy`, `next qa`, and `qa run` until one of:
|
|
153
153
|
|
|
154
|
-
- the brand layer is generated and recorded as applied
|
|
155
|
-
(`report.theme.status: applied`,
|
|
154
|
+
- the brand layer is generated, linked and recorded as applied with
|
|
155
|
+
`campaigns-os record theme --packet <p>` (`report.theme.status: applied`,
|
|
156
|
+
`load_order: after-next-core`), or
|
|
156
157
|
- an explicit waiver is recorded:
|
|
157
158
|
`campaigns-os theme waive --packet <p> --reason "<why>" --waived-by "<named human>"`
|
|
158
159
|
(optionally `--expires-at <ISO>`; placeholders such as "operator" are refused), or
|
|
@@ -197,8 +198,12 @@ If a fresh `brand-theme.css` exists:
|
|
|
197
198
|
3. Preserve SDK-owned runtime surfaces: `data-next-*`, package selectors,
|
|
198
199
|
payment fields, totals, submit controls, receipt templates, route meta tags,
|
|
199
200
|
and SDK JavaScript.
|
|
200
|
-
4.
|
|
201
|
-
|
|
201
|
+
4. Rebuild, run `campaigns-os record build --packet <p>`, then
|
|
202
|
+
`campaigns-os record theme --packet <p>`. It reads each built commerce
|
|
203
|
+
page's stylesheet links and records `report.theme.status`, `css_path`,
|
|
204
|
+
`commerce_pages`, `load_order` and evidence. It refuses, writing nothing,
|
|
205
|
+
when a page that loads `next-core.css` does not load the brand layer after
|
|
206
|
+
it, or when no built commerce page loads `next-core.css`.
|
|
202
207
|
|
|
203
208
|
### Where next-core.css belongs
|
|
204
209
|
|
|
@@ -212,8 +217,9 @@ stylesheets off those pages and list them only in the frontmatter styles of
|
|
|
212
217
|
the pages that render family components, often just checkout.
|
|
213
218
|
|
|
214
219
|
`report.theme.commerce_pages` is the list of pages where the brand layer was
|
|
215
|
-
applied
|
|
216
|
-
example `commerce_pages: ["checkout"]`.
|
|
220
|
+
applied. `record theme` writes it from the built pages: every commerce page
|
|
221
|
+
that loads `next-core.css`, for example `commerce_pages: ["checkout"]`. A page
|
|
222
|
+
that loads neither stylesheet is left out and noted in the evidence. The theme gate does not compare this
|
|
217
223
|
list with the funnel. It passes on `report.theme.status: applied` with
|
|
218
224
|
`load_order: after-next-core`. The gate's own `commerce_pages` output is a
|
|
219
225
|
different field: every checkout, upsell, downsell, receipt or thank-you page
|
package/docs/build-packet.md
CHANGED
|
@@ -22,8 +22,9 @@ ordinary CampaignSpec from the brief, source design and configured campaign's
|
|
|
22
22
|
real commerce values, following `schemas/campaign-spec.v4.schema.json`. The
|
|
23
23
|
operator supplies the selected store/campaign, public Campaigns API key, intended
|
|
24
24
|
pages and commercial choices, plus store contact details and policy URLs. Verify
|
|
25
|
-
the store/campaign binding and package
|
|
26
|
-
|
|
25
|
+
the store/campaign binding, and read the package, offer and shipping references
|
|
26
|
+
from the campaign itself (below); do not guess commerce values. No gateway or
|
|
27
|
+
Map provisioning is required for this entry.
|
|
27
28
|
|
|
28
29
|
Set `spec_identity.local_spec_id` to a new UUID once, commit it with the spec,
|
|
29
30
|
and keep it unchanged through revisions and fresh checkouts. It accepts 1–64
|
|
@@ -45,7 +46,9 @@ npx --no-install campaigns-os next --packet campaign-runtime.build.json
|
|
|
45
46
|
The packet and report retain `map_id: null` and carry `local_spec_id`. Doctor,
|
|
46
47
|
report writes, polish capture, progress, run closeout and QA compare that local
|
|
47
48
|
identity. Material spec hashes still bind the current revision; a changed ID or
|
|
48
|
-
content cannot reuse earlier proof. After a material revision,
|
|
49
|
+
content cannot reuse earlier proof. After a material revision, doctor and
|
|
50
|
+
`next` warn `spec.material_stale` (the spec no longer has the material hash
|
|
51
|
+
prepare-build bound on the Assembly Report, which QA refuses); follow `next` to
|
|
49
52
|
refresh preparation and affected evidence. Keep the spec, source, dependency
|
|
50
53
|
pins and canonical sidecars in Git. Use `readback` and `next` after a fresh
|
|
51
54
|
checkout; identity survives the move, but proof freshness is assessed again.
|
|
@@ -65,6 +68,56 @@ not relax template certification, source proof, store/SDK parity, polish,
|
|
|
65
68
|
commerce checks, or typed-card checkout proof. Resolve their reported gates;
|
|
66
69
|
localhost readiness is not production approval.
|
|
67
70
|
|
|
71
|
+
### Reading package, offer and shipping refs
|
|
72
|
+
|
|
73
|
+
No command writes commerce refs into a local spec, and `login` does not read
|
|
74
|
+
them: a gateway login serves only the Store Profile fields that `spec derive
|
|
75
|
+
--from-store` fills. Read them with the campaign's public Campaigns API key,
|
|
76
|
+
through the request doctor and QA already make to check built pages against
|
|
77
|
+
the live campaign: one GET of NEXT's proxy with the key in the
|
|
78
|
+
`X-Campaign-Key` header. No store or Admin credential is involved.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
CAMPAIGN_KEY='<public key>' node -e '
|
|
82
|
+
fetch("https://campaign-map.nextcommerce.com/api/campaign", {
|
|
83
|
+
headers: { Accept: "application/json", "X-Campaign-Key": process.env.CAMPAIGN_KEY },
|
|
84
|
+
}).then(async (res) => console.log(res.status, JSON.stringify(await res.json(), null, 2)));
|
|
85
|
+
'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`curl -sS -H "Accept: application/json" -H "X-Campaign-Key: <public key>" https://campaign-map.nextcommerce.com/api/campaign`
|
|
89
|
+
returns the same. Add `?ref_id=<campaign id>` when one key serves several
|
|
90
|
+
campaigns. Other HTTP clients work with the same header, but the proxy refuses
|
|
91
|
+
some default user agents, Python `urllib`'s and Perl `libwww-perl`'s among
|
|
92
|
+
them, with a 403 whose body is `error code: 1010`; send another `User-Agent`
|
|
93
|
+
or use one of the commands above.
|
|
94
|
+
|
|
95
|
+
The answer is an envelope, `{ ok, status, endpoint, requested_ref_id,
|
|
96
|
+
retrieved_at, data }`, with `requested_ref_id` present only when `?ref_id=`
|
|
97
|
+
was sent; `ok: false` carries an `error` instead of a campaign. `data` is the
|
|
98
|
+
campaign retrieve body the Campaign Cart SDK reads in the browser: one
|
|
99
|
+
campaign, or an array of them, in which case use the entry whose `id` is the
|
|
100
|
+
selected campaign. Copy from it as follows:
|
|
101
|
+
|
|
102
|
+
| `data` field | CampaignSpec field |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `id` | `campaign.ref_id` (doctor and QA send it as `?ref_id=`) |
|
|
105
|
+
| `name`, `currency`, `language`, `payment_env_key` | the `campaign` fields of the same name |
|
|
106
|
+
| `packages[]`: `ref_id`, `name`, `qty`, `price`, `price_retail`, `image`, the `product_*` fields and the recurring fields (`is_recurring`, `price_recurring`, `interval`, `interval_count`) | one `funnels[].pages[].packages[]` entry for each package the page sells |
|
|
107
|
+
| `offers[]`: `ref_id`, `name`, `type`, `code`, `condition`, `benefit`, `packages[]` (by `package_id`), `shipping_methods[]` | root `offers[]`, copied whole; a page that presents an offer lists it in `funnels[].pages[].offers[]` by `ref_id` |
|
|
108
|
+
| `shipping_methods[]`: `ref_id`, `code`, `price` | root `shipping_methods[]` |
|
|
109
|
+
|
|
110
|
+
A package's ref is its `ref_id`, numbered within the campaign. `external_id`,
|
|
111
|
+
`product_id` and `product_variant_id` are catalog ids, never package refs. The
|
|
112
|
+
same `ref_id` values are what pages render in `data-next-package-id` and
|
|
113
|
+
`data-next-shipping-id`. The read supplies refs and the values the campaign
|
|
114
|
+
serves, not the selection: which packages and offers each page carries, and
|
|
115
|
+
the role flags `is_upsell`, `is_order_bump` and `default_selected`, come from
|
|
116
|
+
the brief and the operator. Copy prices and availability from the read rather
|
|
117
|
+
than typing them from the brief. After the build, doctor and QA repeat this
|
|
118
|
+
read and block a page ref the live campaign does not serve (see
|
|
119
|
+
[the live campaign read](effects.md#the-live-campaign-read)).
|
|
120
|
+
|
|
68
121
|
## Root-Served Campaigns (`campaign.route_root`)
|
|
69
122
|
|
|
70
123
|
Most campaigns are served under a slug prefix (`/<public_route_slug>/...`), and
|
|
@@ -732,7 +785,14 @@ intended) when:
|
|
|
732
785
|
(`…attribution_drift`).
|
|
733
786
|
|
|
734
787
|
One error per finding; each names the two files and the two values, so the
|
|
735
|
-
repair is a one-line edit.
|
|
788
|
+
repair is a one-line edit. Every built HTML file under `_site/<slug>/` is
|
|
789
|
+
scanned, because every one is served. When doctor has the CampaignSpec, a
|
|
790
|
+
finding that names a file no active spec page builds to says so and lists it
|
|
791
|
+
under `stray_files`. Such a file is leftover output (page-kit does not prune
|
|
792
|
+
`_site/`) or an HTML file copied into the source, such as a design export's
|
|
793
|
+
`index.html` under `assets/`. The repair is to remove the source file if
|
|
794
|
+
there is one, delete the built file, and rebuild and record the build again,
|
|
795
|
+
not to retag it. Pages whose route contains a `-backup-` or `-old-`
|
|
736
796
|
segment are parked copies: skipped and listed on the gate as `pages_skipped`,
|
|
737
797
|
never scanned. Presence is not asserted: a campaign whose pages carry no key
|
|
738
798
|
at all, or no `setAttribution` anywhere, passes on the funnel tag alone.
|
|
@@ -893,6 +953,24 @@ canonical rendered output of every certified starter family
|
|
|
893
953
|
it reads for credential declarations (`script-parse:<page_id>`; see
|
|
894
954
|
[QA and test orders](qa-and-test-orders.md)).
|
|
895
955
|
|
|
956
|
+
### Built-output cart placeholder check (`built_output.cart_placeholders`)
|
|
957
|
+
|
|
958
|
+
**Raw cart placeholders (`built_output.cart_placeholders`).** Doctor warns when a known SDK placeholder, such as `{item.name}`, `{subtotal}` or `{package.name}`, appears in live built HTML text or in a text attribute (`alt`, `title`, `placeholder`, `aria-label`, button `value`), where it prints as raw text. Placeholders inside `<template>`, inside `data-next-cart-items`/`data-next-order-items` rows and their declared row templates, and SDK-substituted quantity text are expected. Brace strings that are not known SDK placeholders, including `{tax}`, are review results. The placeholder list is vendored from the SDK version pinned in `src/sdk-attribute-index.mjs`. Pages whose SDK loader does not name an exact version, or names a version the list was not verified against, are reported as unexercised rather than passing. These are warnings, never blockers.
|
|
959
|
+
|
|
960
|
+
### Built-output smoke checks (`built_output.smoke_qc`)
|
|
961
|
+
|
|
962
|
+
**Smoke checks (`built_output.smoke_qc`).** Doctor warns on built pages that have:
|
|
963
|
+
|
|
964
|
+
- an in-page `#link` with no matching `id` or `name` on the same page;
|
|
965
|
+
- no favicon link;
|
|
966
|
+
- no `og:title`, `og:description` or `og:image`;
|
|
967
|
+
- an `og:image` that is relative or points to a missing file;
|
|
968
|
+
- the Tailwind CDN script in a production build;
|
|
969
|
+
- references to the `cdn.29next.store` asset host in any attribute or `<style>` block (use `cdn.cachebucket.com`);
|
|
970
|
+
- `localhost` or loopback URLs in a production build.
|
|
971
|
+
|
|
972
|
+
Targets inside `<template>` or named in page scripts are review results, and pages whose scripts could not all be read are unexercised. Remote `og:image` URLs are not fetched by doctor. The Tailwind and loopback checks need a recorded production build: development builds (including local proof) and `doctor --built`, which cannot tell the environment, report them as unexercised. All are warnings, never blockers.
|
|
973
|
+
|
|
896
974
|
> **Where does the source HTML come from?** See [docs/entry-points.md](./entry-points.md) for the five recognized entry points (template-stock, Figma-driven, AI-generated, hand-authored, mixed) and how each populates `source_html.pages[]` + `design_source`.
|
|
897
975
|
|
|
898
976
|
## Artifact Locations
|
|
@@ -970,8 +1048,9 @@ starter palette is acceptable>" --waived-by "<named human>"`, optionally
|
|
|
970
1048
|
(status and severity — never `fail`) and keeps the shipped palette visible in
|
|
971
1049
|
the verdict; or hand-author the brand
|
|
972
1050
|
layer — write `brand-theme.css`, list it after `next-core.css` in commerce-page
|
|
973
|
-
frontmatter styles, rebuild, and record
|
|
974
|
-
`load_order: after-next-core
|
|
1051
|
+
frontmatter styles, rebuild, `record build`, and `record theme`, which records
|
|
1052
|
+
`report.theme.status: applied` with `load_order: after-next-core` from the
|
|
1053
|
+
built pages. Nothing waives the gate on the operator's
|
|
975
1054
|
behalf. See [Brand Theme Bridge](./brand-theme-bridge.md) for both lanes in
|
|
976
1055
|
full.
|
|
977
1056
|
|
|
@@ -1237,7 +1316,9 @@ loopback host (`127.0.0.1`, `[::1]`) with a ready line naming the
|
|
|
1237
1316
|
`http://localhost:<port>/` fallback, and warns (`deploy.local_serve_url`) when
|
|
1238
1317
|
the recorded URL is neither.
|
|
1239
1318
|
`next` at the deploy stage then hands off a serve-locally prompt and action
|
|
1240
|
-
instead of a ship-to-host one
|
|
1319
|
+
instead of a ship-to-host one, and the served URL is recorded with
|
|
1320
|
+
`campaigns-os record deploy --packet <p> --base-url <url>` (the record command
|
|
1321
|
+
table below). The directory to serve is `_site/`; for a
|
|
1241
1322
|
root-served campaign (`campaign.route_root: "/"`) the handoff adds that pages
|
|
1242
1323
|
are served at site-root paths while assets keep the `/<public_route_slug>/`
|
|
1243
1324
|
prefix, so `_site/` needs the same rewrite of root-level page routes onto
|
|
@@ -1248,9 +1329,10 @@ URL.
|
|
|
1248
1329
|
`local-serve` also selects **local proof mode** for the build stage: page-kit
|
|
1249
1330
|
is built in the development environment (`CPK_ENV=development npx
|
|
1250
1331
|
campaign-build --json > .campaign-runtime/page-kit-build-summary.json`) into
|
|
1251
|
-
`_site/`, and
|
|
1332
|
+
`_site/`, and `campaigns-os record build --packet <packet> --build-environment
|
|
1333
|
+
development` records `stages.assembly.evidence.build_environment:
|
|
1252
1334
|
"development"` on the Assembly Report (a free-form stage field; no schema
|
|
1253
|
-
change). The starter templates gate every vendor loader on the environment,
|
|
1335
|
+
change; never hand-edited). The starter templates gate every vendor loader on the environment,
|
|
1254
1336
|
and a production build's protocol-relative loaders (`//host/...`) fail over a
|
|
1255
1337
|
plain-HTTP local serve, voiding polish capture unwaivably; the SDK's `dl_*`
|
|
1256
1338
|
events still fire in development. Before commit, `campaigns-os page-kit parity
|
|
@@ -1607,12 +1689,17 @@ hand-editing `.campaign-runtime/` JSON:
|
|
|
1607
1689
|
| Command | Writes | Refused (nothing written) when |
|
|
1608
1690
|
|---|---|---|
|
|
1609
1691
|
| `campaigns-os record setup --packet <p>` | Build Context `scaffold.required=false` (`handoff_skill` next-campaigns-build) and `stages.setup` completed | the campaign output directory (`assembly.output_dir`) does not exist, or there is no Build Context or Assembly Report |
|
|
1610
|
-
| `campaigns-os record build --packet <p
|
|
1692
|
+
| `campaigns-os record build --packet <p> [--build-environment <development\|production>]` | `stages.assembly` completed with `build_fingerprint` = doctor's `derived.build_output_fingerprint.value`, `source_package_material_fingerprint` = the report's Design Source Package material fingerprint when present, `evidence.build_environment` = the `--build-environment` value when given (kept from the last record otherwise), and `stages.polish` reset to `required` (`required_by` build, `required_for` qa) unless its evidence is bound to this exact output | doctor cannot compute the fingerprint (no `_site/<public_route_slug>/`), setup is still required, or `stages.setup` is not terminal |
|
|
1611
1693
|
| `campaigns-os record polish --packet <p> --evidence <file>` | `stages.polish` from the file (`docs/polish-evidence.md` §7: completed, blocked or skipped), bound to doctor's current fingerprint; `report.theme.repair_loop_defect` when the file sets it | build is not recorded for the current output, the file has a shape error (named by field), or, for a completed status, the polish gate doctor evaluates would not pass on the result |
|
|
1694
|
+
| `campaigns-os record theme --packet <p>` | `report.theme`: status `applied`, `load_order` `after-next-core`, `css_path`, `commerce_pages` and per-page evidence read from each built commerce page's stylesheet links; any earlier theme waiver is cleared | build is not recorded for the current output, the campaign ships no commerce pages, a built commerce page that loads `next-core.css` does not load `brand-theme.css` (or `checkout-brand.css`) after it or links one missing from the built output, or no built commerce page loads `next-core.css` |
|
|
1695
|
+
| `campaigns-os record deploy --packet <p> --base-url <url>` | the packet's `deploy.preview_url` and `stages.deploy` completed with the URL in `outputs` and one evidence line per built page that answered (each page is requested under the URL first) | the packet is not `local-serve`, the URL is not a loopback origin naming the campaign's route root, a built page does not answer 2xx, polish is not recorded, the built output changed since build was recorded, or the theme gate is blocked |
|
|
1612
1696
|
|
|
1613
1697
|
Each command also refuses a stage `next` has not reached: while doctor's
|
|
1614
1698
|
prepare-build gate is set (`next` answers prepare-build) or while an earlier
|
|
1615
|
-
stage in the order below is not terminal.
|
|
1699
|
+
stage in the order below is not terminal. The exception is the one `next`
|
|
1700
|
+
makes: on the local preview, a polish doctor carries forward (never recorded
|
|
1701
|
+
for this build) does not hold `record deploy` back, as it does not hold `next`;
|
|
1702
|
+
polish stays owed and QA reports it.
|
|
1616
1703
|
|
|
1617
1704
|
Each command reads the same packet, Build Context and Assembly Report `next`
|
|
1618
1705
|
reads (`--context` / `--report` override them the same way), validates what it
|
|
@@ -1634,7 +1721,9 @@ whenever the report's campaign identity does not match the packet, including
|
|
|
1634
1721
|
for packets with no Design Source Package. Every command also adds
|
|
1635
1722
|
`recorded_by`, and `completed_at` unless it records a blocked Polish, to the
|
|
1636
1723
|
stage it records. `--dry-run` runs every check, takes no lock and writes nothing. A failed
|
|
1637
|
-
check exits non-zero with the problems listed, one per line
|
|
1724
|
+
check exits non-zero with the problems listed, one per line; a value outside a
|
|
1725
|
+
schema enum (for example an `adapter_decisions` policy) is listed with the
|
|
1726
|
+
values the schema allows and the value it got. Re-run `record
|
|
1638
1727
|
build` after every page-kit build; a rebuild that changes the output needs
|
|
1639
1728
|
`polish capture` and `record polish` again.
|
|
1640
1729
|
|
|
@@ -47,13 +47,37 @@ Guided questions are intentionally short and business-readable. They prioritize:
|
|
|
47
47
|
2. Which palette/CTA style should commerce pages use?
|
|
48
48
|
3. Which product variants/colors are actually sold?
|
|
49
49
|
4. How should bundle pricing be presented?
|
|
50
|
-
5.
|
|
50
|
+
5. Should the starter template's own promo placeholders (demo countdown timers, promo banners, placeholder voucher codes, exit-pop offers) be filled from the campaign's promo codes and offers, or removed?
|
|
51
51
|
6. Which payment methods/trust badges may appear?
|
|
52
52
|
7. Are runtime/catalog names allowed to override provided display names?
|
|
53
53
|
8. Are there regulated claims or forbidden copy areas?
|
|
54
54
|
|
|
55
55
|
The CLI avoids SDK/page-kit jargon in questions. The implementation can resolve SDK attributes, responsive CSS, asset paths, routing, template copying, and QA reruns. Business choices should come from the brief or be escalated.
|
|
56
56
|
|
|
57
|
+
Question 5 is asked only when the CampaignSpec maps a surface that fills the template's promo placeholders: a `funnels[].promo_codes` roster, or a checkout page's enabled `exit_intent` or `promo_code_input`. It never asks for approval of the source design's own promo, proof or urgency copy, which is built as designed (see below). Without such a surface the guided draft sets `header_claim_source` and `timer_label` to `"none"`: the template's promo placeholders are removed.
|
|
58
|
+
|
|
59
|
+
## Answering The Questions
|
|
60
|
+
|
|
61
|
+
An answer counts only once it is in a brief file that `start` or `prepare-build` reads. The guided draft is regenerated on every run, so an answer given in conversation, or typed into the normalized draft, does not stick.
|
|
62
|
+
|
|
63
|
+
1. Copy `.campaign-runtime/input/campaign-build-brief.normalized.json` to `campaign-build-brief.json` in the target repo. A brief file replaces the guided draft whole, so starting from the copy keeps the fields the draft already filled; any question the file leaves open blocks as a prepared brief.
|
|
64
|
+
2. Set the fields that close each open question:
|
|
65
|
+
|
|
66
|
+
| Question | Fields |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `page_design_authority` | `design_authority.<page_id>.source` for each page named in the question |
|
|
69
|
+
| `brand_palette_cta` | `brand.commerce_palette_source`, `brand.cta_style` |
|
|
70
|
+
| `variant_media_rules` | `media.sold_variants`, `media.allow_other_variant_colors` |
|
|
71
|
+
| `bundle_pricing_presentation` | `offer_presentation.bundle_cards.primary_price` |
|
|
72
|
+
| `promo_urgency_copy` | `promo_urgency.header_claim_source` (`campaign_offers` or `none`), `promo_urgency.timer_label` (the template timer's label, or `none`) |
|
|
73
|
+
| `payment_methods_trust` | `commerce_surfaces.payment_methods_allowed` |
|
|
74
|
+
| `canonical_display_names` | `canonical_display.product_name_source` |
|
|
75
|
+
| `regulated_claims` | one of `campaign_intent.compliance.approved_benefit_language`, `.forbidden_claims`, `.approved_claims`, `.copy_rules` |
|
|
76
|
+
|
|
77
|
+
3. Re-run `start` or `prepare-build` with the same arguments. The file is found in the target repo automatically, or pass `--brief <file>`. Doctor's `build_brief.guided_questions` warning names the open questions and their fields.
|
|
78
|
+
|
|
79
|
+
Re-running regenerates the Assembly Report. Before any stage has recorded evidence, that costs nothing. Once a stage has recorded evidence (setup, build, polish or later), the re-run is refused unless you pass `--force`, which resets those stages and clears their evidence. Answer the questions right after `start`.
|
|
80
|
+
|
|
57
81
|
## Risky Defaults
|
|
58
82
|
|
|
59
83
|
Doctor blocks or asks when a prepared brief leaves high-impact questions unanswered, forbids alternate variant colors without naming sold variants, or contains direct contradictions such as the same payment method being both allowed and hidden.
|
package/docs/effects.md
CHANGED
|
@@ -123,6 +123,10 @@ and `build` (the intake alias for prepare-build + doctor), `theme waive`,
|
|
|
123
123
|
sidecar refresh `qa run` makes after it records the QA stage (the verdict
|
|
124
124
|
itself carries QA's own read).
|
|
125
125
|
|
|
126
|
+
### `checkpoint accept`
|
|
127
|
+
|
|
128
|
+
**Accepting a warning.** `campaigns-os checkpoint accept` records an operator's decision about a warning that a Campaigns OS check measured. It does not change readiness, does not remove the warning, does not change the status `next` reports, and cannot be used on review, unexercised, or excluded results. The warning must already be on record (in the doctor snapshot `next` writes, the Polish capture, or the full QA verdict) before it can be accepted. The accept is bound to the exact measured state and lapses when that state changes, or when its evidence is stale or cannot be reproduced. An optional expiry or review condition adds a second bound. Run it only with the operator's explicit decision, made after the warning was shown to them. A plausible name is not authorization. The command refuses placeholder and automation identities. Results are re-derived from the package's raw captures on every read, and a result whose capture is missing or does not match is listed as unexercised. These checks are tamper evidence, not proof of authorship: a hand-written record that copies every value and recomputes the checksum, or a hand-written result written together with a matching capture, is not detected.
|
|
129
|
+
|
|
126
130
|
## How to read a row
|
|
127
131
|
|
|
128
132
|
```jsonc
|
|
@@ -316,6 +320,8 @@ in for the destination — that the **declared destination is the one contacted*
|
|
|
316
320
|
|
|
317
321
|
| Row | What the offline fixture cannot reach |
|
|
318
322
|
| --- | --- |
|
|
323
|
+
| `checkpoint accept` | An accept-eligible warning: no check this version ships produces one yet. Proved: the refused accept writes nothing — no report, no stale stamp, no journal entry — and contacts nothing. |
|
|
324
|
+
| `checkpoint accept --dry-run` | An accept-eligible warning, as for `checkpoint accept`. Proved: the refused dry run writes nothing and contacts nothing. |
|
|
319
325
|
| `login` | A reachable login gateway and a human at a browser. Proved: the failure path writes nothing at all — no credential, no journal entry. |
|
|
320
326
|
| `logout` | A credential minted by a gateway login. Proved: the no-credential path writes nothing. |
|
|
321
327
|
| `page-kit parity` | A `local-serve` deploy target and a page-kit renderer to build the two renders with. Proved: the refusal writes nothing but the journal entry. |
|
package/docs/local-setup.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
For a new campaign, create an empty working folder and run this from it:
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm init -y && npm install --save-exact next-campaign-page-kit@0.2.0 && npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.
|
|
6
|
+
npm init -y && npm install --save-exact next-campaign-page-kit@0.2.0 && npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.52.0 && npx --no-install campaigns-os tooling setup --target . --platform claude
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
`npm init -y` gives the folder its own `package.json`. Without one, npm
|
|
@@ -26,7 +26,7 @@ Ledger schema id: `campaigns-os-release-ledger/v1`
|
|
|
26
26
|
Change policy version: `1.0.0`
|
|
27
27
|
Reason-code vocabulary version: `1.0.0`
|
|
28
28
|
Limits version: `1.0.0`
|
|
29
|
-
Supported surface at generation time: `1.
|
|
29
|
+
Supported surface at generation time: `1.52.0`
|
|
30
30
|
|
|
31
31
|
## Forward compatibility
|
|
32
32
|
|
package/docs/polish-evidence.md
CHANGED
|
@@ -109,6 +109,16 @@ Run it only after `npm run qa:install-browser` in an environment that permits a
|
|
|
109
109
|
loopback HTTP listener and Chromium. It is deliberately opt-in and is not part
|
|
110
110
|
of `npm run check` or CI.
|
|
111
111
|
|
|
112
|
+
**Media weight and origin.** Polish capture records, for each captured route and viewport:
|
|
113
|
+
|
|
114
|
+
- which resources came from the page's own origin after redirects, with the redirect chain;
|
|
115
|
+
- their measured transfer bytes, which include response headers;
|
|
116
|
+
- for each image, its natural size, rendered size, `object-fit`, and the pixel ratio of the capture.
|
|
117
|
+
|
|
118
|
+
It warns when a video, or an image over 500,000 transfer bytes, comes from the page's own origin. It also warns when an image has at least twice the pixels needed at its rendered size and the captured pixel ratio (currently 1), for images of at least 250,000 natural pixels; `object-fit: cover` cropping is allowed for. Captures with cached or unmeasured transfers, partial transfers that cannot decide the result, image probes that were cut short, and never-loaded media are unexercised, never small. Another origin is reported as another origin: this check does not decide whether it is a CDN or what hosting costs. Pages Polish does not capture are listed as unexercised. The image probe runs after network observation ends and adds no requests. The record is checked against the page-load capture it came from on every read.
|
|
119
|
+
|
|
120
|
+
The size check covers `<img>` elements in the page, including those inside open and closed shadow roots; images inside an iframe, `<input type=image>`, `<object>`, SVG `<image>` and CSS background images are not checked for size, and only their transfer bytes are weighed.
|
|
121
|
+
|
|
112
122
|
### Collector response records (the producer's wire form)
|
|
113
123
|
|
|
114
124
|
Inside the producer, the browser adapter's CDP collector emits one
|
|
@@ -39,8 +39,10 @@ the toolkit runs that loop in a fixed order:
|
|
|
39
39
|
1. **Build in development.** The build stage runs page-kit in the development
|
|
40
40
|
environment — `CPK_ENV=development npx campaign-build --json >
|
|
41
41
|
.campaign-runtime/page-kit-build-summary.json` — into the target's normal
|
|
42
|
-
`_site/`, and
|
|
43
|
-
|
|
42
|
+
`_site/`, and `campaigns-os record build --packet <packet>
|
|
43
|
+
--build-environment development` records
|
|
44
|
+
`stages.assembly.evidence.build_environment: "development"` on the Assembly
|
|
45
|
+
Report. `next build` names the command as
|
|
44
46
|
the `build_local_proof` action and in the build prompt; doctor warns
|
|
45
47
|
(`local_proof.build_environment`) on a completed build that is not recorded
|
|
46
48
|
as a development render. The starter templates gate every vendor loader on
|
|
@@ -55,9 +57,10 @@ the toolkit runs that loop in a fixed order:
|
|
|
55
57
|
with it locally (the parity step renders it to a temp dir, and the deploy
|
|
56
58
|
host renders it from the committed source).
|
|
57
59
|
2. **Serve and prove.** Serve `_site/` on localhost (the `next deploy` handoff
|
|
58
|
-
names the directory and any root-route rewrite)
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
names the directory and any root-route rewrite) and run `polish capture`
|
|
61
|
+
against it. Once polish is recorded, `record deploy --packet <p> --base-url
|
|
62
|
+
<url>` records the URL on `deploy.preview_url` and the deploy stage; then run
|
|
63
|
+
`qa run --browser` and the typed-card order paths against it.
|
|
61
64
|
3. **Prove the pin on the production output.** Before committing, run
|
|
62
65
|
|
|
63
66
|
```bash
|
|
@@ -367,7 +370,11 @@ Only contract-governed claims are compared, and only against `Exact` normalized
|
|
|
367
370
|
truth. Proven differences emit warn-severity `pricing` assertions named
|
|
368
371
|
`price-claim-mismatch`, `cadence-disclosure-mismatch`, or
|
|
369
372
|
`voucher-not-applied`. Decorative, ambiguous, stale, unresolved, or malformed
|
|
370
|
-
claims remain silent.
|
|
373
|
+
claims remain silent. A page showing a voucher the normalized plan cannot price
|
|
374
|
+
(no calculated pair for its code, for example a live voucher on an upsell)
|
|
375
|
+
leaves that page's price claims unresolved: the plan's only Exact truth for
|
|
376
|
+
the page is then the list price, which a voucher-priced page is expected to
|
|
377
|
+
differ from. The verdict's top-level `commercial` section records
|
|
371
378
|
coverage, sanitized missing/unmatched/invalid capture evidence, proxy issues,
|
|
372
379
|
and findings; the same findings are serialized deterministically into the flat
|
|
373
380
|
`assertions` array consumed by existing QA tooling. A proven mismatch keeps the
|
|
@@ -1599,7 +1606,7 @@ the CampaignSpec instead:
|
|
|
1599
1606
|
purchase multipliers (`ref` and `ref:2`). A uniquely referenced catalog
|
|
1600
1607
|
package with its own `qty: 3` composition is still bought once (`ref`), not
|
|
1601
1608
|
multiplied by three. **Order-bump rows — `packages[]` entries marked
|
|
1602
|
-
`is_upsell: true` — are add-ons offered beside the selected tier, not tiers**:
|
|
1609
|
+
`is_order_bump: true` or `is_upsell: true` — are add-ons offered beside the selected tier, not tiers**:
|
|
1603
1610
|
they never become a plan (a three-tier checkout with one bump plans three
|
|
1604
1611
|
tiers, so `tiers:common` on a two-upsell funnel is 12 orders, not 16), and
|
|
1605
1612
|
the runner prints a `[qa:test-order]` line naming the bump ref(s) it left
|
|
@@ -1858,6 +1865,34 @@ table in [build-packet.md](./build-packet.md#deploy-target) and
|
|
|
1858
1865
|
[Local proof mode](#local-proof-mode-deploytarget-local-serve) above for the
|
|
1859
1866
|
development build, the parity check, and the order they run in.
|
|
1860
1867
|
|
|
1868
|
+
### Tracking parameters
|
|
1869
|
+
|
|
1870
|
+
**Tracking parameters.** During a browser test order, QA adds synthetic tracking values (`utm_*`, `affid`, `sub1`, `subaffiliate2`, and up to eight names from `analytics.params.tracking.preserve`) to the pages it loads before the campaign's own navigation starts. It reports two results:
|
|
1871
|
+
|
|
1872
|
+
- **URL preservation:** whether each value is still in the address after each page navigation it observed, through to the navigation after the order. A failing step is named only when QA observed both sides of it. A reload by QA itself, an unobserved step, or an attempt that ended early ends the measurement without a pass.
|
|
1873
|
+
- **Order attribution:** whether the matching attribution field reached the request of an order the store accepted.
|
|
1874
|
+
|
|
1875
|
+
Values from `<meta name="os-tracking-tag">` tags are checked against order metadata. When page script sets attribution itself, differences are review results. Only synthetic values and equality results are stored, and order URLs in QA evidence are now stored without their query. Click ids and `funnel` are not seeded, even when listed in `tracking.preserve`, and are listed as not tested.
|
|
1876
|
+
|
|
1877
|
+
Every string QA stores in its verdict is cut at its first query (a `?`, written literally or percent-encoded), every URL in it is also cut at its fragment, and a string longer than 16 KiB is truncated.
|
|
1878
|
+
|
|
1879
|
+
## Content parameters
|
|
1880
|
+
|
|
1881
|
+
**Content parameters.** For each `analytics.params.content` entry, QA loads each applicable page twice in separate fresh browser contexts: once without the parameter and once with `?<name>=n`. It waits for the SDK to finish its display pass, then checks that the same elements whose `data-next-hide` or `data-next-show` refers to `param.<name>` are visible without the parameter and hidden with it. A parameter whose only handlers cannot hide anything with `n` is a warning. Compound conditions, conditions the SDK parses unreliably, content already hidden without the parameter, elements that change between the two loads, pages that fail to load, and pages where the SDK did not finish produce review or unexercised results, never a pass. Runs without `--browser` list these checks as not requested. No orders are placed.
|
|
1882
|
+
|
|
1883
|
+
## Policy links
|
|
1884
|
+
|
|
1885
|
+
**Policy links.** For each configured `store_terms`, `store_privacy`, `store_returns`, `store_shipping`, and `store_contact` URL, QA checks two things:
|
|
1886
|
+
|
|
1887
|
+
- **Presence:** whether any visited page links to that exact destination, with trailing slashes and default ports normalized and query parameters kept. Presence is decided only when QA read the links on every page it visits; otherwise it is unexercised.
|
|
1888
|
+
- **Availability:** whether the URL reaches an HTML page within 5 redirects, using bounded GET requests that read headers only.
|
|
1889
|
+
|
|
1890
|
+
A redirect to the right page is fine. Missing links, links whose query differs, links whose declared footer label points elsewhere, 404s, server errors and redirects to the store's home page are warnings. Authentication, rate limiting, loops, non-HTML responses, unexpected statuses, configured values that are not absolute URLs, and redirects to other destinations are review results. Network failures are unexercised. A 200 response proves only that the page is reachable. `mailto:` contacts are checked for presence only. Runs without `--browser` list these checks as not requested. Link wording is used only as an English review hint.
|
|
1891
|
+
|
|
1892
|
+
## Operator accepts (`checkpoint accept`)
|
|
1893
|
+
|
|
1894
|
+
**Accepting a warning.** `campaigns-os checkpoint accept` records an operator's decision about a warning that a Campaigns OS check measured. It does not change readiness, does not remove the warning, does not change the status `next` reports, and cannot be used on review, unexercised, or excluded results. The warning must already be on record (in the doctor snapshot `next` writes, the Polish capture, or the full QA verdict) before it can be accepted. The accept is bound to the exact measured state and lapses when that state changes, or when its evidence is stale or cannot be reproduced. An optional expiry or review condition adds a second bound. Run it only with the operator's explicit decision, made after the warning was shown to them. A plausible name is not authorization. The command refuses placeholder and automation identities. Results are re-derived from the package's raw captures on every read, and a result whose capture is missing or does not match is listed as unexercised. These checks are tamper evidence, not proof of authorship: a hand-written record that copies every value and recomputes the checksum, or a hand-written result written together with a matching capture, is not detected.
|
|
1895
|
+
|
|
1861
1896
|
## Launch Readiness Note
|
|
1862
1897
|
|
|
1863
1898
|
Campaigns OS can prove the campaign build, SDK wiring, browser behavior, and
|
|
@@ -1885,6 +1920,13 @@ use the declared topology instead of a single happy path:
|
|
|
1885
1920
|
5. Use `full` when you want every actual terminal path, raising the flood cap to
|
|
1886
1921
|
the exact planned count when necessary.
|
|
1887
1922
|
|
|
1923
|
+
`next` names step 4 at the QA stage. When the checkout page the default run
|
|
1924
|
+
drives declares an order bump (`is_order_bump: true` or `is_upsell: true` rows), it lists a second QA
|
|
1925
|
+
command, `qa_run_bump`, beside `qa_run`: the same `--test-order common` run
|
|
1926
|
+
with `--cart <base>:1,<bump>:1`, where the base is the first selector tier the
|
|
1927
|
+
checkout declares. The QA stage prompt and the human `next` output carry the
|
|
1928
|
+
same command.
|
|
1929
|
+
|
|
1888
1930
|
Record order numbers, `ref_id` values, and expected line-item shapes in the
|
|
1889
1931
|
handoff. If the browser console shows an SDK module-load error but the SDK
|
|
1890
1932
|
fallback loads and checkout/order proof passes, keep it as platform warning
|
|
@@ -1927,19 +1969,32 @@ expectations, and commerce-ref validation. A doctor-clean non-packet run means
|
|
|
1927
1969
|
matches a spec". Treat it as a residue/visual gate, not equivalent to a
|
|
1928
1970
|
packet-driven QA pass.
|
|
1929
1971
|
|
|
1930
|
-
### Per-page credential
|
|
1972
|
+
### Per-page credential binding
|
|
1931
1973
|
|
|
1932
1974
|
Canonical `qa run` emits `page-binding:<page_id>` in the existing `api-metadata`
|
|
1933
1975
|
family, with `campaigns-os-page-binding/v0` evidence (typed in the verdict schema).
|
|
1934
|
-
`match` means the
|
|
1976
|
+
`match` means the page's credential equals the expected credential;
|
|
1935
1977
|
`mismatch` is a blocker. `unknown` requires manual review. All three carry
|
|
1936
1978
|
`identity: not_verified`: credential equality never proves a unique Campaign App
|
|
1937
1979
|
ID, and no App ID is inferred from `campaignId` or `next-campaign-id`.
|
|
1938
1980
|
|
|
1939
1981
|
Expected data reuses the commercial QA resolver (packet, then spec, then an
|
|
1940
1982
|
explicit supported environment source). Conflicting authored values are unknown.
|
|
1941
|
-
|
|
1942
|
-
|
|
1983
|
+
|
|
1984
|
+
With `--browser`, the row comes from the key the Campaign Cart SDK actually
|
|
1985
|
+
sent. The SDK sends it as `Authorization` on every Campaigns API request, and
|
|
1986
|
+
the browser pass reads that header on each page it loads. The values are
|
|
1987
|
+
compared in memory and dropped: the evidence records
|
|
1988
|
+
`observation: sdk_request`, `source_kinds: ["sdk_request"]`, and `match` when
|
|
1989
|
+
every request carried the expected credential or `mismatch` when any carried
|
|
1990
|
+
another. This works whatever the
|
|
1991
|
+
page's scripts look like. A page that sent no Campaigns API request, or a run
|
|
1992
|
+
with no single expected credential, keeps the static row described below.
|
|
1993
|
+
|
|
1994
|
+
Without `--browser`, or for such a page, the row is a static read of what the
|
|
1995
|
+
page declares (`observation: static_declaration`). The SDK loads
|
|
1996
|
+
`window.nextConfig.apiKey` before `next-api-key` at boot, so meta wins at
|
|
1997
|
+
runtime; the static read deliberately reports differing declarations as a
|
|
1943
1998
|
conflict rather than certifying one. It does not observe SDK execution.
|
|
1944
1999
|
|
|
1945
2000
|
The bounded HTML loader is reused. HTML is parsed without execution; JavaScript
|
|
@@ -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.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.
|
|
11
|
+
Recipe kind `campaigns-os-node-v1`, revision `1.0.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.52.0`.
|
|
12
12
|
|
|
13
13
|
## What this is
|
|
14
14
|
|
|
@@ -10,7 +10,7 @@ Omit `--json` for the concise human report. Exit 0 means source-compatible; exit
|
|
|
10
10
|
|
|
11
11
|
The manifest is generated and owned by Campaign Cart, from its storage registry. The scanner has no independent SDK key list and fetches no remote code. Supply `docs/compatibility/storage-migrations.v1.json` from an unmodified checkout of a Campaign Cart release tag, v0.4.40 or later. The report records the full file SHA-256, SDK version, supported target range, registry/extractor/input digest, and Git provenance. When the supplied bytes equal their repository HEAD blob, provenance is `verified-git-blob` with commit and repository path. Proposed, modified, or copied manifests are labeled `unverified-local-file`; the label never certifies a release. Review/pin SDK provenance separately before acting on findings. A release manifest covers the target range it declares, which may end below its own SDK version: the v0.4.40 manifest, for example, covers 0.4.38 only. Targets outside the manifest's supported SDK range report unknown. A manifest whose SDK version is below its supported maximum is invalid.
|
|
12
12
|
|
|
13
|
-
`--target` must name the Git root. `--scope` is mandatory: comma-separated literal repository-relative directory/file paths; use `.` only when the complete repository is intended. Add shared JavaScript directories explicitly. `--exclude` uses the same syntax and records explicit exclusions. Archives receive no implicit exemption. Only Git-tracked `.html`, `.htm`, `.js`, `.mjs`, and `.cjs` files in the selected scope are scanned, using current working-tree bytes and per-file digests; untracked files and built dependencies are outside this evidence. A selected HTML page's local script outside the selected files reports unknown. Relative scripts affected by an HTML `<base href>` also report unknown; review their actual dependency paths. Remote SDK and third-party scripts are not fetched or analyzed.
|
|
13
|
+
`--target` must name the Git root. `--scope` is mandatory: comma-separated literal repository-relative directory/file paths; use `.` only when the complete repository is intended. Add shared JavaScript directories explicitly. `--exclude` uses the same syntax and records explicit exclusions. Archives receive no implicit exemption. Only Git-tracked `.html`, `.htm`, `.js`, `.mjs`, and `.cjs` files in the selected scope are scanned, using current working-tree bytes and per-file digests; untracked files and built dependencies are outside this evidence. A selected HTML page's local script outside the selected files reports unknown. In a Page Kit campaign (a page under `src/<slug>/`), a script src written `{{ 'js/x.js' | campaign_asset }}` resolves to `src/<slug>/assets/js/x.js`, as Page Kit serves it, and a page's frontmatter `scripts:` entries resolve the same way, since a layout's `{% for script in scripts %}` loop loads them through that filter. Any other `campaign_asset` value, a path that resolves outside the assets folder, and frontmatter that is not valid YAML or whose `scripts` is not a list of paths report unknown. Relative scripts affected by an HTML `<base href>` also report unknown; review their actual dependency paths. Remote SDK and third-party scripts are not fetched or analyzed.
|
|
14
14
|
|
|
15
15
|
Acorn parses JavaScript ASTs; parse5 identifies executable inline HTML scripts and source offsets. Findings inventory storage area, operation, key, file/line/column, reason, and SDK-owned public replacement when supplied. The scanner recognizes direct `localStorage`/`sessionStorage`, `window`/`globalThis`/`self` properties, simple constant browser-global/storage aliases and storage destructuring, literal `getItem`/`setItem`/`removeItem` calls (including bracket method notation), property access/assignment/deletion, and lexical constant string indirection. Comments, ordinary strings, JSON script blocks and public store calls do not become storage reads. A literal legacy SDK key at or after its manifest migration boundary is incompatible. Guessed prefixes cannot establish compatibility.
|
|
16
16
|
|