@nextcommerce/campaigns-os 1.48.0 → 1.50.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 +113 -0
- package/agents/claude/CLAUDE.md +5 -4
- package/agents/codex/AGENTS.md +6 -5
- package/agents/copilot/copilot-instructions.md +3 -3
- package/agents/cursor/campaigns-os.mdc +3 -3
- package/compatibility.json +1 -1
- package/contracts/effects.v1.json +173 -0
- package/contracts/release-ledger.json +333 -0
- package/contracts/supported-surface.json +4 -4
- package/docs/brand-theme-bridge.md +12 -6
- package/docs/build-packet.md +8 -3
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/qa-and-test-orders.md +21 -7
- package/docs/runtime-readiness.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 +3 -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 +3 -3
- package/skills.json +10 -10
- package/src/brand-theme.mjs +13 -2
- package/src/cli.mjs +11 -9
- package/src/install-mode.mjs +0 -8
- package/src/invocation.mjs +3 -1
- package/src/qa-binding-evidence.mjs +21 -0
- package/src/qa-browser.mjs +30 -1
- package/src/qa-node.mjs +10 -2
- package/src/stage-record.mjs +303 -22
- package/src/theme-gate.mjs +3 -3
|
@@ -146,7 +146,11 @@
|
|
|
146
146
|
"const": "campaigns-os-page-binding/v0"
|
|
147
147
|
},
|
|
148
148
|
"observation": {
|
|
149
|
-
"
|
|
149
|
+
"enum": [
|
|
150
|
+
"static_declaration",
|
|
151
|
+
"sdk_request"
|
|
152
|
+
],
|
|
153
|
+
"description": "static_declaration: the key the page's HTML and same-origin config scripts declare. sdk_request: the key the Campaign Cart SDK sent to the Campaigns API in the browser pass (qa run --browser)."
|
|
150
154
|
},
|
|
151
155
|
"outcome": {
|
|
152
156
|
"enum": [
|
|
@@ -174,7 +178,8 @@
|
|
|
174
178
|
"enum": [
|
|
175
179
|
"meta",
|
|
176
180
|
"inline",
|
|
177
|
-
"config_script"
|
|
181
|
+
"config_script",
|
|
182
|
+
"sdk_request"
|
|
178
183
|
]
|
|
179
184
|
}
|
|
180
185
|
},
|
|
@@ -182,7 +187,7 @@
|
|
|
182
187
|
"const": "not_verified"
|
|
183
188
|
}
|
|
184
189
|
},
|
|
185
|
-
"description": "Credential-free static declaration
|
|
190
|
+
"description": "Credential-free comparison of the page's key with the expected key: the static declaration, or the key the SDK sent in the browser pass. Not Campaign App identity. No keys, hashes, fragments, source URLs or inferred resource IDs."
|
|
186
191
|
},
|
|
187
192
|
"pageUrlRef": {
|
|
188
193
|
"type": "object",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: campaign-lifecycle-orientation
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.24
|
|
4
4
|
description: Orient a reader to the Campaigns OS lifecycle artifacts a run has already emitted, without advancing any stage or changing any state.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.50.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.50.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: campaign-readback-classification
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.24
|
|
4
4
|
description: Classify a selected campaign from the readback projection's v2 fields and write a read-only handoff without turning diagnosis into permission.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.50.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.50.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: campaign-run-evidence
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.24
|
|
4
4
|
description: Interpret existing Campaigns OS doctor, QA and proof-depth evidence without claiming more proof than the artifacts contain.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.50.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.50.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: contribution-intake
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.24
|
|
4
4
|
description: Turn a suggestion about the agent surface into a classified, evidence-checked proposal and, only with attended approval, one issue on this repository's tracker.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.50.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.50.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-build
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.29
|
|
4
4
|
description: Assemble a NEXT campaign from a doctor-cleared Build Packet, CampaignSpec/API values, prepared HTML/assets, page-kit, and starter-template contracts.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.50.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.50.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -107,6 +107,6 @@ Build rules:
|
|
|
107
107
|
- Run page-kit build and SDK/template lint available in the target repo.
|
|
108
108
|
- Record build with `campaigns-os record build --packet <packet>` after every page-kit build (tier `C`: it overwrites `stages.assembly` and, when the output changed, resets `stages.polish` to `required` in the assembly report, and stamps the doctor output stale; `--dry-run` is tier `none`). It stamps `stages.assembly.build_fingerprint` with the fingerprint doctor computes from `_site/<slug>/` and the Design Source Package material fingerprint when the report has one. Never type or copy these fields by hand.
|
|
109
109
|
- Capture the machine-readable build summary as an artifact: `npx campaign-build --json > .campaign-runtime/page-kit-build-summary.json` (requires `next-campaign-page-kit` >= 0.1.4). Doctor's `built_output.build_summary` check verifies per-page build status and Page Kit shape warnings (`NESTED_NO_PERMALINK`, `DUPLICATE_OUTPUT`, `MISSING_FRONTMATTER`, `LAYOUT_NOT_FOUND`, `NO_CAMPAIGN`) from this artifact. If the installed page-kit predates `--json`, record that in the assembly report instead of skipping silently.
|
|
110
|
-
- Update the assembly report with commands, evidence, warnings, blockers, and next owner. If a brand theme was applied, record `report.theme
|
|
110
|
+
- Update the assembly report with commands, evidence, warnings, blockers, and next owner. If a brand theme was applied, run `campaigns-os record theme --packet <p>` (tier `C`) after `record build`: it reads each built commerce page's stylesheet links and records `report.theme` (status applied, `load_order=after-next-core`, `css_path`, `commerce_pages`, evidence). It refuses, writing nothing, when a page that loads `next-core.css` does not load the brand layer after it. Don't hand-edit `report.theme`; a first repair-loop defect goes through `record polish`.
|
|
111
111
|
|
|
112
112
|
Build does not replace polish or QA. Hand off to `next-campaigns-polish` when the campaign is runnable.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-os
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.44
|
|
4
4
|
description: Coordinate Campaigns OS lifecycle workflows from CampaignSpec, Build Packet, starter-template contracts, stage reports, deploy evidence, and QA proof depth.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.50.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.50.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-os-setup
|
|
3
|
-
version: 2.0.
|
|
3
|
+
version: 2.0.27
|
|
4
4
|
description: Bootstrap or prepare a target page-kit campaign repo from a doctor-cleared Campaigns OS Build Packet before full build wiring. Formerly installed as next-campaigns-setup; renamed 2026-08 to stop colliding with the published NextCommerceCo/skills scaffolder of that name.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.50.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.50.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-polish
|
|
3
|
-
version: 1.1.
|
|
3
|
+
version: 1.1.28
|
|
4
4
|
description: Run the visual/runtime polish pass after build and before QA for a Campaigns OS campaign.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.50.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.50.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -48,7 +48,8 @@ Use this after build has produced a runnable page-kit campaign.
|
|
|
48
48
|
Theme gate: `campaigns-os next polish` (tier `A`, like every `next` form: additive writes under `.campaign-runtime/` plus a stage-progress POST under Run Telemetry consent; `--no-write` and `--no-remit` are each tier `B`) blocks when theme inspect found a
|
|
49
49
|
generatable brand theme that is not yet applied to commerce pages. Do not work
|
|
50
50
|
around the gate — apply the brand layer (`theme generate`, tier `B`; copy into
|
|
51
|
-
campaign assets, load after `next-core.css`,
|
|
51
|
+
campaign assets, load after `next-core.css`, rebuild, `campaigns-os record build`,
|
|
52
|
+
then `campaigns-os record theme --packet <p>`, tier `C`) or record an
|
|
52
53
|
explicit waiver (`campaigns-os theme waive --packet <p> --reason "<why>" --waived-by "<named human>"` — tier `C`, because the waiver overwrites the assembly report and doctor output; placeholders are refused).
|
|
53
54
|
|
|
54
55
|
Responsibilities:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: next-campaigns-qa
|
|
3
|
-
version: 1.3.
|
|
3
|
+
version: 1.3.28
|
|
4
4
|
description: Run spec-aware QA from a saved Map or local-spec Build Packet and tested campaign URL after build, polish, and deploy/local evidence exist, including Playwright typed-card test-order proof.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.50.0+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.50.0+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
package/skills.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "campaigns-os-skills",
|
|
3
3
|
"description": "Skills bundled with Campaigns OS. `skills.sh` installs these into the shared agent skill directories (~/.claude/skills, ~/.codex/skills), so each one is a versioned package: bump the version whenever a package changes. `bundle_revision` identifies the bundle as a whole — `<package version>+skills.<n>`, where the prefix is this package's version and `<n>` counts the skill-text revisions published against it. It advances whenever ANY bundled skill changes, every SKILL.md states it on its first body line, and `campaigns-os tooling status --skills-revision <value>` compares the value an agent read from a skill against the bundle on disk. See docs/skills-revision.md.",
|
|
4
|
-
"bundle_revision": "1.
|
|
4
|
+
"bundle_revision": "1.50.0+skills.1",
|
|
5
5
|
"homepage": "https://github.com/NextCommerceCo/campaigns-os",
|
|
6
6
|
"retired_skills": [
|
|
7
7
|
{
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"id": "next-campaigns-os",
|
|
18
18
|
"name": "Campaigns OS Lifecycle",
|
|
19
|
-
"version": "1.0.
|
|
19
|
+
"version": "1.0.44",
|
|
20
20
|
"path": "skills/next-campaigns-os/SKILL.md",
|
|
21
21
|
"domain": "campaigns",
|
|
22
22
|
"description": "Coordinate Campaigns OS lifecycle workflows from CampaignSpec, Build Packet, starter-template contracts, stage reports, deploy evidence, and QA proof depth."
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
{
|
|
25
25
|
"id": "next-campaigns-os-setup",
|
|
26
26
|
"name": "Campaigns OS Setup",
|
|
27
|
-
"version": "2.0.
|
|
27
|
+
"version": "2.0.27",
|
|
28
28
|
"path": "skills/next-campaigns-os-setup/SKILL.md",
|
|
29
29
|
"domain": "campaigns",
|
|
30
30
|
"description": "Bootstrap or prepare a target page-kit campaign repo from a doctor-cleared Campaigns OS Build Packet before full build wiring. Formerly next-campaigns-setup; renamed to release that name to the published NextCommerceCo/skills scaffolder."
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
{
|
|
33
33
|
"id": "next-campaigns-build",
|
|
34
34
|
"name": "Campaign Build",
|
|
35
|
-
"version": "1.0.
|
|
35
|
+
"version": "1.0.29",
|
|
36
36
|
"path": "skills/next-campaigns-build/SKILL.md",
|
|
37
37
|
"domain": "campaigns",
|
|
38
38
|
"description": "Assemble a NEXT campaign from a doctor-cleared Build Packet, CampaignSpec/API values, prepared HTML/assets, page-kit, and starter-template contracts."
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
{
|
|
41
41
|
"id": "next-campaigns-polish",
|
|
42
42
|
"name": "Campaign Polish",
|
|
43
|
-
"version": "1.1.
|
|
43
|
+
"version": "1.1.28",
|
|
44
44
|
"path": "skills/next-campaigns-polish/SKILL.md",
|
|
45
45
|
"domain": "campaigns",
|
|
46
46
|
"description": "Run the visual/runtime polish pass after build and before QA for a Campaigns OS campaign."
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
{
|
|
49
49
|
"id": "next-campaigns-qa",
|
|
50
50
|
"name": "Campaign QA",
|
|
51
|
-
"version": "1.3.
|
|
51
|
+
"version": "1.3.28",
|
|
52
52
|
"path": "skills/next-campaigns-qa/SKILL.md",
|
|
53
53
|
"domain": "campaigns",
|
|
54
54
|
"description": "Run spec-aware QA from a saved Map or local-spec Build Packet and tested campaign URL after build, polish, and deploy/local evidence exist, including Playwright typed-card test-order proof."
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
{
|
|
57
57
|
"id": "campaign-lifecycle-orientation",
|
|
58
58
|
"name": "Campaign Lifecycle Orientation",
|
|
59
|
-
"version": "1.0.
|
|
59
|
+
"version": "1.0.24",
|
|
60
60
|
"path": "skills/campaign-lifecycle-orientation/SKILL.md",
|
|
61
61
|
"domain": "campaigns",
|
|
62
62
|
"description": "Orient a reader to the Campaigns OS lifecycle artifacts a run has already emitted, without advancing their state: the shared vocabulary, doctor as a gate, the stage record, and the store-theme/Page Kit two-worlds trap."
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
{
|
|
65
65
|
"id": "campaign-run-evidence",
|
|
66
66
|
"name": "Campaign Run Evidence",
|
|
67
|
-
"version": "1.0.
|
|
67
|
+
"version": "1.0.24",
|
|
68
68
|
"path": "skills/campaign-run-evidence/SKILL.md",
|
|
69
69
|
"domain": "campaigns",
|
|
70
70
|
"description": "Interpret existing doctor, QA verdict and proof-depth evidence without claiming more proof than the artifacts contain, and keep funnel proof apart from merchant launch readiness."
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
{
|
|
73
73
|
"id": "campaign-readback-classification",
|
|
74
74
|
"name": "Campaign Readback Classification",
|
|
75
|
-
"version": "1.0.
|
|
75
|
+
"version": "1.0.24",
|
|
76
76
|
"path": "skills/campaign-readback-classification/SKILL.md",
|
|
77
77
|
"domain": "campaigns",
|
|
78
78
|
"description": "Classify a selected campaign from the campaigns-os-readback/v2 fields (artifacts, staleness.stale_keys, clean, doctor, divergences, skip_cascades) and write a read-only handoff without turning diagnosis into permission."
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
{
|
|
81
81
|
"id": "contribution-intake",
|
|
82
82
|
"name": "Contribution Intake",
|
|
83
|
-
"version": "1.0.
|
|
83
|
+
"version": "1.0.24",
|
|
84
84
|
"path": "skills/contribution-intake/SKILL.md",
|
|
85
85
|
"domain": "campaigns",
|
|
86
86
|
"description": "Turn a suggestion about the agent surface into a classified, evidence-checked and redacted proposal, filed on this repository tracker only with attended approval."
|
package/src/brand-theme.mjs
CHANGED
|
@@ -25,7 +25,7 @@ export const THEME_REPORT_STATUSES = new Set(["applied", "skipped", "blocked", "
|
|
|
25
25
|
export const THEME_CONFIDENCES = new Set(["high", "medium", "low", "none"]);
|
|
26
26
|
|
|
27
27
|
const SKIP_DIRS = new Set([".git", "node_modules", "_site", "dist", "build", ".next", "coverage", "qa-output"]);
|
|
28
|
-
const BRAND_LAYER_FILENAMES = new Set(["brand-theme.css", "checkout-brand.css"]);
|
|
28
|
+
export const BRAND_LAYER_FILENAMES = new Set(["brand-theme.css", "checkout-brand.css"]);
|
|
29
29
|
|
|
30
30
|
function isObject(value) {
|
|
31
31
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -574,6 +574,17 @@ function discoverThemeCandidates({ sourceRoot, pageMappings = [], manifest = nul
|
|
|
574
574
|
candidates.push(candidateFromFile(path, role, source, referencedBy));
|
|
575
575
|
}
|
|
576
576
|
|
|
577
|
+
// A stylesheet that pages of different roles link is the funnel's shared
|
|
578
|
+
// layer, not the first linking page's: it ranks as shared.
|
|
579
|
+
function addPageReference(path, role, referencedBy) {
|
|
580
|
+
const existing = seenFiles.has(resolve(path))
|
|
581
|
+
? candidates.find((candidate) => candidate.source === "mapped_html_reference" && resolve(candidate.path) === resolve(path))
|
|
582
|
+
: null;
|
|
583
|
+
if (!existing) return addFile(path, role, "mapped_html_reference", referencedBy);
|
|
584
|
+
existing.referenced_by.push(...referencedBy);
|
|
585
|
+
if (existing.role !== role) existing.role = "shared";
|
|
586
|
+
}
|
|
587
|
+
|
|
577
588
|
for (const conventional of [
|
|
578
589
|
"assets/css/tokens.css",
|
|
579
590
|
"assets/css/landing/tokens.css",
|
|
@@ -595,7 +606,7 @@ function discoverThemeCandidates({ sourceRoot, pageMappings = [], manifest = nul
|
|
|
595
606
|
const content = readFileSync(htmlPath, "utf8");
|
|
596
607
|
for (const ref of extractCssRefs(content)) {
|
|
597
608
|
for (const cssPath of candidateCssPathsForRef(sourceRoot, htmlPath, ref)) {
|
|
598
|
-
|
|
609
|
+
addPageReference(cssPath, candidateRoleFromPath(ref, role), [{ page_id: mapping.page_id || null, path: mapping.path, ref }]);
|
|
599
610
|
}
|
|
600
611
|
}
|
|
601
612
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -324,6 +324,8 @@ Usage:
|
|
|
324
324
|
campaigns-os record setup --packet <campaign-runtime.build.json> [--context <json>] [--report <json>] [--dry-run] [--json] # record setup complete once the campaign output directory exists: Build Context scaffold.required=false and stages.setup completed, validated against their schemas before either is written
|
|
325
325
|
campaigns-os record build --packet <campaign-runtime.build.json> [--context <json>] [--report <json>] [--dry-run] [--json] # after page-kit build: stages.assembly completed with build_fingerprint = doctor's derived.build_output_fingerprint.value (and the Design Source Package material fingerprint when the report has one); stages.polish becomes required unless its evidence is bound to this exact output
|
|
326
326
|
campaigns-os record polish --packet <campaign-runtime.build.json> --evidence <polish-evidence.json> [--context <json>] [--report <json>] [--dry-run] [--json] # after polish capture: stages.polish from the file's status (completed, completed_with_warnings, blocked with blockers, or skipped with skip_reason), evidence and optional repair_loop_defect, bound to doctor's current fingerprint; a completed status is refused, writing nothing, unless the polish gate doctor evaluates would pass. --dry-run runs every check and writes nothing
|
|
327
|
+
campaigns-os record theme --packet <campaign-runtime.build.json> [--context <json>] [--report <json>] [--dry-run] [--json] # after the brand layer is linked and build is recorded: report.theme becomes applied with load_order after-next-core, css_path, commerce_pages and per-page evidence, only when each built commerce page that loads next-core.css loads brand-theme.css (or checkout-brand.css) after it and at least one does; a page loading neither is left out as the design's own markup; refused, writing nothing, otherwise. --dry-run runs every check and writes nothing
|
|
328
|
+
campaigns-os record deploy --packet <campaign-runtime.build.json> --base-url <served url> [--context <json>] [--report <json>] [--dry-run] [--json] # a local preview (deploy.target local-serve) after polish is recorded: GETs every built page under the loopback URL (the campaign route root), then records deploy.preview_url on the packet and stages.deploy completed with the URL in outputs; refused, writing nothing, when the URL is not loopback or not the route root, a page does not answer 2xx, the build changed since it was recorded, or the theme gate is blocked. --dry-run runs every check, the requests included, and writes nothing
|
|
327
329
|
campaigns-os readback <target-repo-root> [--json] [--packet <path>] [--doctor <path>] [--context <path>] [--report <path>] [--qa-verdict <path>] [--findings <path>] # read-only projection of one run's emitted artifacts (packet, doctor output, build context, assembly report, QA verdict, findings export): artifact states, per-artifact freshness against the checkout's HEAD reflog, doctor warning grouping, skip cascades and cross-artifact divergences. Writes nothing, starts no process, touches no network, and records no lifecycle entry; --json emits one campaigns-os-readback/v2 object (docs/readback.md). Exit 2 for a missing target root or a Build Packet set freshness cannot single out.
|
|
328
330
|
campaigns-os readback --example [--json] # project the bundled synthetic sample; freshness is not computable for it by design
|
|
329
331
|
campaigns-os validate-assembly-report --report <json> [--json]
|
|
@@ -1069,8 +1071,8 @@ async function dispatch(command, args, { recorder = NOOP_RECORDER, ambient = nul
|
|
|
1069
1071
|
}
|
|
1070
1072
|
|
|
1071
1073
|
if (command === "record") {
|
|
1072
|
-
const {
|
|
1073
|
-
writeResult(
|
|
1074
|
+
const { recordCommand } = await import("./stage-record.mjs");
|
|
1075
|
+
writeResult(await recordCommand(args), args, 0);
|
|
1074
1076
|
return;
|
|
1075
1077
|
}
|
|
1076
1078
|
|
|
@@ -4466,8 +4468,9 @@ function themeStarterPaletteAdvisory(themeGate, packetPath, residueState) {
|
|
|
4466
4468
|
+ `\`${cmd("theme")} waive --packet ${packetArg} --reason "<why the starter palette is acceptable>"\` `
|
|
4467
4469
|
+ "— which downgrades those rows to warn severity and keeps the shipped palette visible in the "
|
|
4468
4470
|
+ "verdict; or hand-author the brand layer (write brand-theme.css, list it after next-core.css in "
|
|
4469
|
-
+ "commerce-page frontmatter styles, rebuild, then
|
|
4470
|
-
+ "
|
|
4471
|
+
+ "commerce-page frontmatter styles, rebuild, then run "
|
|
4472
|
+
+ `\`${cmd("record")} build --packet ${packetArg}\` and \`${cmd("record")} theme --packet ${packetArg}\`), `
|
|
4473
|
+
+ "per docs/brand-theme-bridge.md. This notice waives nothing on its own.",
|
|
4471
4474
|
};
|
|
4472
4475
|
}
|
|
4473
4476
|
|
|
@@ -4650,7 +4653,7 @@ export function buildNextActions({ result, packetPath, packet, themeGate, polish
|
|
|
4650
4653
|
} else if (result.stage === "deploy") {
|
|
4651
4654
|
if (packet.deploy?.target === LOCAL_SERVE_DEPLOY_TARGET) {
|
|
4652
4655
|
const plan = localServePlan(packet);
|
|
4653
|
-
push("deploy", "manual", null, `Serve the built ${plan.dir} output locally as the origin root (deploy.target is local-serve)${plan.rewrite ? ` — ${plan.rewrite}` : ""}, then record
|
|
4656
|
+
push("deploy", "manual", null, `Serve the built ${plan.dir} output locally as the origin root (deploy.target is local-serve)${plan.rewrite ? ` — ${plan.rewrite}` : ""}, then run ${cmd("record")} deploy --packet ${packetPath} --base-url <served url>: it checks every built page answers and records deploy.preview_url and stages.deploy. Localhost on any port is a Development domain: SDK allowed, analytics suppressed.`);
|
|
4654
4657
|
} else {
|
|
4655
4658
|
push("deploy", "manual", null, `Deploy _site/ output to ${packet.deploy?.target || "the deploy target"}, then record deploy.preview_url (or production_url) on the packet and stages.deploy in the assembly report.`);
|
|
4656
4659
|
}
|
|
@@ -4814,7 +4817,7 @@ Rules:
|
|
|
4814
4817
|
- Replace demo refs; do not copy Olympus-style shipping_methods into shop-three-step.
|
|
4815
4818
|
- For two-step package-selection flows, treat the selector page as the pre-checkout step and pass the selected cart to checkout with forcePackageId; preserve normal tracking params and strip forcePackageId from visible checkout URLs after SDK initialization.
|
|
4816
4819
|
- After page-kit build, inspect rendered _site output before handoff: each active page should have a body, Campaign Cart runtime markers, SDK meta tags from CampaignSpec sdk_hints.meta_tags, and no stale copied funnel attribution.
|
|
4817
|
-
- Run page-kit build and SDK/template lint, then record build before polish: \`${cmd("record")} build --packet ${packetPath}\`. It stamps stages.assembly.build_fingerprint with the fingerprint doctor computes from the built output (derived.build_output_fingerprint.value, sha256 over the sorted path+sha256 manifest of _site/<slug>/; doctor reports built_output.fingerprint_stale whenever the output on disk stops matching the recorded value), records report.design_source_package.material_fingerprint on stages.assembly.source_package_material_fingerprint when present, and sets stages.polish to "required" (required_by="build", required_for=["qa"]). Re-run it after every rebuild; never hand-edit these fields. Build must not mark stages.polish as completed/completed_with_warnings/skipped. If you applied a brand theme, record report.theme
|
|
4820
|
+
- Run page-kit build and SDK/template lint, then record build before polish: \`${cmd("record")} build --packet ${packetPath}\`. It stamps stages.assembly.build_fingerprint with the fingerprint doctor computes from the built output (derived.build_output_fingerprint.value, sha256 over the sorted path+sha256 manifest of _site/<slug>/; doctor reports built_output.fingerprint_stale whenever the output on disk stops matching the recorded value), records report.design_source_package.material_fingerprint on stages.assembly.source_package_material_fingerprint when present, and sets stages.polish to "required" (required_by="build", required_for=["qa"]). Re-run it after every rebuild; never hand-edit these fields. Build must not mark stages.polish as completed/completed_with_warnings/skipped. If you applied a brand theme, run \`${cmd("record")} theme --packet ${packetPath}\` after record build: it reads each built commerce page's stylesheet links and records report.theme (status applied, load_order=after-next-core, css_path, commerce_pages, evidence), and refuses, writing nothing, when a page that loads next-core.css does not load the brand layer after it. Never hand-edit report.theme.
|
|
4818
4821
|
- Capture the machine-readable build summary as an artifact: \`${PAGE_KIT_BUILD_SUMMARY_CAPTURE_COMMAND}\` (requires next-campaign-page-kit >= 0.1.4). Doctor verifies it for per-page build errors and Page Kit shape warnings (NESTED_NO_PERMALINK, DUPLICATE_OUTPUT, MISSING_FRONTMATTER, LAYOUT_NOT_FOUND). If the installed page-kit predates --json, record that in the assembly report instead of skipping silently.${localProofPromptLines(packet)}`;
|
|
4819
4822
|
}
|
|
4820
4823
|
|
|
@@ -4925,9 +4928,8 @@ Read first:
|
|
|
4925
4928
|
Nothing ships anywhere: the page-kit build produces _site/ output and you serve ${serveDir} on localhost (any static server, any port) for QA. Localhost on any port is a Campaigns App Development domain, so the SDK initialises there without an origin allowlist entry and Campaigns analytics events are suppressed.
|
|
4926
4929
|
|
|
4927
4930
|
Once the server is up:
|
|
4928
|
-
1.
|
|
4929
|
-
2.
|
|
4930
|
-
3. Run \`${cmd("next")} --packet ${packetPath}\` to advance to QA.
|
|
4931
|
+
1. Run \`${cmd("record")} deploy --packet ${packetPath} --base-url <localhost origin>${liveUrlPath}\`. It requests every built page under that URL, then records the URL on the packet at deploy.preview_url and stages.deploy as completed with the URL in outputs; it refuses, writing nothing, if a page does not answer.
|
|
4932
|
+
2. Run \`${cmd("next")} --packet ${packetPath}\` to advance to QA.
|
|
4931
4933
|
|
|
4932
4934
|
If the served build cannot be reached, set stages.deploy.status to "blocked" with a clear reason in outputs so the orchestration loop surfaces it rather than skipping past.`;
|
|
4933
4935
|
}
|
package/src/install-mode.mjs
CHANGED
|
@@ -282,11 +282,3 @@ export function invocationPrefixFor(root, pkg = null) {
|
|
|
282
282
|
prefixCache.set(root, prefix);
|
|
283
283
|
return prefix;
|
|
284
284
|
}
|
|
285
|
-
|
|
286
|
-
// Rewrites every command spelled with the canonical bare `campaigns-os <verb>`
|
|
287
|
-
// into the given prefix. Internal bookkeeping (deviation tracking, gate
|
|
288
|
-
// registries, tests) keeps the canonical spelling; only what is printed or
|
|
289
|
-
// emitted for an operator or agent to copy is rewritten. Skill names such as
|
|
290
|
-
// next-campaigns-os-setup, file names (campaigns-os.mjs), and already-prefixed
|
|
291
|
-
// forms (`npx --no-install campaigns-os`, `npm run campaigns-os --`) are left
|
|
292
|
-
// alone.
|
package/src/invocation.mjs
CHANGED
|
@@ -65,7 +65,7 @@ const COMMANDS = frozen({
|
|
|
65
65
|
theme: { subcommands: ["generate", "inspect", "waive"] },
|
|
66
66
|
checkpoint: { subcommands: ["waive"] },
|
|
67
67
|
polish: { subcommands: ["capture"] },
|
|
68
|
-
record: { subcommands: ["build", "polish", "setup"] },
|
|
68
|
+
record: { subcommands: ["build", "deploy", "polish", "setup", "theme"] },
|
|
69
69
|
"validate-assembly-report": {},
|
|
70
70
|
"install-agent-context": { dryRun: true },
|
|
71
71
|
"install-skills": { dryRun: true },
|
|
@@ -92,8 +92,10 @@ const SUBCOMMAND_OVERRIDES = frozen({
|
|
|
92
92
|
"spec derive": { dryRun: true },
|
|
93
93
|
"qa publish": { dryRun: true },
|
|
94
94
|
"record build": { dryRun: true },
|
|
95
|
+
"record deploy": { dryRun: true },
|
|
95
96
|
"record polish": { dryRun: true },
|
|
96
97
|
"record setup": { dryRun: true },
|
|
98
|
+
"record theme": { dryRun: true },
|
|
97
99
|
"qa run": { autoEnd: true },
|
|
98
100
|
"run start": { sweepRoot: "session" },
|
|
99
101
|
"run end": { sweepRoot: "session", dryRun: true },
|
|
@@ -188,6 +188,27 @@ export async function observeBinding({ source, page, expected, scriptLoader, par
|
|
|
188
188
|
return result(values[0] === expected.value ? 'match' : 'mismatch', 'credential_comparison');
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
// The Campaign Cart SDK sends the page's raw key, with no scheme, as
|
|
192
|
+
// `Authorization` on every Campaigns API call, so the browser pass can see the
|
|
193
|
+
// key a page actually uses, whatever its scripts look like. The value is
|
|
194
|
+
// compared as sent: if the SDK ever adds a scheme, every page reads mismatch
|
|
195
|
+
// rather than passing. The API host is matched by its shape,
|
|
196
|
+
// campaigns.apps.<name>.com, without naming it.
|
|
197
|
+
const SDK_API_HOST = /^campaigns\.apps\.[a-z0-9-]+\.com$/;
|
|
198
|
+
export function isSdkApiRequest(url) {
|
|
199
|
+
try { const parsed = new URL(url); return parsed.protocol === 'https:' && SDK_API_HOST.test(parsed.hostname) && parsed.pathname.startsWith('/api/'); } catch { return false; }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// The keys a page sent are compared here and dropped; only the outcome is
|
|
203
|
+
// kept. With nothing sent, or no single expected key, the static read stands
|
|
204
|
+
// (null).
|
|
205
|
+
export function sentBinding(sentKeys, expected) {
|
|
206
|
+
if (!sentKeys.length || expected?.conflict || !expected?.value) return null;
|
|
207
|
+
return { schema_version: BINDING_SCHEMA, observation: 'sdk_request',
|
|
208
|
+
outcome: sentKeys.every(value => value === expected.value) ? 'match' : 'mismatch',
|
|
209
|
+
reason: 'credential_comparison', source_kinds: ['sdk_request'], identity: 'not_verified' };
|
|
210
|
+
}
|
|
211
|
+
|
|
191
212
|
export function bindingAssertion(page, evidence) {
|
|
192
213
|
// No source URLs, values, hashes, masked fragments, or inferred App IDs.
|
|
193
214
|
return { id: `page-binding:${page.page_id}`, family: 'api-metadata', page: page.page_id,
|
package/src/qa-browser.mjs
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
import { ORDER_BUMP_PROBE_INPUT, orderBumpEvidenceScript } from "./qa-order-bump.mjs";
|
|
46
46
|
import { assessPurchaseDataLayer, expectedOrderReferences, purchaseDataLayerAssertion, purchaseDataLayerProbe } from "./qa-purchase-data-layer.mjs";
|
|
47
47
|
import { isBumpRow } from "./commercial-journey.mjs";
|
|
48
|
+
import { bindingAssertion, isSdkApiRequest, sentBinding } from "./qa-binding-evidence.mjs";
|
|
48
49
|
import {
|
|
49
50
|
RESIDUE_PAGE_TYPES,
|
|
50
51
|
demoAssetConfig,
|
|
@@ -962,6 +963,14 @@ async function runPageBrowserChecks(context, page, args, options = {}) {
|
|
|
962
963
|
failure: request.failure()?.errorText || "request failed",
|
|
963
964
|
});
|
|
964
965
|
});
|
|
966
|
+
// The key each SDK request carried. It stays in this function: sentBinding
|
|
967
|
+
// keeps only whether it matched.
|
|
968
|
+
const sentKeys = [];
|
|
969
|
+
const keyReads = [];
|
|
970
|
+
browserPage.on("request", (request) => {
|
|
971
|
+
if (!isSdkApiRequest(request.url())) return;
|
|
972
|
+
keyReads.push(request.headerValue("authorization").then((value) => { if (value !== null) sentKeys.push(value); }, () => {}));
|
|
973
|
+
});
|
|
965
974
|
|
|
966
975
|
// Measurements describe the existing sequence; readiness never shortens it.
|
|
967
976
|
const observationStarted = performance.now();
|
|
@@ -1017,6 +1026,9 @@ async function runPageBrowserChecks(context, page, args, options = {}) {
|
|
|
1017
1026
|
assertions.push(runtimeIssueAssertion(page, "browser-console-errors", actionableConsoleErrors));
|
|
1018
1027
|
}
|
|
1019
1028
|
observation.failed_requests_sampled_after_ms = elapsedMilliseconds(observationStarted);
|
|
1029
|
+
await Promise.all(keyReads);
|
|
1030
|
+
const sent = sentBinding(sentKeys, options.bindingExpected);
|
|
1031
|
+
if (sent) assertions.push(bindingAssertion(page, sent));
|
|
1020
1032
|
if (failedRequests.length) {
|
|
1021
1033
|
assertions.push(assertion({
|
|
1022
1034
|
id: `browser-request-failures:${page.page_id}`,
|
|
@@ -5485,6 +5497,22 @@ function reconcileOrderAgainstDisplay({ lines = [], display = null, events = nul
|
|
|
5485
5497
|
const displayed = new Set(resolved.displayed_package_ids);
|
|
5486
5498
|
const summaryIds = new Set(resolved.summary_package_ids);
|
|
5487
5499
|
const matchedSummaryIds = new Set();
|
|
5500
|
+
// A checkout order bump persists with is_upsell: true (the platform's
|
|
5501
|
+
// reporting tag), yet the checkout summary displays it. Such a line charges
|
|
5502
|
+
// a displayed row; an is_upsell line the summary does not show is a
|
|
5503
|
+
// post-purchase upsell, out of scope here, never a stray charge.
|
|
5504
|
+
let bumpLineCount = 0;
|
|
5505
|
+
for (const line of (lines || []).filter((entry) => entry?.is_upsell)) {
|
|
5506
|
+
const resolution = events ? campaignPackageResolutionForLine(events, line, {
|
|
5507
|
+
selected_packages,
|
|
5508
|
+
preferred_refs: resolved.summary_package_ids,
|
|
5509
|
+
}) : null;
|
|
5510
|
+
const ref = resolution?.pkg?.ref_id == null ? null : String(resolution.pkg.ref_id);
|
|
5511
|
+
if (ref && summaryIds.has(ref)) {
|
|
5512
|
+
matchedSummaryIds.add(ref);
|
|
5513
|
+
bumpLineCount += 1;
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5488
5516
|
const extra = [];
|
|
5489
5517
|
const unresolved = [];
|
|
5490
5518
|
const matchedQuantities = [];
|
|
@@ -5532,6 +5560,7 @@ function reconcileOrderAgainstDisplay({ lines = [], display = null, events = nul
|
|
|
5532
5560
|
displayed_package_ids: [...displayed],
|
|
5533
5561
|
summary_package_ids: [...summaryIds],
|
|
5534
5562
|
non_upsell_line_count: nonUpsellLines.length,
|
|
5563
|
+
...(bumpLineCount ? { order_bump_line_count: bumpLineCount } : {}),
|
|
5535
5564
|
extra,
|
|
5536
5565
|
missing,
|
|
5537
5566
|
matched_quantities: matchedQuantities,
|
|
@@ -5601,7 +5630,7 @@ function orderDisplayParityAssertion(page, planIdentifier, order) {
|
|
|
5601
5630
|
return assertion({
|
|
5602
5631
|
...base,
|
|
5603
5632
|
status: STATUS.PASS,
|
|
5604
|
-
actual: `${reconciliation.non_upsell_line_count} non-upsell line(s) reconciled against ${reconciliation.summary_package_ids.length} displayed package(s)`,
|
|
5633
|
+
actual: `${reconciliation.non_upsell_line_count} non-upsell line(s)${reconciliation.order_bump_line_count ? ` and ${reconciliation.order_bump_line_count} order bump line(s)` : ""} reconciled against ${reconciliation.summary_package_ids.length} displayed package(s)`,
|
|
5605
5634
|
evidence: reconciliation,
|
|
5606
5635
|
});
|
|
5607
5636
|
}
|
package/src/qa-node.mjs
CHANGED
|
@@ -2329,7 +2329,7 @@ async function runResolvedQa(args, resolved, { runSessionActive = false, liveCam
|
|
|
2329
2329
|
});
|
|
2330
2330
|
assertions.push(...liveCampaignRefAssertions({ pages: [...livePages.values()], spec: liveSpec, liveCampaign: liveRead }));
|
|
2331
2331
|
if (args.browser === true) {
|
|
2332
|
-
|
|
2332
|
+
const browserAssertions = await runBrowserChecks(resolved.topologies, args, {
|
|
2333
2333
|
brandContract: resolved.brandContract,
|
|
2334
2334
|
// With no generatable brand theme on the local preview, the starter
|
|
2335
2335
|
// template is the design: its residue is a warning (local-preview-policy.mjs).
|
|
@@ -2337,7 +2337,15 @@ async function runResolvedQa(args, resolved, { runSessionActive = false, liveCam
|
|
|
2337
2337
|
? SEVERITY.WARN
|
|
2338
2338
|
: residueSeverityForThemeGate(gate.status),
|
|
2339
2339
|
supportedPaymentMethods: supportedPaymentMethodsFromSpec(resolved.spec),
|
|
2340
|
-
|
|
2340
|
+
bindingExpected,
|
|
2341
|
+
});
|
|
2342
|
+
// A page-binding row from the browser is the key the SDK actually sent;
|
|
2343
|
+
// it takes the place of that page's static read.
|
|
2344
|
+
for (const observed of browserAssertions) {
|
|
2345
|
+
const at = observed.id.startsWith("page-binding:") ? assertions.findIndex((entry) => entry.id === observed.id) : -1;
|
|
2346
|
+
if (at >= 0) assertions[at] = observed;
|
|
2347
|
+
else assertions.push(observed);
|
|
2348
|
+
}
|
|
2341
2349
|
}
|
|
2342
2350
|
|
|
2343
2351
|
const testOrders = await runAnalyticsOrderSequence({ args, resolved, runId, assertions });
|