@nextcommerce/campaigns-os 1.50.0 → 1.53.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 +474 -0
- package/agents/claude/CLAUDE.md +4 -2
- package/agents/codex/AGENTS.md +3 -1
- package/agents/copilot/copilot-instructions.md +3 -1
- package/agents/cursor/campaigns-os.mdc +3 -1
- 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 +282 -14
- package/contracts/release-ledger.json +1233 -0
- package/contracts/supported-surface.json +2 -2
- 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 +2 -0
- package/docs/build-packet.md +148 -23
- package/docs/campaign-build-brief.md +57 -2
- package/docs/effects.md +14 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/polish-evidence.md +12 -0
- package/docs/progress-snapshots.md +2 -4
- package/docs/qa-and-test-orders.md +47 -4
- package/docs/readback.md +19 -2
- 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/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 +5 -3
- package/skills/next-campaigns-os/SKILL.md +11 -20
- package/skills/next-campaigns-os/references/session-intake.md +17 -9
- package/skills/next-campaigns-os-setup/SKILL.md +4 -3
- package/skills/next-campaigns-polish/SKILL.md +7 -5
- package/skills/next-campaigns-qa/SKILL.md +8 -5
- package/skills.json +10 -10
- package/src/adapter-decision-contract.mjs +1 -1
- package/src/brand-theme.mjs +33 -20
- package/src/brief-summary.mjs +275 -0
- package/src/build-brief.mjs +229 -30
- 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 +467 -143
- package/src/commercial-journey.mjs +65 -4
- package/src/commercial-parity.mjs +6 -1
- package/src/contrast.mjs +649 -0
- package/src/design-source-publication.mjs +110 -3
- package/src/doctor/checks.mjs +415 -33
- package/src/doctor/inspect.mjs +53 -2
- package/src/doctor/next-step.mjs +16 -7
- package/src/input-currency.mjs +394 -0
- package/src/input-refresh.mjs +708 -0
- 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 +660 -141
- package/src/polish-capture.mjs +1 -1
- package/src/polish-deadline.mjs +7 -0
- package/src/polish-media-weight.mjs +492 -0
- package/src/polish-node.mjs +400 -28
- package/src/polish-readability.mjs +828 -0
- package/src/progress-node.mjs +19 -3
- package/src/qa-browser.mjs +439 -180
- package/src/qa-content-params.mjs +889 -0
- package/src/qa-node.mjs +116 -13
- 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 +482 -0
- package/src/qc-check-registry.mjs +91 -0
- package/src/qc-results.mjs +1341 -0
- package/src/readback.mjs +58 -10
- 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-ledger.mjs +235 -44
- package/src/stage-record.mjs +165 -33
- package/src/tooling-setup.mjs +1 -1
|
@@ -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.53.0",
|
|
4
4
|
"package_exports": [
|
|
5
5
|
"./commercial-journey",
|
|
6
6
|
"./commercial-parity",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"sha256": "dfc9abed38d456969e47a21f606d308a03bdf036f3466f7d6e47a602747dacf4"
|
|
121
121
|
},
|
|
122
122
|
"contracts/effects.v1.json": {
|
|
123
|
-
"sha256": "
|
|
123
|
+
"sha256": "8282f7ffe91aaf12c4b79e4431b5ddc35cce82e42f1746d4a0ea44f54bd7fb8e"
|
|
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",
|
|
@@ -94,6 +94,8 @@ used and reported with `theme.foreground.low_contrast`. With no declared CTA
|
|
|
94
94
|
foreground, the output is unchanged. Declarations inside CSS comments are not
|
|
95
95
|
read, so a commented-out token or rule never supplies a CTA or body text colour.
|
|
96
96
|
|
|
97
|
+
Contrast is computed with the same unrounded measurement Polish and QA use, so a declared colour at 4.495:1 now warns. The generator's colour choices are unchanged. Polish measures the rendered text at its real size and weight: a qualifying large label at 3:1 passes there, and smaller text produces a warning the operator can accept.
|
|
98
|
+
|
|
97
99
|
### Body text prefers the darkest declared text token
|
|
98
100
|
|
|
99
101
|
A declared text token is a `:root` custom property in the selected source whose
|
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,8 +46,12 @@ 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
|
-
|
|
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
|
+
the Assembly Report binds, which QA refuses), and `next` routes to the build
|
|
52
|
+
and names the refresh: run `campaigns-os record spec --packet <packet>` to bind
|
|
53
|
+
the new content and keep stage history (see
|
|
54
|
+
[Refreshing after a CampaignSpec change](#refreshing-after-a-campaignspec-change)). Keep the spec, source, dependency
|
|
50
55
|
pins and canonical sidecars in Git. Use `readback` and `next` after a fresh
|
|
51
56
|
checkout; identity survives the move, but proof freshness is assessed again.
|
|
52
57
|
|
|
@@ -65,6 +70,56 @@ not relax template certification, source proof, store/SDK parity, polish,
|
|
|
65
70
|
commerce checks, or typed-card checkout proof. Resolve their reported gates;
|
|
66
71
|
localhost readiness is not production approval.
|
|
67
72
|
|
|
73
|
+
### Reading package, offer and shipping refs
|
|
74
|
+
|
|
75
|
+
No command writes commerce refs into a local spec, and `login` does not read
|
|
76
|
+
them: a gateway login serves only the Store Profile fields that `spec derive
|
|
77
|
+
--from-store` fills. Read them with the campaign's public Campaigns API key,
|
|
78
|
+
through the request doctor and QA already make to check built pages against
|
|
79
|
+
the live campaign: one GET of NEXT's proxy with the key in the
|
|
80
|
+
`X-Campaign-Key` header. No store or Admin credential is involved.
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
CAMPAIGN_KEY='<public key>' node -e '
|
|
84
|
+
fetch("https://campaign-map.nextcommerce.com/api/campaign", {
|
|
85
|
+
headers: { Accept: "application/json", "X-Campaign-Key": process.env.CAMPAIGN_KEY },
|
|
86
|
+
}).then(async (res) => console.log(res.status, JSON.stringify(await res.json(), null, 2)));
|
|
87
|
+
'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`curl -sS -H "Accept: application/json" -H "X-Campaign-Key: <public key>" https://campaign-map.nextcommerce.com/api/campaign`
|
|
91
|
+
returns the same. Add `?ref_id=<campaign id>` when one key serves several
|
|
92
|
+
campaigns. Other HTTP clients work with the same header, but the proxy refuses
|
|
93
|
+
some default user agents, Python `urllib`'s and Perl `libwww-perl`'s among
|
|
94
|
+
them, with a 403 whose body is `error code: 1010`; send another `User-Agent`
|
|
95
|
+
or use one of the commands above.
|
|
96
|
+
|
|
97
|
+
The answer is an envelope, `{ ok, status, endpoint, requested_ref_id,
|
|
98
|
+
retrieved_at, data }`, with `requested_ref_id` present only when `?ref_id=`
|
|
99
|
+
was sent; `ok: false` carries an `error` instead of a campaign. `data` is the
|
|
100
|
+
campaign retrieve body the Campaign Cart SDK reads in the browser: one
|
|
101
|
+
campaign, or an array of them, in which case use the entry whose `id` is the
|
|
102
|
+
selected campaign. Copy from it as follows:
|
|
103
|
+
|
|
104
|
+
| `data` field | CampaignSpec field |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `id` | `campaign.ref_id` (doctor and QA send it as `?ref_id=`) |
|
|
107
|
+
| `name`, `currency`, `language`, `payment_env_key` | the `campaign` fields of the same name |
|
|
108
|
+
| `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 |
|
|
109
|
+
| `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` |
|
|
110
|
+
| `shipping_methods[]`: `ref_id`, `code`, `price` | root `shipping_methods[]` |
|
|
111
|
+
|
|
112
|
+
A package's ref is its `ref_id`, numbered within the campaign. `external_id`,
|
|
113
|
+
`product_id` and `product_variant_id` are catalog ids, never package refs. The
|
|
114
|
+
same `ref_id` values are what pages render in `data-next-package-id` and
|
|
115
|
+
`data-next-shipping-id`. The read supplies refs and the values the campaign
|
|
116
|
+
serves, not the selection: which packages and offers each page carries, and
|
|
117
|
+
the role flags `is_upsell`, `is_order_bump` and `default_selected`, come from
|
|
118
|
+
the brief and the operator. Copy prices and availability from the read rather
|
|
119
|
+
than typing them from the brief. After the build, doctor and QA repeat this
|
|
120
|
+
read and block a page ref the live campaign does not serve (see
|
|
121
|
+
[the live campaign read](effects.md#the-live-campaign-read)).
|
|
122
|
+
|
|
68
123
|
## Root-Served Campaigns (`campaign.route_root`)
|
|
69
124
|
|
|
70
125
|
Most campaigns are served under a slug prefix (`/<public_route_slug>/...`), and
|
|
@@ -470,16 +525,13 @@ writing it would declare an analytics contract QA then blocks on. A derived
|
|
|
470
525
|
field the entry does not carry at all is listed under `not_in_target[]` and
|
|
471
526
|
left as it is.
|
|
472
527
|
|
|
473
|
-
|
|
528
|
+
`spec derive` writes the spec alone and no longer rebinds identity: the
|
|
474
529
|
Build Context's `spec.hash` / `spec.material_hash` and the Assembly Report's
|
|
475
|
-
`identity.spec_hash` / `identity.spec_material_hash`
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
is what keeps a derive-then-QA run conformant. A sidecar already carrying
|
|
481
|
-
another identity is left alone with a `spec.derive.identity_not_rebound`
|
|
482
|
-
warning naming `prepare-build`. A derived route change also warns
|
|
530
|
+
`identity.spec_hash` / `identity.spec_material_hash` keep the spec they were
|
|
531
|
+
bound to, so every build, Polish and QA record made against the earlier
|
|
532
|
+
content reads owed again. Run `record spec` after it (the result's `next`
|
|
533
|
+
names it) to bind the derived spec; QA's verdict and `bundle check` correlate
|
|
534
|
+
against the bound material hash. A derived route change also warns
|
|
483
535
|
`spec.derive.projection_stale`: the packet's page-kit projection
|
|
484
536
|
(`source_html.pages[].page_kit`) and the Build Context page map were prepared
|
|
485
537
|
from the old routes, and `prepare-build` (or `start`) regenerates them. A
|
|
@@ -499,8 +551,8 @@ the spec resolves to, which must lie inside the spec's own directory or the
|
|
|
499
551
|
target repo (`spec.derive.spec_escapes_boundary` otherwise, so a symlinked
|
|
500
552
|
`spec.local_path` cannot redirect the write); the retained doctor sidecar is
|
|
501
553
|
marked stale after a write, and a terminal build gets a
|
|
502
|
-
`spec.derive.build_stale` warning naming
|
|
503
|
-
|
|
554
|
+
`spec.derive.build_stale` warning naming `record spec`, after which `next`
|
|
555
|
+
routes back to the build. `--dry-run` prints the
|
|
504
556
|
same diff and writes nothing; unknown flags and a valued `--dry-run` are
|
|
505
557
|
rejected. Exit 2 with `spec.derive.*` error codes and nothing written when the
|
|
506
558
|
packet cannot be read, `spec.local_path` is absent or not a file, the spec is
|
|
@@ -732,7 +784,14 @@ intended) when:
|
|
|
732
784
|
(`…attribution_drift`).
|
|
733
785
|
|
|
734
786
|
One error per finding; each names the two files and the two values, so the
|
|
735
|
-
repair is a one-line edit.
|
|
787
|
+
repair is a one-line edit. Every built HTML file under `_site/<slug>/` is
|
|
788
|
+
scanned, because every one is served. When doctor has the CampaignSpec, a
|
|
789
|
+
finding that names a file no active spec page builds to says so and lists it
|
|
790
|
+
under `stray_files`. Such a file is leftover output (page-kit does not prune
|
|
791
|
+
`_site/`) or an HTML file copied into the source, such as a design export's
|
|
792
|
+
`index.html` under `assets/`. The repair is to remove the source file if
|
|
793
|
+
there is one, delete the built file, and rebuild and record the build again,
|
|
794
|
+
not to retag it. Pages whose route contains a `-backup-` or `-old-`
|
|
736
795
|
segment are parked copies: skipped and listed on the gate as `pages_skipped`,
|
|
737
796
|
never scanned. Presence is not asserted: a campaign whose pages carry no key
|
|
738
797
|
at all, or no `setAttribution` anywhere, passes on the funnel tag alone.
|
|
@@ -893,6 +952,24 @@ canonical rendered output of every certified starter family
|
|
|
893
952
|
it reads for credential declarations (`script-parse:<page_id>`; see
|
|
894
953
|
[QA and test orders](qa-and-test-orders.md)).
|
|
895
954
|
|
|
955
|
+
### Built-output cart placeholder check (`built_output.cart_placeholders`)
|
|
956
|
+
|
|
957
|
+
**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.
|
|
958
|
+
|
|
959
|
+
### Built-output smoke checks (`built_output.smoke_qc`)
|
|
960
|
+
|
|
961
|
+
**Smoke checks (`built_output.smoke_qc`).** Doctor warns on built pages that have:
|
|
962
|
+
|
|
963
|
+
- an in-page `#link` with no matching `id` or `name` on the same page;
|
|
964
|
+
- no favicon link;
|
|
965
|
+
- no `og:title`, `og:description` or `og:image`;
|
|
966
|
+
- an `og:image` that is relative or points to a missing file;
|
|
967
|
+
- the Tailwind CDN script in a production build;
|
|
968
|
+
- references to the `cdn.29next.store` asset host in any attribute or `<style>` block (use `cdn.cachebucket.com`);
|
|
969
|
+
- `localhost` or loopback URLs in a production build.
|
|
970
|
+
|
|
971
|
+
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.
|
|
972
|
+
|
|
896
973
|
> **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
974
|
|
|
898
975
|
## Artifact Locations
|
|
@@ -980,8 +1057,13 @@ full.
|
|
|
980
1057
|
and auto-discover `campaign-build-brief.yaml`, `.yml`, or `.json` from the
|
|
981
1058
|
source root or target repo. When none is present, Campaigns OS creates a guided
|
|
982
1059
|
draft at `.campaign-runtime/input/campaign-build-brief.normalized.json`.
|
|
983
|
-
|
|
984
|
-
prepared
|
|
1060
|
+
A brief file is guided unless it sets `"brief_mode": "prepared"` (a campaign
|
|
1061
|
+
whose Assembly Report already records a prepared brief stays prepared). Save
|
|
1062
|
+
answers later with `record brief`, which keeps stage evidence unless the brief's
|
|
1063
|
+
material content changes; a presentation change makes build, Polish and QA
|
|
1064
|
+
owed again, and a `qa_policy` change makes QA alone owed again. See
|
|
1065
|
+
[Campaign Build Brief](./campaign-build-brief.md) for the schema, the mode
|
|
1066
|
+
rule and the stage map.
|
|
985
1067
|
|
|
986
1068
|
`start` / `prepare-build` also prepares the normalized Design Source Package at
|
|
987
1069
|
`.campaign-runtime/input/design-source-package.json`. When that path is absent,
|
|
@@ -1251,9 +1333,10 @@ URL.
|
|
|
1251
1333
|
`local-serve` also selects **local proof mode** for the build stage: page-kit
|
|
1252
1334
|
is built in the development environment (`CPK_ENV=development npx
|
|
1253
1335
|
campaign-build --json > .campaign-runtime/page-kit-build-summary.json`) into
|
|
1254
|
-
`_site/`, and
|
|
1336
|
+
`_site/`, and `campaigns-os record build --packet <packet> --build-environment
|
|
1337
|
+
development` records `stages.assembly.evidence.build_environment:
|
|
1255
1338
|
"development"` on the Assembly Report (a free-form stage field; no schema
|
|
1256
|
-
change). The starter templates gate every vendor loader on the environment,
|
|
1339
|
+
change; never hand-edited). The starter templates gate every vendor loader on the environment,
|
|
1257
1340
|
and a production build's protocol-relative loaders (`//host/...`) fail over a
|
|
1258
1341
|
plain-HTTP local serve, voiding polish capture unwaivably; the SDK's `dl_*`
|
|
1259
1342
|
events still fire in development. Before commit, `campaigns-os page-kit parity
|
|
@@ -1602,6 +1685,43 @@ records it (`campaigns-os record setup|build|polish`; deploy and QA record their
|
|
|
1602
1685
|
own stages) → calls `next` again → repeat until stage="done"
|
|
1603
1686
|
```
|
|
1604
1687
|
|
|
1688
|
+
### Refreshing after a CampaignSpec change
|
|
1689
|
+
|
|
1690
|
+
When the CampaignSpec's material content changes, every build, Polish and QA record made against the earlier content stops counting as current, in local-spec, saved-Map and gateway packets alike. For a saved Map or gateway packet, Campaigns OS compares the copy intake fetched; it does not check whether that copy is the latest remote revision, and reports the remote as unconfirmed. Doctor warns `spec.material_stale`, and `next`, progress, readback, the Assembly Report's own status and the QA gate show the stages that are owed again; `next` routes to the first one. Run `campaigns-os record spec --packet <packet>` to bind the new content: it updates the build context and report identity, re-reads the brief file last saved (by intake or `record brief`), keeps waivers, warning accepts, the applied theme and deploy settings, and moves each superseded stage record into that stage's `history`, which is never used as current proof. `spec derive` no longer rebinds identity; run `record spec` after it. A change to the set, order or routes of active pages still needs intake. Re-recording an unchanged build after a spec change keeps the build owed, because the change has not reached the pages, unless the operator records a reason with `--deviation-reason`; this holds even when `record spec` was not run, and that `record build` also marks Polish and QA owed again. Polish stays owed until a new `polish capture`, and QA must produce a new verdict against the current content. Polish measurements of byte-identical output keep their values, but the Polish stage is owed again. These checks are tamper evidence, not proof: a hand-written record that copies the current values, or a packet pointed at an older copy of the spec, is not detected.
|
|
1691
|
+
|
|
1692
|
+
The CampaignSpec's material content is the whole spec except `spec_identity`,
|
|
1693
|
+
`slug`, `map_id` and `saved_at`; a page `label` edit is material. A spec
|
|
1694
|
+
change affects the stages as follows:
|
|
1695
|
+
|
|
1696
|
+
| Stage | After a spec material change |
|
|
1697
|
+
|---|---|
|
|
1698
|
+
| `prepare_build` | unchanged (`record spec` refuses a page-scope change, which needs intake) |
|
|
1699
|
+
| `doctor` | recomputed on every read |
|
|
1700
|
+
| `setup` | kept |
|
|
1701
|
+
| `assembly` | owed: `required`, `required_by: "spec"`, `required_for: ["polish", "qa"]` |
|
|
1702
|
+
| `polish` | owed: `required`, `required_by: "spec"`, `required_for: ["qa"]` |
|
|
1703
|
+
| `deploy` | kept |
|
|
1704
|
+
| `qa` | owed: `required`, `required_by: "spec"`, `required_for: []` |
|
|
1705
|
+
|
|
1706
|
+
`record spec` refuses, writing nothing, in this order: `spec_unreadable` (the
|
|
1707
|
+
spec does not parse), `spec_identity_changed` (its map id or local spec id
|
|
1708
|
+
names another campaign than the packet or the report), `page_scope_changed`,
|
|
1709
|
+
then, for the brief file last saved (by intake or `record brief`), `brief_too_large`,
|
|
1710
|
+
`brief_source_is_package_artifact` and `brief_file_missing` (the recorded
|
|
1711
|
+
path is missing or is not a readable regular file: a directory, a pipe or
|
|
1712
|
+
socket, a symlink to nothing, or a file without read permission). When it must
|
|
1713
|
+
re-derive the brief, it also refuses `brief_inputs_unavailable` (the Build
|
|
1714
|
+
Context lacks an intake product re-deriving the brief needs). It reads
|
|
1715
|
+
`unchanged` and writes nothing when the bound material equals the spec and no
|
|
1716
|
+
stage's spec or brief stamp differs from the current content; a stage that
|
|
1717
|
+
does not record which content it was made against is named in a
|
|
1718
|
+
`binding_unknown` notice instead. Otherwise it refreshes, re-deriving the
|
|
1719
|
+
normalized brief from the brief file last saved, so an edit made to the
|
|
1720
|
+
normalized brief itself is replaced by what its source gives. A stage whose spec
|
|
1721
|
+
stamp already equals the new content (a build recorded after the edit) is not
|
|
1722
|
+
demoted. `record spec` does not write the Build Packet. `--dry-run` runs every
|
|
1723
|
+
check and writes nothing.
|
|
1724
|
+
|
|
1605
1725
|
### Recording stage completion
|
|
1606
1726
|
|
|
1607
1727
|
Setup, build and polish completion is recorded with one command each, never by
|
|
@@ -1610,14 +1730,17 @@ hand-editing `.campaign-runtime/` JSON:
|
|
|
1610
1730
|
| Command | Writes | Refused (nothing written) when |
|
|
1611
1731
|
|---|---|---|
|
|
1612
1732
|
| `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 |
|
|
1613
|
-
| `campaigns-os record build --packet <p
|
|
1733
|
+
| `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 |
|
|
1614
1734
|
| `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 |
|
|
1615
1735
|
| `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` |
|
|
1616
1736
|
| `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 |
|
|
1617
1737
|
|
|
1618
1738
|
Each command also refuses a stage `next` has not reached: while doctor's
|
|
1619
1739
|
prepare-build gate is set (`next` answers prepare-build) or while an earlier
|
|
1620
|
-
stage in the order below is not terminal.
|
|
1740
|
+
stage in the order below is not terminal. The exception is the one `next`
|
|
1741
|
+
makes: on the local preview, a polish doctor carries forward (never recorded
|
|
1742
|
+
for this build) does not hold `record deploy` back, as it does not hold `next`;
|
|
1743
|
+
polish stays owed and QA reports it.
|
|
1621
1744
|
|
|
1622
1745
|
Each command reads the same packet, Build Context and Assembly Report `next`
|
|
1623
1746
|
reads (`--context` / `--report` override them the same way), validates what it
|
|
@@ -1639,7 +1762,9 @@ whenever the report's campaign identity does not match the packet, including
|
|
|
1639
1762
|
for packets with no Design Source Package. Every command also adds
|
|
1640
1763
|
`recorded_by`, and `completed_at` unless it records a blocked Polish, to the
|
|
1641
1764
|
stage it records. `--dry-run` runs every check, takes no lock and writes nothing. A failed
|
|
1642
|
-
check exits non-zero with the problems listed, one per line
|
|
1765
|
+
check exits non-zero with the problems listed, one per line; a value outside a
|
|
1766
|
+
schema enum (for example an `adapter_decisions` policy) is listed with the
|
|
1767
|
+
values the schema allows and the value it got. Re-run `record
|
|
1643
1768
|
build` after every page-kit build; a rebuild that changes the output needs
|
|
1644
1769
|
`polish capture` and `record polish` again.
|
|
1645
1770
|
|
|
@@ -35,7 +35,7 @@ The Build Packet, Build Context, and Assembly Report all reference that normaliz
|
|
|
35
35
|
|
|
36
36
|
## Modes
|
|
37
37
|
|
|
38
|
-
Prepared mode is for veteran users. A complete brief should let the build proceed without business questions. If a
|
|
38
|
+
Prepared mode is for veteran users, and a brief file selects it with `"brief_mode": "prepared"` (see [Brief mode](#brief-mode)). A complete brief should let the build proceed without business questions. If a prepared brief is incomplete or contradictory, doctor blocks with `build_brief.*` errors.
|
|
39
39
|
|
|
40
40
|
Guided mode is for new or partial inputs. Campaigns OS drafts a brief from CampaignSpec, page mappings, template family, source assets, and available runtime hints. It records only high-impact unresolved questions as warnings so existing builds still run while the business uncertainty is visible.
|
|
41
41
|
|
|
@@ -47,13 +47,68 @@ 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
|
+
## Saving Answers
|
|
60
|
+
|
|
61
|
+
Save brief answers with `campaigns-os record brief --packet <packet>`. Copy the normalized draft to `campaign-build-brief.json` in the target repository, set the fields named in the open questions, and run `record brief`. You do not need to re-run `start` or `prepare-build`, and no stage evidence is cleared unless the saved answers change the brief's material content. `record brief` refuses a path that points at one of the files Campaigns OS writes itself, such as the normalized brief.
|
|
62
|
+
|
|
63
|
+
A brief is prepared, where every open question blocks, only when the file sets `"brief_mode": "prepared"`. A brief file without `brief_mode` is guided: questions you leave open remain warnings. One exception keeps existing campaigns as they were built: when the campaign's Assembly Report already records a prepared brief, a file without `brief_mode` stays prepared. Set `"brief_mode": "guided"` to make it guided.
|
|
64
|
+
|
|
65
|
+
Saving a brief whose content is unchanged keeps all stage evidence and statuses. Reformatting, reordering keys, writing a default value explicitly, or changing only `_meta` also keeps all stage evidence and statuses; the save may rewrite the brief files. When the presentation sections change (`campaign_intent`, `design_authority`, `brand`, `media`, `offer_presentation`, `promo_urgency`, `commerce_surfaces`, `canonical_display`, `template_residue_policy`, or any section you add), build, Polish and QA are owed again. When only `qa_policy` changes, QA alone is owed again. Setup and deploy records are kept. Each affected stage records when its inputs changed. Each replaced record is kept under the stage's `history`; the last five superseded records per stage are kept, and older ones are dropped. History is display only and is never used as current proof.
|
|
66
|
+
|
|
67
|
+
Recording build again over output that has not changed since the brief or CampaignSpec changed does not make the build current: `next` keeps asking for a build, because the change has not reached the pages. If the operator decides the change needs no change to the built pages, record build with `--deviation-reason "<the operator's reason>"`; the build is then recorded with a warning, and the reason is shown in `next` and the QC handoff. `--deviation-reason` is a general flag any caller can pass: Campaigns OS records the reason, but cannot tell whether the operator chose it. A CampaignSpec change that was never saved with `record spec` is recorded by the next `record build`, `record polish` or QA run, with the same effect; `record build` then also marks Polish and QA owed again. An edit to the brief file is not read until it is saved with `record brief`: until then the build reads the saved brief, and doctor warns `build_brief.input_unsaved`. The same applies after `prepare-build --force`, which now keeps stage history, archives the build, Polish and QA records it clears, and keeps the record that the inputs changed. Recording Polish again does not make it current until a new `polish capture` runs after the change. Recording Polish as skipped, with a skip reason, while it is owed is the operator's decision to skip it: the input change no longer makes Polish owed. The Polish gate still passes only a completed Polish, so `next` stays at Polish until one is recorded (see `docs/polish-evidence.md`).
|
|
68
|
+
|
|
69
|
+
A build, Polish or QA record that does not say which brief and CampaignSpec content it was made against counts as unconfirmed, not current. Records made by earlier releases are unconfirmed, so after upgrading, `next` routes back to build once; record build and Polish again and run QA again. These checks are tamper evidence, not proof: a record written by hand that copies the current values or removes the record of an input change, or a packet pointed at an older copy of the spec or brief, is not detected.
|
|
70
|
+
|
|
71
|
+
The fields that close each open question (doctor's `build_brief.guided_questions` warning names the open questions and their fields):
|
|
72
|
+
|
|
73
|
+
| Question | Fields |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `page_design_authority` | `design_authority.<page_id>.source` for each page named in the question |
|
|
76
|
+
| `brand_palette_cta` | `brand.commerce_palette_source`, `brand.cta_style` |
|
|
77
|
+
| `variant_media_rules` | `media.sold_variants`, `media.allow_other_variant_colors` |
|
|
78
|
+
| `bundle_pricing_presentation` | `offer_presentation.bundle_cards.primary_price` |
|
|
79
|
+
| `promo_urgency_copy` | `promo_urgency.header_claim_source` (`campaign_offers` or `none`), `promo_urgency.timer_label` (the template timer's label, or `none`) |
|
|
80
|
+
| `payment_methods_trust` | `commerce_surfaces.payment_methods_allowed` |
|
|
81
|
+
| `canonical_display_names` | `canonical_display.product_name_source` |
|
|
82
|
+
| `regulated_claims` | one of `campaign_intent.compliance.approved_benefit_language`, `.forbidden_claims`, `.approved_claims`, `.copy_rules` |
|
|
83
|
+
|
|
84
|
+
### Brief mode
|
|
85
|
+
|
|
86
|
+
The mode is decided in this order:
|
|
87
|
+
|
|
88
|
+
1. `"brief_mode": "prepared"` in the file: prepared.
|
|
89
|
+
2. `"brief_mode": "guided"` in the file: guided.
|
|
90
|
+
3. No `brief_mode`, and the campaign's Assembly Report on disk records a prepared brief: prepared.
|
|
91
|
+
4. No `brief_mode` otherwise, including the generated draft and every new hand-written file: guided.
|
|
92
|
+
5. Any other `brief_mode` value is the error `build_brief.brief_mode`, and the brief reads `invalid`.
|
|
93
|
+
|
|
94
|
+
The file's own `_meta` (including a copied draft's `_meta.mode`) is never read for the mode; the normalized brief records how the mode was decided in `_meta.mode_source` (`field`, `legacy_report` or `generated`). Input errors block in both modes. In guided mode, open questions and evaluation gates stay warnings.
|
|
95
|
+
|
|
96
|
+
### What a brief change makes owed
|
|
97
|
+
|
|
98
|
+
The brief's material content is the normalized brief without `_meta`, `status`, `questions`, `gates`, `confidence`, `schema_version`, `brief_mode` and `qa_policy.enforcement`, in two parts: `qa_policy`, and every other section (presentation).
|
|
99
|
+
|
|
100
|
+
| Stage | Presentation changed | `qa_policy` changed |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `prepare_build` | brief blockers re-derived; other blockers kept | re-derived |
|
|
103
|
+
| `doctor` | recomputed on every read | recomputed on every read |
|
|
104
|
+
| `setup` | kept | kept |
|
|
105
|
+
| `assembly` | owed (`required`, `required_by: "brief"`) | kept |
|
|
106
|
+
| `polish` | owed (`required`, `required_by: "brief"`) | kept |
|
|
107
|
+
| `deploy` | kept | kept |
|
|
108
|
+
| `qa` | owed (`required`, `required_by: "brief"`) | owed |
|
|
109
|
+
|
|
110
|
+
Only completed records are made owed. A skipped stage stays skipped.
|
|
111
|
+
|
|
57
112
|
## Risky Defaults
|
|
58
113
|
|
|
59
114
|
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,18 @@ 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
|
+
|
|
130
|
+
### `record brief` and `record spec`
|
|
131
|
+
|
|
132
|
+
**Saving brief answers.** `campaigns-os record brief --packet <packet>` saves Campaign Build Brief answers without re-running `start` or `prepare-build`. It writes the normalized brief under `{target}/.campaign-runtime/input/`, the Build Packet, the build context, the Assembly Report and the doctor-sidecar stale stamp, and sends nothing. It refuses a path that points at one of the files Campaigns OS writes itself, such as the normalized brief. Saving a brief whose content is unchanged keeps all stage evidence and statuses. Reformatting, reordering keys, writing a default value explicitly, or changing only `_meta` also keeps all stage evidence and statuses; the save may rewrite the brief files. When the presentation sections change, build, Polish and QA are owed again. When only `qa_policy` changes, QA alone is owed again. Setup and deploy records are kept. Each replaced record is kept under the stage's `history`; the last five superseded records per stage are kept, and history is never used as current proof.
|
|
133
|
+
|
|
134
|
+
**Binding a CampaignSpec change.** `campaigns-os record spec --packet <packet>` binds the new CampaignSpec content: it updates the build context and report identity, re-reads the brief file last saved (by intake or `record brief`), keeps waivers, warning accepts, the applied theme and deploy settings, and moves each superseded stage record into that stage's `history`. It writes the build context, the Assembly Report, the normalized brief and the doctor-sidecar stale stamp; it does not write the Build Packet, and it sends nothing. `spec derive` no longer rebinds identity; run `record spec` after it. A change to the set, order or routes of active pages still needs intake.
|
|
135
|
+
|
|
136
|
+
Both rows are tier C, because a change makes recorded stages owed again, and each `--dry-run` form is tier none: it writes nothing and appends no lifecycle entry. A CampaignSpec change that was never saved with `record spec` is recorded by the next `record build`, `record polish` or QA run, with the same effect; `record build` then also marks Polish and QA owed again. An edit to the brief file is not read until it is saved with `record brief`: until then the build reads the saved brief, and doctor warns `build_brief.input_unsaved`. Intake now also refuses, without `--force`, to discard recorded waivers, warning accepts, stage history, an applied theme, or a recorded deploy target, preview URL, production URL, order-path depth or allowed-domains confirmation; with `--force`, it keeps stage history, archives the build, Polish and QA records it clears, and keeps the record that the inputs changed.
|
|
137
|
+
|
|
126
138
|
## How to read a row
|
|
127
139
|
|
|
128
140
|
```jsonc
|
|
@@ -316,6 +328,8 @@ in for the destination — that the **declared destination is the one contacted*
|
|
|
316
328
|
|
|
317
329
|
| Row | What the offline fixture cannot reach |
|
|
318
330
|
| --- | --- |
|
|
331
|
+
| `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. |
|
|
332
|
+
| `checkpoint accept --dry-run` | An accept-eligible warning, as for `checkpoint accept`. Proved: the refused dry run writes nothing and contacts nothing. |
|
|
319
333
|
| `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
334
|
| `logout` | A credential minted by a gateway login. Proved: the no-credential path writes nothing. |
|
|
321
335
|
| `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.53.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.53.0`
|
|
30
30
|
|
|
31
31
|
## Forward compatibility
|
|
32
32
|
|
package/docs/polish-evidence.md
CHANGED
|
@@ -109,6 +109,18 @@ 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
|
+
|
|
122
|
+
**Readability.** `polish capture` also measures text contrast on every page under `_site/<slug>/` (up to 128 pages per run), at desktop (1440×1200) and mobile (390×844) widths, device pixel ratio 1, including campaigns whose pages are all template stock: the Polish route plan now covers the mapped pages plus every built page, while the page-load capture keeps its own page set. On a campaign with no mapped pages, `polish capture` measures readability, prints the page-load message as a warning, and exits successfully. The text it reads: purchase, accept and decline controls; submit buttons and submit-type inputs; SDK action controls; bundle cards (names, prices, badges and the card selected at load); order bumps; prices; checkout labels and placeholder hints; and other body text. Ratios follow WCAG 2.x and are compared unrounded: 4.5:1 for normal text, 3:1 for large text (at least 24 CSS px, or at least 18.66 CSS px at weight 700 or more; semibold does not qualify). Colours in any CSS Color 4 form the browser reports are converted to sRGB; values outside sRGB are clipped and marked. Backgrounds are composited through transparent layers. SVG text is measured with its fill colour, and placeholder hints with their own colour, size and weight. Gradients, background images, pseudo-element backgrounds, a `::first-line` or `::first-letter` style that changes the text's colour, background or opacity, overlapping positioned layers or images (and the shapes of an SVG drawing under its text), text clipped to a background, colours that cannot be read (including a text fill colour, and an SVG fill that is not a plain colour or is combined with a stroke), and controls marked disabled only by the SDK's `next-disabled` class produce a review result with a reason and a crop; so do opacity, filters, blend modes and masks on the text or on any element around it. `clip-path` is ignored. Disabled controls (`disabled` or `aria-disabled="true"`) are listed as excluded (inactive control) and are never accepted; `readonly` is not disabled. Polish measures the states present when the page loads — default, selected, active, and open panels — without clicking, scrolling or typing. Text that has not rendered, controls still loading, controls hidden at load, text supplied by CSS `content`, selection states and collapsed panels not present at load, text inside closed shadow roots or cross-origin frames, pages measured before the SDK, stylesheets or fonts were ready, and pages not captured are listed as unexercised. Overlapping layers are detected by box intersection: a layer the page paints over text in another way is not detected. A ratio under the requirement is a warning. Warnings never block Polish or change readiness. The probe reads the page without changing it, and adds at most about five minutes of work per run; pages it could not reach in that time are listed as unexercised. The stored measurements are re-derived on every read, and the page set, widths and campaign are checked against the current build; but the colours the browser reported are tamper-evident only, like other Polish captures. Readability and media weight results read as stale when the built output differs from the recorded build, even before a new `record build`.
|
|
123
|
+
|
|
112
124
|
### Collector response records (the producer's wire form)
|
|
113
125
|
|
|
114
126
|
Inside the producer, the browser adapter's CDP collector emits one
|
|
@@ -18,8 +18,7 @@ blocked QA keeps it open. A progress delivery cannot close a run.
|
|
|
18
18
|
|
|
19
19
|
The strict v0 object carries its schema, package version, observation timestamp,
|
|
20
20
|
producer (`next` or `qa`), opaque progress stream and sequence, prior snapshot ID,
|
|
21
|
-
content digest, Map ID and separate revision/spec/build hashes. Six independent
|
|
22
|
-
stage records retain observed status and explicit build binding. Continuation
|
|
21
|
+
content digest, Map ID and separate revision/spec/build hashes. Six independent stage records carry each stage's effective status and explicit build binding: the status the Assembly Report records, except that a stage made owed again by a brief or CampaignSpec change reads `required`, and a stage whose inputs cannot be confirmed reads `unknown`. Continuation
|
|
23
22
|
retains the canonical stage, blocked/divergent flags, fixed action IDs and gate
|
|
24
23
|
states. Preview carries only presence and a hash of the URL. An optional QA block
|
|
25
24
|
retains the exact verdict ID, disposition (including `ready_with_exceptions`),
|
|
@@ -28,8 +27,7 @@ retained verdict; recovery uses `qa publish`, without repeating an order.
|
|
|
28
27
|
|
|
29
28
|
No commands, prompts, free text, content, local paths, URLs, query strings,
|
|
30
29
|
credentials, order values or customer payloads enter the wire. Unknown vocabulary
|
|
31
|
-
becomes a fixed `unknown` marker and cannot imply a passing continuation. A
|
|
32
|
-
stage's observed `completed` status is a ledger claim, not independent verification.
|
|
30
|
+
becomes a fixed `unknown` marker and cannot imply a passing continuation. A stage's `completed` status is a ledger claim, checked only against the brief and CampaignSpec content it records; it is not independent verification.
|
|
33
31
|
Only matching fingerprints carry `matching`; absent or unverifiable binding is
|
|
34
32
|
`unconfirmed`. A current local output hash never proves a deployment is current.
|
|
35
33
|
|