@nextcommerce/campaigns-os 1.60.0 → 1.62.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/AGENTS.md +2 -1
- package/CHANGELOG.md +244 -0
- package/README.md +86 -438
- package/agents/claude/CLAUDE.md +20 -0
- package/agents/codex/AGENTS.md +19 -0
- package/agents/copilot/copilot-instructions.md +19 -0
- package/agents/cursor/campaigns-os.mdc +21 -0
- package/campaign-spec/README.md +7 -0
- package/campaign-spec/dist/analytics-vendors.d.ts +40 -0
- package/campaign-spec/dist/analytics-vendors.js +44 -0
- package/campaign-spec/dist/checkout-flow.d.ts +18 -0
- package/campaign-spec/dist/checkout-flow.js +28 -0
- package/campaign-spec/dist/index.d.ts +4 -2
- package/campaign-spec/dist/index.js +5 -1
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +8 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.js +39 -0
- package/campaign-spec/dist/rules/campaign-currencies.d.ts +29 -0
- package/campaign-spec/dist/rules/campaign-currencies.js +130 -0
- package/campaign-spec/dist/rules/checkout-path-has-packages.d.ts +31 -0
- package/campaign-spec/dist/rules/checkout-path-has-packages.js +79 -0
- package/campaign-spec/dist/rules/index.d.ts +2 -0
- package/campaign-spec/dist/rules/index.js +6 -0
- package/campaign-spec/dist/types.d.ts +43 -9
- package/compatibility.json +1 -1
- package/contracts/brand-theme-target-tokens.next-core.v0.json +95 -25
- package/contracts/campaign-cart-sdk-support-policy.v0.json +2 -2
- package/contracts/commerce-surface-catalog.json +68 -61
- package/contracts/effects.v1.json +549 -165
- package/contracts/fixtures/runtime-recipe/accept/current.json +3 -0
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +3 -0
- package/contracts/fixtures/runtime-recipe/dist-states.json +1 -1
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +3 -0
- package/contracts/release-ledger.json +1767 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +3 -0
- package/contracts/supported-surface.json +10 -10
- package/contracts/template-brand-contract.shared-commerce.v0.json +23 -1
- package/docs/access-model.md +20 -0
- package/docs/brand-theme-bridge.md +62 -3
- package/docs/build-packet.md +456 -36
- package/docs/campaign-build-brief.md +16 -0
- package/docs/campaign-standardization-report.md +4 -4
- package/docs/campaigns-os-build-flow.md +112 -2
- package/docs/campaignspec-authoring-examples.md +141 -0
- package/docs/design-source-package.md +64 -8
- package/docs/effects.md +17 -9
- package/docs/entry-points.md +188 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/polish-evidence.md +42 -2
- package/docs/progress-snapshots.md +7 -4
- package/docs/qa-and-test-orders.md +197 -3
- package/docs/quickstart.md +438 -0
- package/docs/readback.md +154 -6
- package/docs/runtime-readiness.md +6 -3
- package/docs/sdk-storage-compatibility.md +4 -2
- package/docs/skills-revision.md +10 -10
- package/docs/source-adapters.md +213 -0
- package/package.json +10 -3
- package/schemas/campaign-design-source-package.v0.schema.json +8 -0
- package/schemas/campaign-runtime-build-context.v0.schema.json +9 -0
- package/schemas/campaign-runtime-build-packet.v0.schema.json +9 -0
- package/schemas/campaign-spec.v4.schema.json +19 -3
- package/schemas/campaign-spec.v5.schema.json +19 -3
- package/schemas/campaigns-os-readback.v2.schema.json +238 -1
- package/schemas/source-html-manifest.v0.schema.json +3 -0
- 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 -4
- package/skills/next-campaigns-os/SKILL.md +7 -4
- package/skills/next-campaigns-os-setup/SKILL.md +5 -3
- package/skills/next-campaigns-polish/SKILL.md +8 -10
- package/skills/next-campaigns-qa/SKILL.md +7 -4
- package/skills/next-campaigns-sdk-bump/SKILL.md +151 -25
- package/skills.json +11 -11
- package/src/brand-theme.mjs +415 -9
- package/src/brief-summary.mjs +6 -2
- package/src/browser-launch.mjs +2 -1
- package/src/build-brief.mjs +13 -3
- package/src/built-site-scope.mjs +2 -2
- package/src/campaign-workspace.mjs +288 -15
- package/src/change-scope.mjs +284 -0
- package/src/cli.mjs +358 -97
- package/src/commercial-journey.mjs +16 -3
- package/src/commercial-parity.mjs +15 -7
- package/src/content-residue.mjs +4 -2
- package/src/design-file-reference.mjs +93 -0
- package/src/design-source-package.mjs +91 -1
- package/src/design-source-publication.mjs +9 -6
- package/src/directory-lock.mjs +73 -19
- package/src/doctor/checks.mjs +87 -81
- package/src/doctor/ci.mjs +177 -0
- package/src/doctor/inspect.mjs +41 -6
- package/src/doctor/next-step.mjs +7 -0
- package/src/doctor/source-provenance.mjs +74 -0
- package/src/doctor-sidecar.mjs +3 -2
- package/src/evidence-debt.mjs +159 -0
- package/src/finding-cause.mjs +12 -11
- package/src/input-refresh.mjs +339 -32
- package/src/invocation.mjs +7 -1
- package/src/live-campaign-refs.mjs +7 -2
- package/src/local-preview-policy.mjs +35 -2
- package/src/page-kit-analytics-vendors.mjs +132 -0
- package/src/parity/compare.mjs +316 -0
- package/src/parity/design-access.mjs +54 -0
- package/src/parity/element-parity-record.mjs +569 -0
- package/src/parity/probe.mjs +205 -0
- package/src/parity/snapshot.mjs +110 -0
- package/src/png-header.mjs +87 -0
- package/src/polish-browser.mjs +189 -10
- package/src/polish-captures-record.mjs +52 -0
- package/src/polish-deadline.mjs +14 -0
- package/src/polish-gate.mjs +25 -6
- package/src/polish-node.mjs +81 -7
- package/src/polish-readability.mjs +221 -9
- package/src/polish-visual-fixtures.mjs +74 -0
- package/src/polish-visual.mjs +613 -0
- package/src/progress-node.mjs +3 -2
- package/src/qa-analytics-correctness.mjs +14 -2
- package/src/qa-analytics-parity.mjs +111 -6
- package/src/qa-browser.mjs +40 -4
- package/src/qa-commercial-parity.mjs +50 -3
- package/src/qa-node.mjs +235 -22
- package/src/qa-publish.mjs +9 -8
- package/src/qa-sidecar.mjs +16 -4
- package/src/qa-vendor-beacons.mjs +232 -0
- package/src/qa-vendor-endpoints.mjs +306 -0
- package/src/qc-accept.mjs +176 -6
- package/src/qc-check-registry.mjs +42 -2
- package/src/qc-results.mjs +477 -42
- package/src/readback.mjs +113 -21
- package/src/route-identity.mjs +10 -0
- package/src/run-record.mjs +18 -10
- package/src/runtime-state-ignore.mjs +6 -2
- package/src/sdk-attribute-index.mjs +11 -11
- package/src/sdk-markup.mjs +77 -2
- package/src/sdk-storage-compatibility.mjs +115 -6
- package/src/source-html-intake.mjs +15 -1
- package/src/source-html-manifest.mjs +7 -0
- package/src/source-html-server.mjs +129 -0
- package/src/spec-derive-campaign.mjs +216 -0
- package/src/spec-derive.mjs +473 -53
- package/src/spec-identity.mjs +14 -0
- package/src/stage-ledger.mjs +40 -1
- package/src/stage-record.mjs +100 -17
- package/src/target-lock.mjs +11 -1
- package/src/theme-gate.mjs +2 -2
- package/src/visual-diff.mjs +296 -0
- package/src/visual-reference.mjs +126 -0
|
@@ -4,6 +4,22 @@ The Campaign Build Brief is the merchandising and design-presentation truth for
|
|
|
4
4
|
|
|
5
5
|
CampaignSpec remains the operational truth: packages, offers, shipping, routing, SDK hints, payment/runtime values, and API identity. The Build Brief answers business-owned presentation questions that agents should not infer silently: page authority, palette and CTA treatment, product media rules, pricing display, promo language, payment/trust surfaces, canonical names, residue policy, and QA expectations.
|
|
6
6
|
|
|
7
|
+
## Design Authority
|
|
8
|
+
|
|
9
|
+
For a page built from an HTML export of a named design file, the drafted brief
|
|
10
|
+
records `design_authority.<page>.source: design_file`, with `reference` set to
|
|
11
|
+
the design file link, `frames` set to the declared frame links, and
|
|
12
|
+
`derived_artifact` naming the export (`provided_design_export` and its HTML
|
|
13
|
+
path). The summary reads "follow the design file, built from its HTML export".
|
|
14
|
+
A Figma spec `design_source` supplies this lineage only with
|
|
15
|
+
figma-sections-export provenance that passes doctor's
|
|
16
|
+
`source_html.producer_provenance` check without a waiver; a manifest
|
|
17
|
+
`design_file` is its own declaration.
|
|
18
|
+
A value you state in the brief file is kept as you wrote it. This records
|
|
19
|
+
authority; it does not read the design file or compare elements. Doctor's
|
|
20
|
+
`design.access` reports that access as unexercised, with guidance to continue or
|
|
21
|
+
compare the export and file by hand.
|
|
22
|
+
|
|
7
23
|
## Artifact Locations
|
|
8
24
|
|
|
9
25
|
Campaigns OS accepts YAML or JSON:
|
|
@@ -81,10 +81,10 @@ field before attributing the file.
|
|
|
81
81
|
`--template-family`), `--slug`, `--sdk-support-policy`, `--field-contract`,
|
|
82
82
|
`--no-doctor`, `--json`, and the two flags every command accepts,
|
|
83
83
|
`--run-id` and `--lifecycle-journal`. Any other flag is refused before the
|
|
84
|
-
scan starts, with the known list in the message
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
scan starts, with the known list in the message. Values follow the flag as
|
|
85
|
+
the next argument: like every Campaigns OS command, standardize refuses the
|
|
86
|
+
`--flag=value` spelling before the scan starts (`--no-doctor=maybe` used to
|
|
87
|
+
run the doctor anyway).
|
|
88
88
|
|
|
89
89
|
### Exit codes
|
|
90
90
|
|
|
@@ -17,6 +17,116 @@ The happy path is intentionally tight:
|
|
|
17
17
|
|
|
18
18
|
Pause only for missing inputs, doctor blockers, blocked deploys, out-of-scope runtime pages that block checkout proof, or merchant-specific uncertainty. The default path should not branch into external browser skills or hand-built backend order creation.
|
|
19
19
|
|
|
20
|
+
## Live-campaign changes
|
|
21
|
+
|
|
22
|
+
Keep a standing pull-request check after launch: install locked dependencies
|
|
23
|
+
with `npm ci`, build the campaign site with
|
|
24
|
+
`CPK_ENV=development npx --no-install campaign-build` (the check does not read the build summary),
|
|
25
|
+
then run `npx --no-install campaigns-os doctor --packet <packet> --ci --no-live-refs`.
|
|
26
|
+
Run these from the Page Kit folder containing the pinned tools and committed
|
|
27
|
+
packet, local CampaignSpec, Build Context and Assembly Report. The site build
|
|
28
|
+
must produce `_site/<public_route_slug>/`; `campaigns-os build` prepares inputs
|
|
29
|
+
and does not build the site. Do not record a new build merely to clear the check.
|
|
30
|
+
Match the Assembly Report's build environment: the documented local-proof flow
|
|
31
|
+
uses `development`; set `CPK_ENV=production` when the recorded evidence reviewed
|
|
32
|
+
a production render instead. Environment differences also change fingerprints.
|
|
33
|
+
|
|
34
|
+
| CI status | Exit code | Meaning |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `current` | `0` | Polish and QA both bind to the build on disk and neither is lifecycle-required, or the only owed stage is advisory Polish for a page-only change. |
|
|
37
|
+
| `owed` | `3` | Polish or QA binds to another build or is lifecycle-required; evidence is owed. For a page-only change only QA on the changed pages counts. |
|
|
38
|
+
| `unknown` | `4` | Output or assembly fingerprints are missing, a stage fingerprint is missing without a lifecycle requirement, output is unreadable, or inspection throws; debt cannot be determined. |
|
|
39
|
+
| `usage` | `2` | The packet is missing, unreadable or invalid JSON. |
|
|
40
|
+
|
|
41
|
+
Any owed stage returns 3; otherwise an unknown stage returns 4. Normal
|
|
42
|
+
doctor errors remain visible but do not determine the CI exit code. Without
|
|
43
|
+
`--ci`, doctor retains its usual inspection output and 0/2 result. `--json`
|
|
44
|
+
adds a `ci` object containing `status`, `exit_code`, `polish`, `qa`, and
|
|
45
|
+
`changes` when assessment completes. Without `--json`, every CI outcome prints
|
|
46
|
+
an escaped Markdown report, including a "could not evaluate" report for packet
|
|
47
|
+
usage errors (exit 2) or inspection failures (exit 4). Page changes are compared
|
|
48
|
+
with the retained assembly manifest, so the page list may be empty if that
|
|
49
|
+
manifest was already updated; old Polish/QA bindings still show debt.
|
|
50
|
+
Without a usable manifest, page changes are unknown, but matching stage
|
|
51
|
+
bindings remain current unless lifecycle-required, and differing bindings remain owed.
|
|
52
|
+
The check reads recorded fingerprints, not machine-local capture files.
|
|
53
|
+
Readback's `evidence_debt` uses the same assessment, showing an agent this debt
|
|
54
|
+
before it edits; its separate time-based staleness assessment is not the CI signal.
|
|
55
|
+
|
|
56
|
+
### What a change owes
|
|
57
|
+
|
|
58
|
+
Each owed stage carries `change`: what changed since the build that stage
|
|
59
|
+
last reviewed. It is read from the rebuilds `record build` keeps
|
|
60
|
+
(`stages.assembly.build_changes`) plus the drift on disk, so the changed pages
|
|
61
|
+
stay listed after `record build`. A change is one of two kinds:
|
|
62
|
+
|
|
63
|
+
- **Page-only.** Only the HTML of some pages changed (copy, image references,
|
|
64
|
+
inline styling), and none of them is a checkout page or changed its commerce markup:
|
|
65
|
+
`data-next-*`, `os-checkout-*` and other package, offer, shipping or upsell
|
|
66
|
+
attributes, inline event handlers (`onclick` and the like), `next-*` meta
|
|
67
|
+
tags (the upsell accept and decline routes among them), scripts, link
|
|
68
|
+
targets or form actions. A checkout page is one whose type is checkout,
|
|
69
|
+
checkout step or select, or one that carries checkout or payment markup
|
|
70
|
+
(`data-next-checkout*`, `data-next-payment*`, `data-next-express-checkout*`,
|
|
71
|
+
`os-checkout*`). The campaign's own entry in
|
|
72
|
+
`_data/campaigns.json` (SDK pin, analytics ids, store fields) is unchanged.
|
|
73
|
+
It owes QA on the changed pages only: `qa.scope` is `pages` and
|
|
74
|
+
`qa.owed_pages` lists them. Polish is advisory (`polish.advisory: true`): it
|
|
75
|
+
is reported, and `next`, the CI check and the QA gate do not require it.
|
|
76
|
+
- **Commerce-affecting.** Anything else: a checkout page, commerce markup or
|
|
77
|
+
the config entry changed, a page was added or removed (a route change), or a
|
|
78
|
+
built file other than a page's HTML changed (a script, stylesheet or image
|
|
79
|
+
file cannot be mapped to one page). It owes the full Polish and QA, as
|
|
80
|
+
does a change that cannot be read (a record made before `record build`
|
|
81
|
+
kept a commerce signature, or a rebuild history that no longer reaches the
|
|
82
|
+
reviewed build). A Polish or QA requirement a brief or spec change made is
|
|
83
|
+
always full.
|
|
84
|
+
|
|
85
|
+
A page-only change extends only a review that completed. Polish is advisory
|
|
86
|
+
only when the last Polish completed or was skipped; a blocked Polish owes the
|
|
87
|
+
full stage. QA is owed on the changed pages only when the last QA completed,
|
|
88
|
+
or when it was a page-scoped run that did not complete, in which case the
|
|
89
|
+
pages are counted from the full QA that run extended, so its unpaid pages stay
|
|
90
|
+
owed. A blocked or pending full QA owes a full run.
|
|
91
|
+
|
|
92
|
+
The PR comment opens with the kind it found and what that owes, for example
|
|
93
|
+
"Change found: page-only. It owes QA on `upsell1`; Polish is advisory." A
|
|
94
|
+
commerce-affecting change names its reasons.
|
|
95
|
+
|
|
96
|
+
Copy the [workflow example](https://github.com/NextCommerceCo/campaigns-os/blob/main/docs/examples/campaign-repo-evidence-check.yml) into the
|
|
97
|
+
campaign repository and adjust the packet path. It posts or updates one PR
|
|
98
|
+
comment using the fixed `<!-- campaign-evidence-check -->` marker. Evidence
|
|
99
|
+
reports use stdout only; stderr stays in the job log. If the command produces
|
|
100
|
+
no stdout, the comment points to the job log with a fixed fallback message.
|
|
101
|
+
If install or build fails before doctor runs, the comment is updated with
|
|
102
|
+
"Campaign build failed; evidence not evaluated. See the job log."
|
|
103
|
+
Evidence findings are **report-only by default**. Opt in to failing on a nonzero doctor
|
|
104
|
+
exit by setting the repository Actions variable
|
|
105
|
+
`CAMPAIGNS_OS_EVIDENCE_CHECK` to `fail`. Install/build failures still fail normally.
|
|
106
|
+
Fork pull requests may lack comment-write permission; comment failures are
|
|
107
|
+
nonfatal and the report remains in the job log. The example uses `pull_request`
|
|
108
|
+
and does not run untrusted code with `pull_request_target` privileges.
|
|
109
|
+
|
|
110
|
+
To repay a page-only change, rebuild, run `record build`, then run
|
|
111
|
+
`qa run --packet <packet> --page <page_id>` against that build, repeating
|
|
112
|
+
`--page` for each changed page the report names. A page-scoped run checks only
|
|
113
|
+
those pages and places no test orders; the QA stage it records keeps the
|
|
114
|
+
purchase proof of the full run it extends. It refuses, before checking
|
|
115
|
+
anything, when the output on disk is not the recorded build, when the change
|
|
116
|
+
is commerce-affecting, when a changed page is left out, when the QA it would
|
|
117
|
+
extend did not complete, or when QA's inputs (the brief's `qa_policy` and the
|
|
118
|
+
CampaignSpec) changed or cannot be confirmed current. Running Polish for a
|
|
119
|
+
page-only change is optional.
|
|
120
|
+
|
|
121
|
+
To repay fingerprint-drift debt for any other change, rebuild and record the output, re-run `polish capture` against
|
|
122
|
+
that served build, record Polish with `record polish --evidence <evidence.json>`,
|
|
123
|
+
then run `qa run` to record QA for the current build. For lifecycle-required
|
|
124
|
+
stages with matching build bindings, re-run the required stages against the
|
|
125
|
+
existing build: `polish capture` and `record polish` when Polish is owed, and
|
|
126
|
+
`qa run` when QA is owed. The CI report names each owed stage's cause and
|
|
127
|
+
selects the remedy accordingly. The workflow only inspects;
|
|
128
|
+
it does not renew evidence or deploy.
|
|
129
|
+
|
|
20
130
|
## Partial Builds
|
|
21
131
|
|
|
22
132
|
Partial builds are valid campaign work. A pass may build only new presell pages,
|
|
@@ -83,11 +193,11 @@ the assembly report.
|
|
|
83
193
|
- Landing and presell pages should preserve prepared HTML when it is a real standalone design. Use page-kit passthrough structure, inject the SDK/config requirements, and repoint CTAs into the CampaignSpec flow.
|
|
84
194
|
- **Pre-checkout pages must ship the same SDK bootstrap as the checkout layout.** Presell and landing pages are SDK `page_type: product`; they need `config.js` (before the loader), the `campaign-cart@v{sdk_version}/dist/loader.js` module script, and the `next-funnel` + `next-page-type` meta tags — not just inert `data-next-*` attributes. Without the loader the SDK silently no-ops: `data-next-hide` conditional visibility (`param.banner` / `param.seen`), `utmTransfer` UTM/query carry-through to checkout (top-of-funnel ad attribution), and SDK analytics never fire. Treat `param.banner` / `param.seen` visibility and `utmTransfer` as standard pre-checkout wiring, not per-campaign discoveries. Doctor enforces this with `built_output.pre_checkout_sdk_bootstrap`.
|
|
85
195
|
- **Every page names the same campaign.** A page borrowed from another funnel (a copied upsell or receipt) must not keep the other campaign's `next-api-key` / `config.js` API key, `next-funnel` meta, or `setAttribution({ funnel })` call; the SDK reads these per page and reconciles nothing, so the order lands on or attributes to the wrong campaign with no visible error. Doctor blocks this, unwaivably, with `built_output.campaign_identity` (one error per drift, naming both files and both values).
|
|
86
|
-
- **SDK markup must do what it says.** `data-next-checkout` goes on the `<form>`; `data-next-checkout-field` values are the SDK's fixed names (`fname`, `lname`, `postal` — never `firstName`, `lastName`, `zip`); an `add-to-cart` button linked by `data-next-selector-id` needs a selector with that id and that selector in `select` mode (swap mode plus the button writes the cart twice); every `data-next-upsell-action` sits inside its `data-next-upsell` container, on any page type; one default-selected card per selector; single-brace tokens inside SDK templates. Doctor enforces the first five as blockers and the last
|
|
196
|
+
- **SDK markup must do what it says.** `data-next-checkout` goes on the `<form>`; `data-next-checkout-field` values are the SDK's fixed names (`fname`, `lname`, `postal` — never `firstName`, `lastName`, `zip`); an `add-to-cart` button linked by `data-next-selector-id` needs a selector with that id and that selector in `select` mode (swap mode plus the button writes the cart twice); every `data-next-upsell-action` sits inside its `data-next-upsell` container, on any page type; one default-selected card per selector; single-brace tokens inside SDK templates; the page type in `<meta name="next-page-type">` (`upsell` on every upsell offer page), never only in a `data-next-page-type` attribute. Doctor enforces the first five as blockers and the last three as warnings under `built_output.sdk_markup` (codes `SWAP_WITH_ADD_TO_CART`, `CHECKOUT_NOT_FORM`, `WRONG_FIELD_NAME`, `MISSING_SELECTOR_ID_MATCH`, `ORPHANED_UPSELL_ACTION`, `DOUBLE_SELECTED`, `TEMPLATE_DOUBLE_BRACE`, `PAGE_TYPE_ATTRIBUTE`, `UPSELL_PAGE_TYPE_MISSING`).
|
|
87
197
|
- **Campaign scripts must parse.** A hand-edited script with a stray or missing bracket throws a `SyntaxError` on every page that loads it, and nothing in it runs. Doctor parses every campaign-owned `.js` file a built page loads by a local `<script src>` and blocks, unwaivably, with `built_output.script_syntax.parse_failure`, naming the file, line and column.
|
|
88
198
|
- Checkout, upsell, downsell, and receipt pages should preserve starter-template SDK contracts while keeping the campaign/source visual language. Treat starter templates as the reference for required `data-next-*` controls and wiring, not as a mandate to carry their visual chrome into the final campaign.
|
|
89
199
|
- If source HTML declares SDK-owned zones such as `data-commerce-zone="checkout-form"` or `data-commerce-zone="order-summary"`, keep the selected starter-template family's SDK wiring for that zone (the checkout form, bound fields, payment submit and cart summary). The wrapper and page composition around it stay source-owned; do not add family class names to the campaign's own layout to satisfy QA.
|
|
90
|
-
- If `context.theme` names a generated `brand-theme.css`, copy it into campaign assets and load it after `next-core.css` on checkout, upsell, downsell, and receipt pages. Generated brand-theme v0 is root-variable-only; do not use it as permission to edit SDK-owned selectors or runtime structure.
|
|
200
|
+
- If `context.theme` names a generated `brand-theme.css`, copy it into campaign assets and load it after `next-core.css` on checkout, upsell, downsell, and receipt pages. When `context.theme.generated.fonts_css_path` names a `brand-fonts.css`, copy it beside the brand layer and load it on the same pages, so headings and body copy use the design's faces (`record theme` refuses a styled page that skips it). Generated brand-theme v0 is root-variable-only; do not use it as permission to edit SDK-owned selectors or runtime structure.
|
|
91
201
|
- Buy-more-save-more selectors should use selected quantity plus Offer-aware price displays. Do not swap in stale package-per-tier IDs unless the CampaignSpec explicitly represents an older campaign that still owns separate packages for each option.
|
|
92
202
|
- SDK routing meta tags should be emitted as campaign-root paths, for example `/campaign-slug/upsell/`, even when the CampaignSpec source value is slug-relative like `upsell/`.
|
|
93
203
|
- One-time prepurchase/order-bump packages outside the main bundle should default to fixed quantity and fixed line total display unless the spec explicitly requires syncing quantity with the main bundle.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# CampaignSpec Authoring Examples
|
|
2
|
+
|
|
3
|
+
Use these examples when authoring or generating common Campaign Map shapes. They
|
|
4
|
+
are public-package guidance: the goal is clearer exported CampaignSpec fields,
|
|
5
|
+
not a private merchant workflow.
|
|
6
|
+
|
|
7
|
+
## Hero Product + Order Bump + Upsell + Receipt
|
|
8
|
+
|
|
9
|
+
Reference fixture: [single-step checkout, upsell and receipt](https://github.com/NextCommerceCo/campaigns-os/blob/main/contracts/fixtures/campaign-specs/shop-single-step-upsell-receipt.json).
|
|
10
|
+
Fixtures are repository test data and are not included in the installed package.
|
|
11
|
+
|
|
12
|
+
For CampaignSpec 5.0 multi-step shapes, compare the [three-step checkout](https://github.com/NextCommerceCo/campaigns-os/blob/main/contracts/fixtures/campaign-specs/shop-three-step-dynamic-shipping.json)
|
|
13
|
+
and [select-to-checkout](https://github.com/NextCommerceCo/campaigns-os/blob/main/contracts/fixtures/campaign-specs/olympus-mv-two-step-configurable.json) examples.
|
|
14
|
+
Author new specs against the shipped [v5 schema](../schemas/campaign-spec.v5.schema.json).
|
|
15
|
+
|
|
16
|
+
Canonical page shape:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
checkout -> upsell -> receipt
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Authoring guidance:
|
|
23
|
+
|
|
24
|
+
- Checkout page:
|
|
25
|
+
- `type: "checkout"` with `sdk_hints.sdk_page_type: "checkout"`
|
|
26
|
+
- `page_url: "/checkout/"` — `page_url` is the route field.
|
|
27
|
+
- `packages[]` entries use `ref_id` + `qty` (the real export key is `qty`,
|
|
28
|
+
not `quantity`). A plain entry is the main package; add-ons carry the
|
|
29
|
+
boolean role flags `is_order_bump: true` / `is_upsell: true` — there is
|
|
30
|
+
no `role` field in exports. The flags mean "add-on" on `checkout`,
|
|
31
|
+
`checkout_step` and `select` pages only. On an upsell or downsell page
|
|
32
|
+
they mark nothing (see the upsell page below).
|
|
33
|
+
- `sdk_hints.template_family` names the intended starter family only as a
|
|
34
|
+
hint; the Build Packet still locks the family.
|
|
35
|
+
- `sdk_hints.meta_tags.next-success-url` points to the next runtime route.
|
|
36
|
+
- `success_url` holds the next PAGE ID (never a URL, despite the name).
|
|
37
|
+
- A checkout may equally declare its forward step as `next_page`; twelve
|
|
38
|
+
pages across ten certified fixtures do. Forward links resolve from
|
|
39
|
+
whichever routing field a page declares — `on_accept`, then `success_url`,
|
|
40
|
+
then `next_page` — so an unconventional journey is wired rather than
|
|
41
|
+
dropped.
|
|
42
|
+
- `next_page` is the generic "wherever this page goes next" and is honoured on
|
|
43
|
+
every page type. The other two forward fields carry a page-shaped meaning
|
|
44
|
+
and are honoured only where that meaning exists:
|
|
45
|
+
- `success_url` means "after payment succeeds" — only on `type: "checkout"`.
|
|
46
|
+
- `on_accept` means "after accepting the offer on this page" — only on
|
|
47
|
+
`type: "upsell"` or `"downsell"`.
|
|
48
|
+
|
|
49
|
+
Declared anywhere else they are inert, `next_page` wins, and the
|
|
50
|
+
`RouteFieldIgnoredForPageType` warning says which field was skipped and
|
|
51
|
+
where the shopper actually goes. A `select` page is the first step of a
|
|
52
|
+
two-step checkout and a `checkout_step` is a non-final page of a split
|
|
53
|
+
checkout: neither takes payment nor presents an accept/decline offer, so a
|
|
54
|
+
copy-pasted `success_url` or `on_accept` there does not route.
|
|
55
|
+
- Multi-step checkout (CampaignSpec 5.0): `checkout` is the page that takes
|
|
56
|
+
payment, and it forwards to an upsell, downsell or thank-you page. A page
|
|
57
|
+
with a checkout form that collects details and moves on without placing an
|
|
58
|
+
order is `type: "checkout_step"` and routes with `next_page`; a bundle
|
|
59
|
+
picker before checkout is `type: "select"`. Every path from an entry page
|
|
60
|
+
to an upsell passes exactly one `checkout`; a campaign may hold several, one
|
|
61
|
+
per split-test path. A 4.2/4.3 spec that chained `checkout → checkout` is
|
|
62
|
+
read as `checkout_step` pages ahead of the last one.
|
|
63
|
+
See the [v5 schema](../schemas/campaign-spec.v5.schema.json) for the
|
|
64
|
+
authoring fields; runtime routing is projected by Campaigns OS.
|
|
65
|
+
|
|
66
|
+
- Upsell page:
|
|
67
|
+
- `type: "upsell"` or `type: "downsell"`, with
|
|
68
|
+
`sdk_hints.sdk_page_type: "upsell"`
|
|
69
|
+
- `page_url` is a Page Kit route, not a source filename.
|
|
70
|
+
- `packages[]` or `offers[]` names the post-purchase offer refs. Write each
|
|
71
|
+
package as a plain `{ ref_id, qty }` entry. On an upsell or downsell page
|
|
72
|
+
every package row is the page's offer and never an order bump, so
|
|
73
|
+
`is_upsell: true` on that row is accepted and changes nothing about
|
|
74
|
+
its role. Offers are identified by `ref_id` into the root `offers[]` catalog
|
|
75
|
+
(`package_ref_id` is not part of the contract).
|
|
76
|
+
- `on_accept` and `on_decline` hold the next PAGE IDs; route paths live
|
|
77
|
+
in `page_url` / derived `resolved_routing`.
|
|
78
|
+
|
|
79
|
+
- Receipt page:
|
|
80
|
+
- `type: "thankyou"` — the canonical terminal page type. The SDK-layer
|
|
81
|
+
`receipt` projection is expressed as `sdk_hints.sdk_page_type: "receipt"`
|
|
82
|
+
(and `meta_tags["next-page-type"]`), never as `page.type`.
|
|
83
|
+
- `page_url: "/receipt/"`
|
|
84
|
+
- Receipt summary/frontmatter hints preserve SDK-owned order item templates.
|
|
85
|
+
|
|
86
|
+
Generator checklist:
|
|
87
|
+
|
|
88
|
+
- Always emit stable `page.id` values; page IDs are the join key for
|
|
89
|
+
`source_html.pages[]`.
|
|
90
|
+
- Prefer Page Kit public routes (`checkout/`, `upsell/`, `receipt/`) over
|
|
91
|
+
filenames (`checkout.html`).
|
|
92
|
+
- Keep package, shipping, voucher, and offer refs in CampaignSpec/API fields,
|
|
93
|
+
not in source HTML.
|
|
94
|
+
- Put template-family preference in `campaign.preferred_template_family` when
|
|
95
|
+
useful, but require the Build Packet/template lock before commerce wiring.
|
|
96
|
+
- Keep checkout/upsell/downsell/receipt runtime surfaces template-clone-first;
|
|
97
|
+
source HTML can own visual composition and copy, not payment/order mutation.
|
|
98
|
+
|
|
99
|
+
## Campaign Currencies
|
|
100
|
+
|
|
101
|
+
- `campaign.currency` is the default currency: a three-letter upper-case
|
|
102
|
+
ISO 4217 code such as `"USD"`. A lower-case code (`"usd"`) or a symbol
|
|
103
|
+
(`"US$"`) is an error, because Campaign Cart can fall back to a cached USD
|
|
104
|
+
campaign and price the page in the wrong currency.
|
|
105
|
+
- `campaign.available_currencies` lists every currency the campaign sells in,
|
|
106
|
+
the default included, as code strings: `["USD", "EUR", "GBP"]`. The
|
|
107
|
+
Campaigns API returns this list as `{ code, label }` objects; Map Builder
|
|
108
|
+
writes the codes, and a hand-written spec should too.
|
|
109
|
+
- The Campaigns App API also has `additional_currencies`, which is the same
|
|
110
|
+
list without the default. It is not a CampaignSpec field: put every
|
|
111
|
+
currency in `available_currencies`.
|
|
112
|
+
- When `available_currencies` is non-empty and does not contain
|
|
113
|
+
`campaign.currency`, the validator warns. An empty list states nothing and
|
|
114
|
+
is not checked.
|
|
115
|
+
|
|
116
|
+
These checks are the `CampaignCurrencies` validator rule, which
|
|
117
|
+
`campaigns-os doctor` runs with the rest of the spec rules.
|
|
118
|
+
|
|
119
|
+
## Order Bump Notes
|
|
120
|
+
|
|
121
|
+
An order bump is still a checkout package/offer surface. It should be encoded as
|
|
122
|
+
a checkout page package with `is_order_bump: true`, a `qty`, and its source
|
|
123
|
+
`ref_id`.
|
|
124
|
+
When the selected starter family does not support a bump, the build should
|
|
125
|
+
remove the bump surface and record the decision in the Assembly Report instead
|
|
126
|
+
of leaving demo bump refs in copied frontmatter.
|
|
127
|
+
|
|
128
|
+
## Multi-Upsell Depth
|
|
129
|
+
|
|
130
|
+
For more than one post-purchase page, keep each upsell/downsell page explicit in
|
|
131
|
+
`funnels[].pages[]` and wire `on_accept` / `on_decline` to the next page. QA
|
|
132
|
+
then derives typed-card proof coverage from topology. `common` runs every
|
|
133
|
+
actual terminal path when they fit under the flood cap; above it, it runs
|
|
134
|
+
checkout, first-offer accept and decline, and a deduplicated shortest real
|
|
135
|
+
receipt path, then adds the shortest path that clicks the decline on each offer
|
|
136
|
+
or downsell page not yet declined, up to the cap. Explicit paths support targeted
|
|
137
|
+
repair, while `full` walks every actual terminal path in the selected checkout
|
|
138
|
+
topology. Cycles, missing routes, and reachable nonterminals block exhaustive
|
|
139
|
+
proof before browser launch. The accidental-flood cap remains `6`; when the
|
|
140
|
+
planned count exceeds it, the command names the exact explicit
|
|
141
|
+
`--max-test-orders` raise.
|
|
@@ -32,6 +32,48 @@ Unknown top-level fields are rejected.
|
|
|
32
32
|
|
|
33
33
|
## Contributions and coverage
|
|
34
34
|
|
|
35
|
+
**A design file behind an HTML export.** Name it on the page's source-html
|
|
36
|
+
manifest entry as `design_file: {type, file_url, breakpoints}`. `breakpoints`
|
|
37
|
+
holds optional desktop, tablet and mobile frame links. Intake records a separate,
|
|
38
|
+
non-renderable contribution (`figma_frames` with native trust for Figma, otherwise
|
|
39
|
+
`other` with opaque trust), with `reference_only` coverage. The page's
|
|
40
|
+
`html_funnel` mapping links to it through `derived_from`. Pages sharing a file
|
|
41
|
+
share one contribution. A CampaignSpec page's Figma `design_source` records lineage
|
|
42
|
+
only with figma-sections-export provenance that passes doctor's
|
|
43
|
+
`source_html.producer_provenance` check without a waiver; then it wins when both
|
|
44
|
+
channels are present. Otherwise, a spec pointer alone adds no contribution or
|
|
45
|
+
lineage at intake, and no `design.access` row unless the package already records
|
|
46
|
+
a spec contribution. A Figma `design_source` that names a different file or
|
|
47
|
+
frames from the manifest's `design_file` is reported as a conflict for review
|
|
48
|
+
even when it records no lineage, as is a file key that differs from the
|
|
49
|
+
exporter's. A file shared by several pages keeps the manifest's provenance if
|
|
50
|
+
any page declared it in the manifest.
|
|
51
|
+
|
|
52
|
+
Malformed `design_file` values remain non-fatal manifest warnings; the schema
|
|
53
|
+
accepts them so validation agrees with intake. File URL schemes are
|
|
54
|
+
case-insensitive. Explicit tool types are lowercased for identity. For a type
|
|
55
|
+
other than `figma` or `other`, the design-file source ref's optional
|
|
56
|
+
`design_file_type` string preserves the type doctor needs to reconstruct the
|
|
57
|
+
recorded identity even after a declaration edit. This new field is material;
|
|
58
|
+
source-reference `notes` remain nonmaterial, including on `design_file` refs.
|
|
59
|
+
|
|
60
|
+
Recording a file adds no readiness requirement, Source TODO or blocker. A
|
|
61
|
+
manifest edit, including `design_file`, follows the existing stale-package rule:
|
|
62
|
+
rerun intake with `--force`. A later spec declaration leaves the package current;
|
|
63
|
+
when eligible under the Figma provenance rule above, doctor reports the
|
|
64
|
+
unrecorded file with the same recovery guidance.
|
|
65
|
+
`design.access` reports `unexercised` / `design_access_missing` when the recorded
|
|
66
|
+
file agrees with the declaration. For a recorded spec contribution, doctor
|
|
67
|
+
compares the current spec declaration even if today's manifest or provenance is
|
|
68
|
+
unavailable. This version cannot read design files. This
|
|
69
|
+
records authority only; Polish element parity compares the built page with its
|
|
70
|
+
source HTML, not the design file (see [Polish evidence](polish-evidence.md)).
|
|
71
|
+
A changed design file is listed for review; changing only breakpoint frames on
|
|
72
|
+
that file does not change its identity. If the package records a file but the
|
|
73
|
+
current declaration is absent, `design.access` reports `review` /
|
|
74
|
+
`design_reference_missing`: restore the declaration, or rerun intake with
|
|
75
|
+
`--force` to drop the recorded file.
|
|
76
|
+
|
|
35
77
|
Each `contributions[]` record identifies one source contribution and records:
|
|
36
78
|
|
|
37
79
|
- `id`, `kind`, `trust`, and whether it is `renderable`;
|
|
@@ -275,10 +317,10 @@ The projected fields are explicit:
|
|
|
275
317
|
| Contribution core | `id`, `kind`, `trust`, `renderable`. |
|
|
276
318
|
| Provenance | `source_type`, `adapter`, `producer`, `generator`, `generator_version`, `source_root`, `manifest_schema_version`, `manifest_path`, `manifest_sha256`, `producer_material_fingerprint`, `asset_crawl_schema_version`. |
|
|
277
319
|
| Presentation intent | `summary`, `composition`, `content_hierarchy`, `imagery`, `copy`, `brand`, `responsive_behavior`. |
|
|
278
|
-
| Source reference | `id`, `kind`, `path`, `url`, `sha256`, `role`, `media_type
|
|
320
|
+
| Source reference | `id`, `kind`, `path`, `url`, `sha256`, `role`, `media_type`, optional `design_file_type` (only on `design_file` refs). |
|
|
279
321
|
| Visual reference | `id`, `kind`, `viewport`, `availability`, `url`, `path`, `sha256`, `width`, `height`, `device_profile`, `scale_factor`, `browser`, `source_ref_id`, `unavailable_reason`. |
|
|
280
322
|
| Template Reference | `id`, `family`, `version`, `contract_path`, `artifact_path`, `sha256`, `standard_viewport_refs` using the visual projection above. |
|
|
281
|
-
| Coverage mapping | `id`, `surface_id`, `coverage_role`, `confidence`, `source_refs`, `screenshot_refs`, `reference_refs`, `template_reference_id`. |
|
|
323
|
+
| Coverage mapping | `id`, `surface_id`, `coverage_role`, `confidence`, `source_refs`, `screenshot_refs`, `reference_refs`, `template_reference_id`, optional `derived_from`. |
|
|
282
324
|
| Source Gap | `id`, `kind`, `scope`, `applies_to`, `reason`, `status`, `attributed_by`, `attributed_at`, `evidence_refs`. |
|
|
283
325
|
| Source TODO | `id`, `kind`, `scope`, `applies_to`, `description`, `status`, `owner`, `required_viewports`, `source_ref_ids`. |
|
|
284
326
|
| Approved waiver | `id`, `scope`, `applies_to`, `reason`, `status`, `waived_by`, `waived_at`, `expires_at`, `review_condition`, `evidence_refs`. |
|
|
@@ -397,12 +439,24 @@ left by a process that died is recovered automatically, and a lock directory
|
|
|
397
439
|
without an owner record (only an older release leaves one) is never taken
|
|
398
440
|
over: the command refuses it after about a second and names it. Confirm no
|
|
399
441
|
campaigns-os process is working on the target, then remove it. Anything else
|
|
400
|
-
at the lock path (a file, a symbolic link,
|
|
401
|
-
that holds other entries) is not a lock:
|
|
402
|
-
what is there and never removes it. The
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
442
|
+
at the lock path (a file, a symbolic link, a directory the command cannot read,
|
|
443
|
+
or a directory with no owner record that holds other entries) is not a lock:
|
|
444
|
+
once the wait ends the command names what is there and never removes it. The
|
|
445
|
+
holder of the lock removes staging, recovery, released and abandoned
|
|
446
|
+
directories beside it whose recorded owner process no longer exists.
|
|
447
|
+
|
|
448
|
+
Run one Campaigns OS release against a target at a time. Each owner record
|
|
449
|
+
carries a `lock_format` number (the lock layout the writing release uses).
|
|
450
|
+
Releases before 1.43.2 took the lock in two steps, creating the directory and
|
|
451
|
+
then writing its owner, and a later release can replace that directory in the
|
|
452
|
+
gap between them, so both runs enter. A command that finds the lock held by a
|
|
453
|
+
live process whose owner record has no `lock_format`, or a different one,
|
|
454
|
+
refuses at once and names that process instead of waiting for it. Every
|
|
455
|
+
release before `lock_format` existed writes no number, so a holder from one of
|
|
456
|
+
those is refused too, even though only releases before 1.43.2 can race. The
|
|
457
|
+
check only sees a release that already holds the lock: two releases that start
|
|
458
|
+
at the same instant can still meet in that gap. A dead holder from any release
|
|
459
|
+
is recovered as usual. A waiver's `--dry-run` preview takes no lock.
|
|
406
460
|
|
|
407
461
|
Before writing any output, `prepare-build` also requires distinct paths for the
|
|
408
462
|
Build Packet, Build Context, Assembly Report, Doctor output, normalized Build
|
|
@@ -458,6 +512,8 @@ only when filename matching was ambiguous, and is `null` otherwise.)
|
|
|
458
512
|
|
|
459
513
|
### The record shape
|
|
460
514
|
|
|
515
|
+
Polish now reads these files. Record `scale_factor` (the device pixel ratio the screenshot was taken at) and keep `width`/`height` equal to the image's pixel size: a screenshot is compared with the built page only when its width divided by `scale_factor` equals the capture width, and a screenshot without `scale_factor` is not compared. Link it to its source page (`source_ref_id`, filled in for you) and record `captured_at`, or it is listed for review.
|
|
516
|
+
|
|
461
517
|
One manifest page entry with its proof, inside the envelope the validator requires
|
|
462
518
|
(the mobile record is elided):
|
|
463
519
|
|
package/docs/effects.md
CHANGED
|
@@ -70,10 +70,12 @@ with one of these:
|
|
|
70
70
|
| Token | Resolves to |
|
|
71
71
|
| --- | --- |
|
|
72
72
|
| `{target}` | The target the invocation names: the directory given to `--target`, or the Page Kit target repository the Build Packet points at. |
|
|
73
|
+
| `{runtime}` | The funnel's runtime directory under `{target}`: `{target}/.campaign-runtime` for a Build Packet that records no `assembly.runtime_dir` (every packet before per-funnel records, and the first funnel of a project), else `{target}/<assembly.runtime_dir>`, which is `.campaign-runtime/funnels/<public_route_slug>` for each later funnel of the same project. The funnel's Build Context, Assembly Report, doctor output, theme output and intake inputs live here. |
|
|
73
74
|
| `{cwd}` | The working directory the invocation runs in. |
|
|
74
75
|
| `{spec}` | The CampaignSpec file the Build Packet names (`spec.local_path`), which need not live inside the target repository. |
|
|
75
76
|
| `{home}` | Your machine: the home directory and the config root under it (`XDG_CONFIG_HOME` when set). |
|
|
76
77
|
| `{packet}` | The Build Packet the invocation names (`--packet`), wherever it lives — it need not be the copy inside the target repository. |
|
|
78
|
+
| `{packet-runtime}` | The same funnel runtime directory beside the Build Packet: `<packet directory>/.campaign-runtime`, or `<packet directory>/<assembly.runtime_dir>` when the packet records one. The committed QA verdict sidecar and the Run Records live here. |
|
|
77
79
|
| `{lifecycle-journal}` | The command-lifecycle journal wherever it was selected for this invocation. |
|
|
78
80
|
| `{proxy-base}` | The endpoint `--proxy-base` names, or the canonical NEXT endpoint when it does not. |
|
|
79
81
|
| `{base-url}` | The campaign under test, as `--base-url` names it or as the packet derives it. |
|
|
@@ -81,8 +83,8 @@ with one of these:
|
|
|
81
83
|
|
|
82
84
|
The tokens matter because effects are not all under the target. `install-skills`
|
|
83
85
|
writes your **home** directory, not the campaign. `telemetry on` writes your
|
|
84
|
-
**machine** config. `run-record` writes beside the **
|
|
85
|
-
target repo. A row that said "writes the target" would be wrong about all three.
|
|
86
|
+
**machine** config. `run-record` writes beside the **Build Packet**
|
|
87
|
+
(`{packet-runtime}`), not the target repo. A row that said "writes the target" would be wrong about all three.
|
|
86
88
|
|
|
87
89
|
### The Map Builder fetch
|
|
88
90
|
|
|
@@ -109,14 +111,14 @@ campaign. No store or Admin credential is used. The proxy's answer is an
|
|
|
109
111
|
envelope whose `data` field holds the campaign. A failed read, an `ok: false`
|
|
110
112
|
envelope, one with an `error` and no campaign, or several campaigns with no
|
|
111
113
|
`campaign.ref_id` to pick one is recorded as `not_run` with its reason, never
|
|
112
|
-
as a pass. `--no-live-refs` on `doctor` or `
|
|
114
|
+
as a pass. `--no-live-refs` on `doctor`, `qa run` or `spec derive` skips only this read and
|
|
113
115
|
records `not_run` with reason `disabled`; every other declared write and send
|
|
114
116
|
is unchanged. Each form has its own row, including `doctor --write
|
|
115
117
|
--no-live-refs` and `qa run --no-live-refs` with each `qa run` modifier.
|
|
116
118
|
`doctor --no-write --no-live-refs` has the effects of the `doctor
|
|
117
119
|
--no-live-refs` row.
|
|
118
120
|
|
|
119
|
-
Only `doctor` and `
|
|
121
|
+
Only `doctor`, `qa run` and `spec derive` (see `spec derive` and the live campaign, below) make this read. The other commands that run doctor
|
|
120
122
|
internally make no request and record `derived.live_campaign_refs` as
|
|
121
123
|
`not_run` with reason `not_read`, with no warning: `start`, `prepare-build`
|
|
122
124
|
and `build` (the intake alias for prepare-build + doctor), `theme waive`,
|
|
@@ -130,11 +132,13 @@ itself carries QA's own read).
|
|
|
130
132
|
|
|
131
133
|
### `record brief` and `record spec`
|
|
132
134
|
|
|
133
|
-
**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 `{
|
|
135
|
+
**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 the funnel's runtime directory (`{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.
|
|
134
136
|
|
|
135
|
-
**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
|
|
137
|
+
**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, and it sends nothing. It writes the Build Packet and the Design Source Package only when it binds a page change: a move of routes alone, or on a handed-off campaign (deploy or QA completed) a page added or removed; then a deploy recorded without a build fingerprint is owed again too when the set of built pages changes (a fingerprint-bound deploy is made owed by the next `record build`). A rerun after a run that stopped part way moves the Design Source Package references that run left behind. `spec derive` no longer rebinds identity; run `record spec` after it. A change to the order of active pages, or a page added or removed before handoff, still needs intake.
|
|
136
138
|
|
|
137
|
-
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.
|
|
139
|
+
Both `record` 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.
|
|
140
|
+
|
|
141
|
+
**`spec derive` and the live campaign.** Once the campaign is built (its `_site/<route>/` exists) and a public Campaigns API key resolves, `spec derive` makes the same read-only `GET {proxy-base}/api/campaign` doctor makes and refreshes the offer and package fields the spec mirrors from the Campaigns App; `--dry-run` makes it too, so the diff shows those fields. `--no-live-refs` sends nothing (its own row, tier B). `spec derive` and its `--dry-run` are tier A.
|
|
138
142
|
|
|
139
143
|
## How to read a row
|
|
140
144
|
|
|
@@ -164,8 +168,9 @@ flags that change what the invocation does to the world are listed once, in
|
|
|
164
168
|
`vocabulary.effect_changing_flags`: `--apply`, `--browser`, `--built`, `--dry-run`,
|
|
165
169
|
`--emit-packet`, `--example`, `--force`, `--from-store`, `--list`,
|
|
166
170
|
`--no-live-refs`, `--no-post-verdict`, `--no-probe`, `--no-remit`, `--no-run-session`,
|
|
167
|
-
`--no-write`, `--republish`, `--test-order`, `--
|
|
168
|
-
that only change the output shape (`--json`, `--report`)
|
|
171
|
+
`--no-write`, `--republish`, `--test-order`, `--vendor-beacons`, `--write`,
|
|
172
|
+
`--write-map`. Flags that only change the output shape (`--json`, `--report`)
|
|
173
|
+
deliberately do not.
|
|
169
174
|
|
|
170
175
|
**"The help text" is every help block the CLI prints**, not one file's.
|
|
171
176
|
`campaigns-os qa` prints its own from `src/qa-node.mjs`, and while the coverage
|
|
@@ -253,6 +258,9 @@ input resolution. They append no lifecycle entry. Accepted modes remain
|
|
|
253
258
|
`accept`, `decline`, and `both` (case-insensitive); browser `--test-order` still
|
|
254
259
|
takes precedence and does not require the legacy cart. API credentials are
|
|
255
260
|
still checked only inside the legacy handler and failures there are journaled.
|
|
261
|
+
A `qa run --vendor-beacons` value other than `live`, or `--vendor-beacons` on
|
|
262
|
+
a run without browser `--test-order`, is likewise an argv-only refusal before
|
|
263
|
+
QA input resolution.
|
|
256
264
|
The nested run-record refusal scope in session closeout guards against future
|
|
257
265
|
changes. No internal closeout can currently create a refusal before its
|
|
258
266
|
invoking command journals.
|