@nextcommerce/campaigns-os 1.41.2 → 1.43.2
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 +4 -2
- package/CHANGELOG.md +629 -0
- package/README.md +8 -6
- package/agents/claude/CLAUDE.md +5 -1
- package/campaign-spec/dist/types.d.ts +2 -0
- package/contracts/agent-relevant-change-policy.v1.json +5 -0
- package/contracts/effects.v1.json +118 -25
- package/contracts/migration-sidecar-bundle.v0.json +9 -0
- package/contracts/release-ledger.json +1424 -0
- package/contracts/supported-surface.json +12 -11
- package/docs/build-packet.md +120 -8
- package/docs/campaigns-os-build-flow.md +3 -2
- package/docs/design-source-package.md +89 -15
- package/docs/effects.md +83 -2
- package/docs/local-setup.md +51 -0
- package/docs/migration-sidecar-bundle.md +6 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/progress-snapshots.md +16 -6
- package/docs/qa-and-test-orders.md +157 -17
- package/docs/release-ledger-authoring-guide.md +6 -4
- package/docs/runtime-readiness.md +1 -1
- package/docs/skills-revision.md +10 -10
- package/package.json +3 -2
- package/schemas/campaign-runtime-assembly-report.v0.schema.json +6 -1
- package/schemas/campaign-runtime-build-packet.v0.schema.json +6 -1
- package/schemas/campaign-spec.v4.schema.json +4 -0
- package/schemas/campaigns-os-progress-snapshot.v0.schema.json +1 -0
- package/schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json +1 -0
- package/schemas/campaigns-os-qa-verdict.v0.schema.json +1 -0
- package/schemas/campaigns-os-run-record.v0.schema.json +1 -0
- package/skills/campaign-lifecycle-orientation/SKILL.md +13 -8
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +8 -6
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +4 -4
- package/skills/next-campaigns-os/SKILL.md +17 -4
- package/skills/next-campaigns-os/references/session-intake.md +4 -4
- package/skills/next-campaigns-os-setup/SKILL.md +3 -3
- package/skills/next-campaigns-polish/SKILL.md +3 -3
- package/skills/next-campaigns-qa/SKILL.md +10 -9
- package/skills.json +11 -11
- package/src/build-brief.mjs +6 -4
- package/src/built-script-syntax.mjs +480 -0
- package/src/campaigns-api-key.mjs +99 -0
- package/src/cli-helpers.mjs +118 -0
- package/src/cli.mjs +796 -6963
- package/src/design-source-package.mjs +1 -1
- package/src/design-source-publication.mjs +898 -0
- package/src/diagnostic.mjs +2 -1
- package/src/directory-lock.mjs +270 -0
- package/src/doctor/checks.mjs +4415 -0
- package/src/doctor/inspect.mjs +636 -0
- package/src/doctor/next-step.mjs +731 -0
- package/src/finding-cause.mjs +14 -10
- package/src/install-invocation.mjs +29 -0
- package/src/invocation.mjs +179 -0
- package/src/lifecycle.mjs +5 -4
- package/src/polish-node.mjs +5 -2
- package/src/private-template-source.mjs +1 -1
- package/src/progress-node.mjs +9 -37
- package/src/progress.mjs +5 -3
- package/src/proof-policy.mjs +1 -1
- package/src/qa-analytics-correctness.mjs +3 -0
- package/src/qa-binding-evidence.mjs +76 -11
- package/src/qa-browser.mjs +778 -77
- package/src/qa-build-scope.mjs +47 -0
- package/src/qa-node.mjs +276 -39
- package/src/qa-publish.mjs +4 -0
- package/src/qa-sidecar.mjs +2 -0
- package/src/qa-verdict-discovery.mjs +11 -0
- package/src/qa-verdict-publish.mjs +1 -0
- package/src/qa-verdict.mjs +8 -1
- package/src/readback.mjs +2 -1
- package/src/run-record-closeout.mjs +3 -4
- package/src/run-record.mjs +4 -0
- package/src/sidecar-bundle.mjs +21 -0
- package/src/source-html-intake.mjs +1 -1
- package/src/source-html-manifest.mjs +9 -2
- package/src/spec-source-identity.mjs +44 -0
- package/src/stage-ledger.mjs +32 -1
- package/src/target-lock.mjs +54 -0
- package/src/template-brand-contract.mjs +17 -1
- package/src/tooling-setup.mjs +160 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "The downstream contract manifest. Everything listed here is SUPPORTED SURFACE: consumers (campaigns-agent, campaign-builder, the private ops repo, page-kit campaign repos) may depend on it, and changing it is a deliberate act — hashed entries require a surface_version bump in the same change (check-supported-surface.mjs --base, mirroring the skills.json bump gate), named entries must keep existing at their path, cli_commands must keep resolving in the CLI dispatch, package_exports must stay exported, and every entry must ship in the npm pack (files[] coverage). Anything NOT listed here — src/** internals, scripts/** checkers, examples/**, prompts/**, contracts/** other than this file and the entries named[] below (the orientation contract, the release ledger, and the consumer-facing orientation fixtures) — is implementation: consumers may read it for context but must not build on it, and it can change without notice. Rationale and the compatibility promise: docs/supported-surface.md.",
|
|
3
|
-
"surface_version": "1.
|
|
3
|
+
"surface_version": "1.43.2",
|
|
4
4
|
"package_exports": [
|
|
5
5
|
"./commercial-journey",
|
|
6
6
|
"./commercial-parity",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
],
|
|
48
48
|
"hashed": {
|
|
49
49
|
"contracts/migration-sidecar-bundle.v0.json": {
|
|
50
|
-
"sha256": "
|
|
50
|
+
"sha256": "53269453dfdaac4b286124d445b60b0c46205dfbaf8037ac61b30254a32df53a"
|
|
51
51
|
},
|
|
52
52
|
"contracts/runtime-recipe.campaigns-os-node-v1.json": {
|
|
53
53
|
"sha256": "90aaaaade90cf2525ea1aa05be5cb2d02b57e6e59f9278a946efa8a087946fd3"
|
|
@@ -59,16 +59,16 @@
|
|
|
59
59
|
"sha256": "65bcd8b5f41f1e9ecfc36f3b8838cd4ed531bd74c5a318ee5147131f6c9b4a1c"
|
|
60
60
|
},
|
|
61
61
|
"schemas/campaign-runtime-assembly-report.v0.schema.json": {
|
|
62
|
-
"sha256": "
|
|
62
|
+
"sha256": "8a0d37be9a4bb34d0ed073c9d6bfa4406a7b499c801c34c5fbb26646e2665c0b"
|
|
63
63
|
},
|
|
64
64
|
"schemas/campaign-runtime-build-context.v0.schema.json": {
|
|
65
65
|
"sha256": "f310c1b7844d9b1994864d15858ff0c6f17efc1e5285ba193d181bdb2dd4be2f"
|
|
66
66
|
},
|
|
67
67
|
"schemas/campaign-runtime-build-packet.v0.schema.json": {
|
|
68
|
-
"sha256": "
|
|
68
|
+
"sha256": "47c2899f409ed5927cf410bddd63e7bb8a9d154eabf7a07455777aed118212e9"
|
|
69
69
|
},
|
|
70
70
|
"schemas/campaign-spec.v4.schema.json": {
|
|
71
|
-
"sha256": "
|
|
71
|
+
"sha256": "af56c3de638d3c4e1fd5df8c2d733754d9ad16caa23b5bec92ce3a6f71bd0271"
|
|
72
72
|
},
|
|
73
73
|
"schemas/campaigns-os-legacy-migration-inventory.v0.schema.json": {
|
|
74
74
|
"sha256": "d6edc4572900d15fdaf6b8c59916b0d885303c1d398bdadeb7c58285522c9049"
|
|
@@ -80,10 +80,10 @@
|
|
|
80
80
|
"sha256": "1405c32bb9698030e9a40d754191e413cdd505dde91ac7de2e41c8887700f8f2"
|
|
81
81
|
},
|
|
82
82
|
"schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json": {
|
|
83
|
-
"sha256": "
|
|
83
|
+
"sha256": "3c40fd79f218d1b95f0424f9a2b67db140a306329e2ca02793e2740d3afded59"
|
|
84
84
|
},
|
|
85
85
|
"schemas/campaigns-os-qa-verdict.v0.schema.json": {
|
|
86
|
-
"sha256": "
|
|
86
|
+
"sha256": "ec4514e81a791a63bc0b358d7977aa823f3c5524e0f5caaba2712e66f262e18a"
|
|
87
87
|
},
|
|
88
88
|
"schemas/campaigns-os-doctor-output.v0.schema.json": {
|
|
89
89
|
"sha256": "87866e619d2a18f01a208a1682cb14a2079f61df51c642f4e1902b6e6bce0ce5"
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"sha256": "08ca132134b9e4610370e88082a5e888d09ddec0d5ee4512d84cc57181da722e"
|
|
96
96
|
},
|
|
97
97
|
"schemas/campaigns-os-run-record.v0.schema.json": {
|
|
98
|
-
"sha256": "
|
|
98
|
+
"sha256": "00dc9bcec79c12ea22a27f636188bcc33c6811df2d646ac7b49d4cc076e97355"
|
|
99
99
|
},
|
|
100
100
|
"schemas/campaigns-os-runtime-recipe.v1.schema.json": {
|
|
101
101
|
"sha256": "f8a2ab5eadbb71fb6d5653b670ee71e4471c3df74dd04000d3dbd3d9ae9e71fc"
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"sha256": "841c1f884260ce3eb3ddb518960a127bc915992a9539c818e5986ce156acff1f"
|
|
111
111
|
},
|
|
112
112
|
"schemas/campaigns-os-progress-snapshot.v0.schema.json": {
|
|
113
|
-
"sha256": "
|
|
113
|
+
"sha256": "ef31b4f20e39dfc1614dd4b45e4cf1692a51760b008cb51d133659307bd2a954"
|
|
114
114
|
},
|
|
115
115
|
"demo/apollo-v0/provenance.json": {
|
|
116
116
|
"sha256": "2e440be6513fe06b351c3a72c35424a04f807792621cb01e89803441fd925e06"
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"sha256": "dfc9abed38d456969e47a21f606d308a03bdf036f3466f7d6e47a602747dacf4"
|
|
120
120
|
},
|
|
121
121
|
"contracts/effects.v1.json": {
|
|
122
|
-
"sha256": "
|
|
122
|
+
"sha256": "1dae66e70bb5d167ca407d4ad03e70a1e2e761407b8ad7b85c8a7a51456df858"
|
|
123
123
|
},
|
|
124
124
|
"schemas/campaigns-os-effects.v1.schema.json": {
|
|
125
125
|
"sha256": "3eadd22169ab98bc7c2f682af2751267605170581182158d96be035b0cfe44dc"
|
|
@@ -212,6 +212,7 @@
|
|
|
212
212
|
"agents/codex/AGENTS.md",
|
|
213
213
|
"agents/copilot/copilot-instructions.md",
|
|
214
214
|
"agents/cursor/campaigns-os.mdc",
|
|
215
|
-
"docs/skills-revision.md"
|
|
215
|
+
"docs/skills-revision.md",
|
|
216
|
+
"docs/local-setup.md"
|
|
216
217
|
]
|
|
217
218
|
}
|
package/docs/build-packet.md
CHANGED
|
@@ -4,7 +4,7 @@ The Build Packet is the campaign assembly handoff. It wraps, but does not replac
|
|
|
4
4
|
|
|
5
5
|
It answers:
|
|
6
6
|
|
|
7
|
-
- Which CampaignSpec and Map ID are we building?
|
|
7
|
+
- Which CampaignSpec and saved Map ID or local-spec ID are we building?
|
|
8
8
|
- Which public route slug and campaign directory are expected?
|
|
9
9
|
- Where are the prepared HTML/assets?
|
|
10
10
|
- Which Campaign Build Brief is the merchandising/design presentation truth?
|
|
@@ -15,6 +15,56 @@ It answers:
|
|
|
15
15
|
|
|
16
16
|
The current schema is `schemas/campaign-runtime-build-packet.v0.schema.json`.
|
|
17
17
|
|
|
18
|
+
## Local-spec entry
|
|
19
|
+
|
|
20
|
+
A saved Map is optional for a prepared-HTML build. The coding agent authors an
|
|
21
|
+
ordinary CampaignSpec from the brief, source design and configured campaign's
|
|
22
|
+
real commerce values, following `schemas/campaign-spec.v4.schema.json`. The
|
|
23
|
+
operator supplies the selected store/campaign, public Campaigns API key, intended
|
|
24
|
+
pages and commercial choices, plus store contact details and policy URLs. Verify
|
|
25
|
+
the store/campaign binding and package/offer references; do not guess commerce
|
|
26
|
+
values. No gateway or Map provisioning is required for this entry.
|
|
27
|
+
|
|
28
|
+
Set `spec_identity.local_spec_id` to a new UUID once, commit it with the spec,
|
|
29
|
+
and keep it unchanged through revisions and fresh checkouts. It accepts 1–64
|
|
30
|
+
letters, digits, underscores or hyphens, with no surrounding whitespace. Local
|
|
31
|
+
IDs are checked exactly; the legacy normalization of saved Map IDs does not
|
|
32
|
+
apply. Malformed or conflicting local identities cannot be adopted into campaign
|
|
33
|
+
evidence; blocked diagnostic reports may still be written. Doctor reports local
|
|
34
|
+
identity failures as `spec.local_identity`, while
|
|
35
|
+
saved-Map failures retain `spec.map_id`. Set `spec_identity.public_route_slug`
|
|
36
|
+
to the intended route. Omit `map_id`, saved-Map URLs and saved-Map revision
|
|
37
|
+
metadata; a local ID is never a Map ID. A spec declaring both kinds is refused.
|
|
38
|
+
A separately authored campaign gets a new local ID even if its route matches.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
npx --no-install campaigns-os start --spec campaign-spec.json --source source-html --target . --template-family <certified-family> --deploy-target local-serve
|
|
42
|
+
npx --no-install campaigns-os next --packet campaign-runtime.build.json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The packet and report retain `map_id: null` and carry `local_spec_id`. Doctor,
|
|
46
|
+
report writes, polish capture, progress, run closeout and QA compare that local
|
|
47
|
+
identity. Material spec hashes still bind the current revision; a changed ID or
|
|
48
|
+
content cannot reuse earlier proof. After a material revision, follow `next` to
|
|
49
|
+
refresh preparation and affected evidence. Keep the spec, source, dependency
|
|
50
|
+
pins and canonical sidecars in Git. Use `readback` and `next` after a fresh
|
|
51
|
+
checkout; identity survives the move, but proof freshness is assessed again.
|
|
52
|
+
|
|
53
|
+
Run QA through `--packet`. Local verdicts use the storage key
|
|
54
|
+
`local-spec-<local_spec_id>` and carry the explicit ID in the full verdict and
|
|
55
|
+
committed sidecar. A matching route alone cannot adopt a verdict. Local QA is
|
|
56
|
+
never posted to the Map portal: `qa run` suppresses publication even with
|
|
57
|
+
`--post-verdict`, while `qa publish` refuses with `local_spec`. Progress remains
|
|
58
|
+
local with `map_id_missing`. Run Telemetry retains
|
|
59
|
+
its existing consent controls. `spec derive --write-map` requires a real saved
|
|
60
|
+
Map. Moving to a saved Map requires fresh preparation and evidence; this entry
|
|
61
|
+
does not claim saved-Map revision alignment.
|
|
62
|
+
|
|
63
|
+
Existing saved-Map specs and packets continue to work. The identity change does
|
|
64
|
+
not relax template certification, source proof, store/SDK parity, polish,
|
|
65
|
+
commerce checks, or typed-card checkout proof. Resolve their reported gates;
|
|
66
|
+
localhost readiness is not production approval.
|
|
67
|
+
|
|
18
68
|
## Root-Served Campaigns (`campaign.route_root`)
|
|
19
69
|
|
|
20
70
|
Most campaigns are served under a slug prefix (`/<public_route_slug>/...`), and
|
|
@@ -78,7 +128,7 @@ campaigns-os page-kit sync --packet campaign-runtime.build.json [--dry-run] [--j
|
|
|
78
128
|
```
|
|
79
129
|
|
|
80
130
|
The CampaignSpec is the authority for the Store Profile: those values are
|
|
81
|
-
authored in the Map
|
|
131
|
+
authored in the saved Map or the repository-owned local spec, so `page-kit sync` writes the nine
|
|
82
132
|
fields the spec carries (`campaign.store_*`) unconditionally. The SDK pin is
|
|
83
133
|
different. On an existing campaign the repo pin moves first and the Map/spec
|
|
84
134
|
is stale until someone re-saves it, so a spec → repo write would undo a bump
|
|
@@ -103,7 +153,7 @@ covers only the governed fields. `--dry-run` prints the same diff without
|
|
|
103
153
|
writing. Exit 0 on success (including a no-op re-run); exit 2 with
|
|
104
154
|
`page_kit.sync.*` error codes and nothing written when the packet cannot be
|
|
105
155
|
read, the target entry or the spec is missing or not an object, the spec
|
|
106
|
-
identifies another campaign (`spec_identity.public_route_slug` or `
|
|
156
|
+
identifies another campaign (`spec_identity.public_route_slug`, `map_id` or `local_spec_id`
|
|
107
157
|
disagreeing with the packet: `page_kit.sync.spec_identity_mismatch`), or the
|
|
108
158
|
resolved `_data/campaigns.json` lies outside the target repo through a symlink
|
|
109
159
|
(`page_kit.sync.target_escapes_repo`).
|
|
@@ -733,6 +783,64 @@ advisories, `unknown_attributes[]`, `pages_scanned`,
|
|
|
733
783
|
It passes, with no advisory, on the canonical rendered output of every
|
|
734
784
|
certified starter family (`fixtures/certified-families/`).
|
|
735
785
|
|
|
786
|
+
### Built-output script syntax gate (`built_output.script_syntax`)
|
|
787
|
+
|
|
788
|
+
Every doctor run that sees built output (the packet path and `doctor --built`
|
|
789
|
+
alike) parses each campaign-owned `.js` file a built page loads by a local
|
|
790
|
+
`<script src>`. A script that does not parse throws a `SyntaxError` on every
|
|
791
|
+
load of every page that references it, and nothing it defines runs; every
|
|
792
|
+
HTML-reading gate passes over it. The shape that shipped was a template-family
|
|
793
|
+
checkout script, copied and hand-edited, left with one closing `});` too many.
|
|
794
|
+
|
|
795
|
+
Parsing uses Acorn at the latest `ecmaVersion`: `sourceType: 'script'` for
|
|
796
|
+
classic scripts and `'module'` for `type="module"`, which is how the browser
|
|
797
|
+
reads each. Remote scripts (an `http(s):` URL, a protocol-relative `//` URL,
|
|
798
|
+
`data:`) are not campaign-owned and are not read, and neither are data blocks
|
|
799
|
+
such as JSON-LD. The type is compared as the browser compares it, with
|
|
800
|
+
surrounding ASCII whitespace stripped and case ignored. A classic `nomodule`
|
|
801
|
+
script is skipped: a module-capable browser never fetches or runs it. A
|
|
802
|
+
`type="module"` script ignores `nomodule` and is still parsed. Each src
|
|
803
|
+
resolves the way the browser resolves it: against the base in effect when the
|
|
804
|
+
parser prepares the script at its end tag, which is the first HTML `<base
|
|
805
|
+
href>` in tree order among those already parsed, or else the page. A `<base>`
|
|
806
|
+
parsed after a script does not move it, whether it is async, deferred or a
|
|
807
|
+
module: its URL is fixed when it is prepared, not when it is fetched. Parse
|
|
808
|
+
order decides, not final tree position, so a base that table foster parenting
|
|
809
|
+
moves ahead of an earlier script still does not apply to it. A `base` inside
|
|
810
|
+
SVG or MathML is not a base element, and only HTML-namespace `<script>`
|
|
811
|
+
elements are read: an SVG `<script>` never loads a `src` attribute. The href is read as the URL parser reads
|
|
812
|
+
it: only leading and trailing ASCII control characters and spaces are
|
|
813
|
+
stripped. A base
|
|
814
|
+
the browser refuses (one that does not parse, or a `data:` or `javascript:`
|
|
815
|
+
URL) falls back to the page, as the HTML "set the frozen base URL" steps
|
|
816
|
+
require. The percent-decoded path maps under the site root first, then the
|
|
817
|
+
campaign directory, never outside either. A base on another origin makes
|
|
818
|
+
relative srcs remote. Imports inside a module are not followed.
|
|
819
|
+
|
|
820
|
+
A parse failure blocks (not waivable — a script that cannot be parsed cannot be
|
|
821
|
+
intended to ship) under `built_output.script_syntax.parse_failure`, one error
|
|
822
|
+
per file. The message leads with `<file>:<line>:<column>` and a fixed
|
|
823
|
+
diagnostic category (for example `Unexpected token` or `Invalid regular
|
|
824
|
+
expression`), never text from the script, and names the pages that load the
|
|
825
|
+
file. A referenced local script that is not in the built output is a warning,
|
|
826
|
+
not a blocker, under `built_output.script_syntax.missing_script`, one warning
|
|
827
|
+
per src naming the pages that load it: the browser gets a 404 for it and
|
|
828
|
+
nothing it would define runs, but whether the page needs it is not known here.
|
|
829
|
+
The src is also listed in `scripts_unresolved[]`. While a parse failure blocks
|
|
830
|
+
the gate, the missing scripts stay on the gate's `warned[]` rather than also
|
|
831
|
+
surfacing as warnings.
|
|
832
|
+
|
|
833
|
+
The gate's evidence lands beside the other checkpoint gates at
|
|
834
|
+
`derived.checkpoint_gates[]` (`id: built_output.script_syntax`, status `pass` |
|
|
835
|
+
`blocked` | `not_applicable`, `findings[]` with `file`, `line`, `column`,
|
|
836
|
+
`source_type` and `pages`, `warned[]` with `src` and `pages`,
|
|
837
|
+
`scripts_scanned`, `scripts_unresolved[]`, `pages_scanned`). Fixtures: `fixtures/script-syntax/{good,bad}`. It passes, parsing every
|
|
838
|
+
local script the pages load and with no missing-script warning, on the
|
|
839
|
+
canonical rendered output of every certified starter family
|
|
840
|
+
(`fixtures/certified-families/`). QA applies the same rule to the page scripts
|
|
841
|
+
it reads for credential declarations (`script-parse:<page_id>`; see
|
|
842
|
+
[QA and test orders](qa-and-test-orders.md)).
|
|
843
|
+
|
|
736
844
|
> **Where does the source HTML come from?** See [docs/entry-points.md](./entry-points.md) for the five recognized entry points (template-stock, Figma-driven, AI-generated, hand-authored, mixed) and how each populates `source_html.pages[]` + `design_source`.
|
|
737
845
|
|
|
738
846
|
## Artifact Locations
|
|
@@ -1114,14 +1222,15 @@ and report proof policy fields above.
|
|
|
1114
1222
|
|
|
1115
1223
|
| Flag | Source | When to use |
|
|
1116
1224
|
| --- | --- | --- |
|
|
1117
|
-
| `--spec <path>` | Local JSON file |
|
|
1118
|
-
| `--map-id <id>` | Map Builder proxy (KV-backed) |
|
|
1225
|
+
| `--spec <path>` | Local JSON file | Agent-authored local specs, saved-Map exports, offline work or CI fixtures |
|
|
1226
|
+
| `--map-id <id>` | Map Builder proxy (KV-backed) | Saved-Map intake from the current KV revision |
|
|
1119
1227
|
|
|
1120
1228
|
When `--map-id <id>` is set, the CLI fetches `GET <proxy>/api/spec/<id>` (default `<proxy>` is `https://campaign-map.nextcommerce.com`) and caches the response to `<target>/.campaign-runtime/fetched-specs/<id>.json`. The cached file is what downstream stages read, so the packet's `spec.local_path` always resolves to an on-disk artifact regardless of intake mode.
|
|
1121
1229
|
|
|
1122
|
-
|
|
1230
|
+
Saved-Map retrieval behavior (`--map-id`):
|
|
1123
1231
|
|
|
1124
1232
|
- **Re-fetch by default.** Every `start` / `prepare-build` invocation re-fetches from KV. KV is the source of truth; the cache file is a debug/inspection artifact, not a performance optimization.
|
|
1233
|
+
- **One writer at a time.** The fetch happens first, but the fetched spec is written to the cache file only once the run holds the per-target prepare-build lock. A second run against the same target, fetching a newer Map revision, waits for the lock before replacing the cache, so the run holding it records the hash of the revision it actually parsed.
|
|
1125
1234
|
- **`--cached-spec`** reuses the cache without a network call. Use for offline iteration or when the proxy is temporarily unreachable.
|
|
1126
1235
|
- **`--proxy-base <url>`** overrides the default origin. Use for staging environments or local Worker dev (`wrangler dev`). Spec retrieval carries no credential, so any reachable origin works here — but the same flag also aims the credential-bearing rails (Run Telemetry remit, QA verdict publish, `telemetry list`), and those require `https:` unless the host is loopback (`localhost`, `127.0.0.1`, `[::1]`), which is allowed over plain http with a stderr warning. A plain-http remote proxy is refused before the request. See docs/workflow-findings-sidecar.md (Remit Channel).
|
|
1127
1236
|
- Failure modes (HTTP error, `{ok: false}` response, network timeout) surface as clean CLI errors before any packet is written.
|
|
@@ -1130,7 +1239,7 @@ The fetched spec is treated identically to a `--spec`-supplied local file from t
|
|
|
1130
1239
|
|
|
1131
1240
|
## Source HTML Manifest Auto-Population
|
|
1132
1241
|
|
|
1133
|
-
When the source HTML root carries a source-html manifest at `<source>/.campaigns-os/source-html-manifest.json` (schema `source-html-manifest/v0`, published at `schemas/source-html-manifest.v0.schema.json`) — or `--design-manifest <path>` names a manifest of that schema anywhere else, for a source root nobody can write to — `campaigns-os prepare-build` reads it and uses its `pages[]` block to populate `packet.source_html.pages[]` directly — bypassing the legacy filesystem-name slug matching. Wherever the manifest lives, its `pages[].path` entries stay relative to `--source`. A `pages[]` entry with `skip_reason` and no `path` declares a template-stock page: its assembly decision carries `template_stock: true` and the locked family, and intake demands no design source for it ([Template-stock pages](design-source-package.md#template-stock-pages-the-family-decides)).
|
|
1242
|
+
When the source HTML root carries a source-html manifest at `<source>/.campaigns-os/source-html-manifest.json` (schema `source-html-manifest/v0`, published at `schemas/source-html-manifest.v0.schema.json`) — or `--design-manifest <path>` names a manifest of that schema anywhere else, for a source root nobody can write to — `campaigns-os prepare-build` reads it and uses its `pages[]` block to populate `packet.source_html.pages[]` directly — bypassing the legacy filesystem-name slug matching. Wherever the manifest lives, its `pages[].path` entries stay relative to `--source`. Each `pages[]` entry carries exactly one of `path` or `skip_reason`: an entry with both is invalid, and an invalid entry makes prepare-build ignore the whole manifest and fall back to filesystem matching. A `pages[]` entry with `skip_reason` and no `path` declares a template-stock page: its assembly decision carries `template_stock: true` and the locked family, and intake demands no design source for it ([Template-stock pages](design-source-package.md#template-stock-pages-the-family-decides)).
|
|
1134
1243
|
|
|
1135
1244
|
The source-html manifest remains a producer/source-HTML adapter input. It is not
|
|
1136
1245
|
renamed into the Design Source Package. In the normalized source workflow,
|
|
@@ -1140,7 +1249,10 @@ contributions, coverage, gaps/TODOs, Surface Identity, references, and readback.
|
|
|
1140
1249
|
When source-html data is the available input and the default package path is
|
|
1141
1250
|
missing, current v0 `prepare-build` synthesizes the package. If a package already
|
|
1142
1251
|
exists, it is validated against the current material inputs and reused byte for
|
|
1143
|
-
byte or refused; it is never silently regenerated.
|
|
1252
|
+
byte or refused; it is never silently regenerated. The one exception is a stale
|
|
1253
|
+
package an earlier `prepare-build` synthesized and nobody has changed since:
|
|
1254
|
+
`--force` regenerates it from the current inputs
|
|
1255
|
+
([Design Source Package: stale packages](design-source-package.md#prepare-build-emit-validate-or-refuse)). Downstream Build and Polish
|
|
1144
1256
|
consume the package concept rather than branching back to
|
|
1145
1257
|
`packet.source_html` as a second source model. The emitted package lives at
|
|
1146
1258
|
`.campaign-runtime/input/design-source-package.json` by default and is referenced
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The happy path is intentionally tight:
|
|
4
4
|
|
|
5
|
-
1.
|
|
5
|
+
1. Use a saved Map export, or have the coding agent author a CampaignSpec from the brief and verified campaign values through the [local-spec entry](build-packet.md#local-spec-entry). Preserve its Map ID or stable `local_spec_id`, plus its public route slug.
|
|
6
6
|
2. Run `campaigns-os start` with the CampaignSpec, prepared source files, target page-kit repo, and template family.
|
|
7
7
|
3. Treat doctor as the first gate. If its `next` block says `doctor-blocked` or `prepare-build` (the same stage names `campaigns-os next` uses), stop and resolve the named blocker.
|
|
8
8
|
4. Run setup when doctor asks for setup; otherwise continue to assembly.
|
|
@@ -11,7 +11,7 @@ The happy path is intentionally tight:
|
|
|
11
11
|
7. Install the package-owned Playwright browser with `npm run qa:install-browser`.
|
|
12
12
|
8. Run polish, serve the current built output, and run `campaigns-os polish capture --packet <packet> --base-url <served-current-build-url>`. Do not mark Polish terminal or begin deploy/QA until this package-owned page-load evidence passes or has an exact finding waiver.
|
|
13
13
|
9. Deploy a preview.
|
|
14
|
-
10. Run `campaigns-os qa resolve
|
|
14
|
+
10. Run `campaigns-os qa resolve --packet <packet>`, then `campaigns-os qa run --packet <packet> --browser --test-order common` with the tested URL. Saved-Map QA uses the existing consent and publication controls; pass `--no-post-verdict` to keep its verdict local. Local-spec QA always keeps verdicts and progress local, even with `--post-verdict`, and `qa publish` refuses local-spec packets. Run Telemetry retains its consent controls.
|
|
15
15
|
11. Treat test-order depth as the control: global test cards bypass the gateway and create no transactions, so no approval is needed. Localhost on any port is a Campaigns App Development domain (SDK allowed, analytics suppressed); non-localhost preview/production origins must still be allowlisted for the campaign API key so the SDK loads.
|
|
16
16
|
12. Promote, block, or iterate from the recorded build, polish, deploy, QA, and test-order evidence.
|
|
17
17
|
|
|
@@ -84,6 +84,7 @@ the assembly report.
|
|
|
84
84
|
- **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
85
|
- **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
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); one default-selected card per selector; single-brace tokens inside SDK templates. Doctor enforces the first four as blockers and the last two as warnings under `built_output.sdk_markup` (codes `SWAP_WITH_ADD_TO_CART`, `CHECKOUT_NOT_FORM`, `WRONG_FIELD_NAME`, `MISSING_SELECTOR_ID_MATCH`, `DOUBLE_SELECTED`, `TEMPLATE_DOUBLE_BRACE`).
|
|
87
|
+
- **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.
|
|
87
88
|
- 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.
|
|
88
89
|
- If source HTML declares SDK-owned zones such as `data-commerce-zone="checkout-form"` or `data-commerce-zone="order-summary"`, adopt the selected starter-template family shell for that runtime page. Do not build a custom checkout/upsell structure around a few borrowed includes; browser QA will check declared family structure where `agentContract.qaStructure` exists.
|
|
89
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.
|
|
@@ -335,14 +335,80 @@ If any current campaign, active/mapped page, source material, manifest or crawl
|
|
|
335
335
|
provenance, coverage, template family/reference, material fingerprint,
|
|
336
336
|
readiness, or readback check fails, `prepare-build` refuses. It leaves the
|
|
337
337
|
existing package and packet/context/report/brief sidecars byte-identical. It
|
|
338
|
-
does not silently regenerate or overwrite the package
|
|
339
|
-
|
|
338
|
+
does not silently regenerate or overwrite the package. The refusal names the
|
|
339
|
+
package file and says which recovery applies:
|
|
340
|
+
|
|
341
|
+
- **A package `prepare-build` synthesized itself.** The Assembly Report's
|
|
342
|
+
`design_source_package` reference records `origin`: `"synthesized"` when
|
|
343
|
+
`prepare-build` wrote the package bytes (or reused bytes it had written), and
|
|
344
|
+
`"adopted"` when it validated and reused a package it did not write. When the
|
|
345
|
+
previous report at this run's report path says `"synthesized"` and the
|
|
346
|
+
package bytes still hash to that report's `sha256`, the stale package is the
|
|
347
|
+
producer's own output: rerun with `--force` and `prepare-build` regenerates
|
|
348
|
+
it from the current inputs (mode `regenerated`), announcing the replacement
|
|
349
|
+
on stderr. `--force` also resets any stage evidence the Assembly Report
|
|
350
|
+
carries, exactly as it does for the report alone.
|
|
351
|
+
- **Any other package** — placed by an operator, edited by hand after it was
|
|
352
|
+
written, adopted by an earlier run, or recorded by a report that predates
|
|
353
|
+
`origin` — is never overwritten, `--force` or not. Reconcile it with the
|
|
354
|
+
current inputs, or, if no downstream stage has consumed it, delete it and
|
|
355
|
+
rerun so `prepare-build` synthesizes a fresh one.
|
|
356
|
+
|
|
357
|
+
Only the Assembly Report carries `origin`; the packet and context references
|
|
358
|
+
keep their four strict fields.
|
|
359
|
+
|
|
360
|
+
The package is published before the report that records it. So that a run
|
|
361
|
+
which fails or dies between the two does not leave its own package unprovable,
|
|
362
|
+
`prepare-build` first writes a pending provenance record beside the package
|
|
363
|
+
(`.campaign-runtime/input/.design-source-package.json.pending-provenance.json`)
|
|
364
|
+
naming the sha256 of the bytes it is about to publish, and removes it once the
|
|
365
|
+
Assembly Report is written. A `--force` run that replaces a stale package it
|
|
366
|
+
proved its own keeps that package's sha256 in the record too, so a
|
|
367
|
+
regeneration that fails or dies part way leaves whichever package is on disk
|
|
368
|
+
provable. A run that finds another writer's package at the path and adopts it
|
|
369
|
+
instead of publishing drops its own candidate from the record. A retry that
|
|
370
|
+
finds the record treats a package whose bytes still hash to one of its entries
|
|
371
|
+
as `"synthesized"`, exactly as if the report had recorded it; bytes changed
|
|
372
|
+
since are not vouched for, and a malformed record vouches for nothing. Just
|
|
373
|
+
before the report is published the record is narrowed to what the report
|
|
374
|
+
records (the package's hash when synthesized, nothing when adopted), so no
|
|
375
|
+
unpublished candidate outlives the report. The record's path is
|
|
376
|
+
reserved like the other outputs, so no configurable output may point at it.
|
|
377
|
+
|
|
378
|
+
Runs against the same target take turns. From the stage-evidence check and
|
|
379
|
+
the reading of its inputs (CampaignSpec, source manifest, page mappings, asset
|
|
380
|
+
crawl) through the packet, context and report that record them,
|
|
381
|
+
`prepare-build` holds a lock directory beside the package
|
|
382
|
+
(`.campaign-runtime/input/.design-source-package.json.lock`), so each run judges
|
|
383
|
+
provenance against the report and package the previous run left, and records
|
|
384
|
+
the inputs it actually read. With `--map-id`, the fetched spec is written to the
|
|
385
|
+
shared `fetched-specs/` cache file inside the same lock, so a run waiting for
|
|
386
|
+
the lock cannot replace the spec the lock holder is reading. The source-html
|
|
387
|
+
manifest is read once: the `manifest_sha256` the package records is the hash of
|
|
388
|
+
the exact bytes source intake parsed. Every command that edits the Assembly Report
|
|
389
|
+
(`doctor`, `qa run`, waivers, the polish merge and the other stage producers)
|
|
390
|
+
takes the same lock for its read-modify-write, so no stage evidence lands
|
|
391
|
+
between `prepare-build`'s final stage-evidence check and its publication; a
|
|
392
|
+
producer that `prepare-build` reaches from inside its own run enters directly.
|
|
393
|
+
Of several concurrent `--force` runs, the first regenerates the package and the
|
|
394
|
+
rest reuse it as `"synthesized"`. A command that finds the lock held waits up to
|
|
395
|
+
a minute. The lock directory and its owner record appear together, so a lock
|
|
396
|
+
left by a process that died is recovered automatically, and a lock directory
|
|
397
|
+
without an owner record (only an older release leaves one) is never taken
|
|
398
|
+
over: the command refuses it after about a second and names it. Confirm no
|
|
399
|
+
campaigns-os process is working on the target, then remove it. Do not run an
|
|
400
|
+
older Campaigns OS release against the same target at the same time. A
|
|
401
|
+
waiver's `--dry-run` preview takes no lock.
|
|
340
402
|
|
|
341
403
|
Before writing any output, `prepare-build` also requires distinct paths for the
|
|
342
404
|
Build Packet, Build Context, Assembly Report, Doctor output, normalized Build
|
|
343
405
|
Brief, and fixed Design Source Package. Equal paths and filesystem aliases are
|
|
344
406
|
rejected, including symlinks, hard links, dangling leaf symlinks, and symlinked
|
|
345
|
-
parent directories.
|
|
407
|
+
parent directories. No output may be placed inside the lock directory, or
|
|
408
|
+
inside the staging and tomb directories the lock creates beside it
|
|
409
|
+
(`.lock.staging-*`, `.lock.recovery-staging-*`, `.lock.released-*`,
|
|
410
|
+
`.lock.abandoned-*`), directly or through a directory alias: they are removed
|
|
411
|
+
with their contents when the run finishes.
|
|
346
412
|
|
|
347
413
|
This behavior is the implemented v0 compatibility boundary. It does not promise
|
|
348
414
|
that a separate future workflow command will generate, repair, approve, or
|
|
@@ -504,10 +570,12 @@ Either declaration records the page on the assembly report's
|
|
|
504
570
|
`template_family` set to the family the packet locks, lists it under
|
|
505
571
|
`stages.prepare_build.declared_out_of_scope`, and reaches
|
|
506
572
|
`stages.prepare_build.status: "completed_partial"`. Intake demands no design
|
|
507
|
-
source for the page: no `capture-*` TODO, no `link-*` TODO.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
573
|
+
source for the page: no `capture-*` TODO, no `link-*` TODO. This source-scope
|
|
574
|
+
classification does **not** authorize publishing a stock page. `next build`
|
|
575
|
+
keeps these routes unbuilt by default and requires explicit operator opt-in
|
|
576
|
+
per page before materializing a stand-in from the locked family. In particular,
|
|
577
|
+
presell and landing pages staying on another host must not be replaced with
|
|
578
|
+
placeholder copy. The family decides only *how* intake records coverage:
|
|
511
579
|
|
|
512
580
|
- **A family that publishes complete Template Reference proof — today `apollo`
|
|
513
581
|
alone** — covers the page with synthesized `template_baseline` coverage from
|
|
@@ -533,10 +601,10 @@ not built yet`, and keeps checkout launch and test-order proof blocked while a
|
|
|
533
601
|
runtime page (`select`, `checkout`, `upsell`, `receipt`) is among them. You get
|
|
534
602
|
a terminal, honest intake — not a fully proven campaign.
|
|
535
603
|
|
|
536
|
-
The build stage lifts those limits page by page.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
604
|
+
The build stage lifts those limits page by page for opted-in pages. It uses
|
|
605
|
+
the locked family's own page of that role (an opted-in pre-checkout `select`
|
|
606
|
+
stand-in first, because it seeds the cart the runtime pages read).
|
|
607
|
+
Once the page's built HTML exists at its route under
|
|
540
608
|
`_site/<slug>/`, doctor reads the `template_stock` marker on the scope decision
|
|
541
609
|
and counts the page as built: it moves into `derived.scope.built_pages` (with
|
|
542
610
|
`template_stock: true`, `template_family`, and no `source_path`), joins the
|
|
@@ -565,10 +633,10 @@ family the `template-baseline` contribution's `presentation_intent` names.
|
|
|
565
633
|
|
|
566
634
|
### Recovery after a blocked first run
|
|
567
635
|
|
|
568
|
-
A blocked run still emits the package, and `prepare-build` never
|
|
569
|
-
package it did not just create. So a first run that blocked leaves a
|
|
570
|
-
whose provenance names the *old* manifest, and simply rerunning with a
|
|
571
|
-
manifest fails closed:
|
|
636
|
+
A blocked run still emits the package, and a plain `prepare-build` never
|
|
637
|
+
refreshes a package it did not just create. So a first run that blocked leaves a
|
|
638
|
+
package whose provenance names the *old* manifest, and simply rerunning with a
|
|
639
|
+
new manifest fails closed:
|
|
572
640
|
|
|
573
641
|
```
|
|
574
642
|
campaigns-os: Design Source Package at <target>/.campaign-runtime/input/design-source-package.json
|
|
@@ -597,6 +665,12 @@ npm run campaigns-os -- start \
|
|
|
597
665
|
--template-family <family>
|
|
598
666
|
```
|
|
599
667
|
|
|
668
|
+
When the refusal says the package was synthesized by an earlier
|
|
669
|
+
`prepare-build` and is unchanged since, skip step 2 and add `--force` to step 3
|
|
670
|
+
instead: `prepare-build` (and `start` and `build`, which run the same intake)
|
|
671
|
+
then regenerates the package itself. `--force` resets any stage evidence the
|
|
672
|
+
Assembly Report carries, so the same caution applies.
|
|
673
|
+
|
|
600
674
|
Step 2 is only ever correct for a package emitted by a blocked run that no
|
|
601
675
|
downstream stage has consumed. Once Build or Polish has bound its evidence to a
|
|
602
676
|
package fingerprint, deleting it invalidates that evidence; reconcile through
|
package/docs/effects.md
CHANGED
|
@@ -9,6 +9,13 @@ The point of the file is not the prose. It is that **every row is proved by a
|
|
|
9
9
|
test** (`src/effects.test.mjs`), and a row without its test cannot be published:
|
|
10
10
|
`npm run check:effects` refuses it.
|
|
11
11
|
|
|
12
|
+
`tooling setup` composes the existing skill/context/browser installers after a
|
|
13
|
+
project-pin and preservation preflight. It also appends a project `CLAUDE.md`
|
|
14
|
+
import. It bypasses session recovery, gateway credential reads and lifecycle
|
|
15
|
+
capture; `--dry-run` is read-only. Like `qa install-browser`, its browser download
|
|
16
|
+
has preflight-only effects proof offline; setup's preservation and recovery
|
|
17
|
+
behavior has focused tests.
|
|
18
|
+
|
|
12
19
|
- The contract: [`contracts/effects.v1.json`](../contracts/effects.v1.json)
|
|
13
20
|
- Its shape: [`schemas/campaigns-os-effects.v1.schema.json`](../schemas/campaigns-os-effects.v1.schema.json)
|
|
14
21
|
- The proof: `src/effects.test.mjs`
|
|
@@ -67,7 +74,7 @@ with one of these:
|
|
|
67
74
|
| `{lifecycle-journal}` | The command-lifecycle journal wherever it was selected for this invocation. |
|
|
68
75
|
| `{proxy-base}` | The endpoint `--proxy-base` names, or the canonical NEXT endpoint when it does not. |
|
|
69
76
|
| `{base-url}` | The campaign under test, as `--base-url` names it or as the packet derives it. |
|
|
70
|
-
| `{playwright-download-host}` | Where Playwright fetches browser builds from: `PLAYWRIGHT_DOWNLOAD_HOST` when set, else the Playwright CDN. The
|
|
77
|
+
| `{playwright-download-host}` | Where Playwright fetches browser builds from: `PLAYWRIGHT_DOWNLOAD_HOST` when set, else the Playwright CDN. The third-party browser download used by `qa install-browser` and `tooling setup`. |
|
|
71
78
|
|
|
72
79
|
The tokens matter because effects are not all under the target. `install-skills`
|
|
73
80
|
writes your **home** directory, not the campaign. `telemetry on` writes your
|
|
@@ -127,7 +134,76 @@ a flag the command rejects up front. It writes nothing, journals nothing, and is
|
|
|
127
134
|
the row to read when you want to know what a typo costs. The one exception is
|
|
128
135
|
declared on the rows it belongs to: `start`, `prepare-build`, `build`,
|
|
129
136
|
`run start` and `run end` close out a **stale** run session at the root they are
|
|
130
|
-
about to act on *before* argv is refused.
|
|
137
|
+
about to act on *before* argv is refused. Commands that implement `--dry-run`
|
|
138
|
+
skip this closeout whenever that flag is present, including a valued flag that
|
|
139
|
+
will be refused: `run end --dry-run yes` writes, sends, and deletes nothing.
|
|
140
|
+
|
|
141
|
+
A refusal is decided by argv alone. When file content or state on disk decides
|
|
142
|
+
the outcome, the command has reached a handler failure and journals it.
|
|
143
|
+
|
|
144
|
+
For intake, run-record, built-site QA, and `next`, argv-only checks run before
|
|
145
|
+
their handler reads the target; invalid values are refused without a journal
|
|
146
|
+
entry. For `start`, `prepare-build`, and `build`, bare, empty, and whitespace-only
|
|
147
|
+
values of `--spec`, `--map-id`, `--source`, `--target`, `--source-kind`,
|
|
148
|
+
`--proxy-base`, `--wrapper-policy`, `--design-manifest`, `--order-path-depth`,
|
|
149
|
+
`--template-family`, `--allow-uncertified-template`, `--theme-policy`, and
|
|
150
|
+
`--brief` are refused before local spec reads, Map fetches, or cache writes on
|
|
151
|
+
the `--spec`, `--map-id`, and `--map-id --cached-spec` paths. So is a
|
|
152
|
+
`--theme-policy` outside `inspect_only`, `auto`, and `off`. Whether a named
|
|
153
|
+
template family is certified, and whether a named brief can be read, depend on
|
|
154
|
+
file content: those checks still run in the handler and are journaled.
|
|
155
|
+
The operator-facing `run-record` and `run end` commands refuse bare, empty, or
|
|
156
|
+
whitespace-only values for every value-taking inherited run-record flag before
|
|
157
|
+
packet work. The five agent
|
|
158
|
+
token and elapsed-time flags retain their non-negative-integer diagnostics;
|
|
159
|
+
`--surfaces` rejects unknown values, and `--dry-run` rejects a value. The
|
|
160
|
+
inherited boolean flags (`--no-remit`, `--no-write`, `--dry-run`, and `--json`)
|
|
161
|
+
retain their bare-flag behavior. `run end` also rejects `--new-run` and
|
|
162
|
+
`--run-id` because the saved session fixes its run ID. `run-record` also
|
|
163
|
+
rejects bare, empty, or whitespace-only `--run-id` and valued `--new-run`.
|
|
164
|
+
Internal stale-session and QA closeouts retain the previous handling of values
|
|
165
|
+
inherited from their invoking commands. A bare, empty, or whitespace-only
|
|
166
|
+
`--proxy-base` on a sweeping command still writes the stale Run Record.
|
|
167
|
+
Terminal QA auto-end tolerates whitespace-only inherited `--context`,
|
|
168
|
+
`--report`, or `--proxy-base`. A whitespace-only `--context` resolves as a
|
|
169
|
+
literal relative path, so the default context file is not read. Bare or empty
|
|
170
|
+
`--context` or `--report` still makes QA auto-end fail and leaves the session
|
|
171
|
+
open; bare or empty `--qa-verdict` fails a Run Record closeout when inherited,
|
|
172
|
+
though QA auto-end supplies its own verdict path. The underlying run-record
|
|
173
|
+
handler still rejects invalid agent
|
|
174
|
+
integers, unknown `--surfaces`, and any valued `--dry-run` that reaches it. QA
|
|
175
|
+
auto-end drops `--dry-run` from inherited flags; if another inherited value
|
|
176
|
+
fails in the handler, auto-end is skipped and the session stays open. QA's own
|
|
177
|
+
journal entry is unaffected because auto-end runs after QA persistence. A named
|
|
178
|
+
`--design-manifest` that is missing or is not a file is checked
|
|
179
|
+
against the filesystem after intake has begun, so that failure is journaled.
|
|
180
|
+
An invalid manifest's contents are likewise a handler failure. A `next` stage
|
|
181
|
+
must be one of the stages in the orchestration stage contract; an unknown name
|
|
182
|
+
is refused before the `next` handler reads the packet, runs doctor, or writes
|
|
183
|
+
doctor output. The ambient run-session lookup in `main()` may read a named
|
|
184
|
+
`--packet` before the handler runs.
|
|
185
|
+
|
|
186
|
+
`polish capture --packet` would report "polish capture requires
|
|
187
|
+
packet.assembly.target_repo to resolve to a local target repo" as a journaled
|
|
188
|
+
handler failure because packet content would decide it. Today the workspace
|
|
189
|
+
resolver always yields a local path, so this check does not fire through the
|
|
190
|
+
CLI. `run end` reports "run end needs a build packet" as a journaled handler failure
|
|
191
|
+
when the saved session has no packet and argv names none. For `qa run` and `qa
|
|
192
|
+
resolve`, "QA requires a Map ID" is a refusal when argv carries no non-empty
|
|
193
|
+
`--packet`, `--site`, `--built`, positional Map ID, or `--map-id` value. A selector
|
|
194
|
+
flag without a value is refused with "Missing value for --<flag>". If a named
|
|
195
|
+
packet yields neither a Map ID nor a valid local-spec identity after checkpoint
|
|
196
|
+
preflight reads the packet, spec, and report, the requirement is a journaled
|
|
197
|
+
handler failure. A conflicting local/Map identity is also a handler failure.
|
|
198
|
+
When `qa run` selects `--legacy-api-test-order`, a missing, bare, empty, or
|
|
199
|
+
unusable `--cart` and an unknown legacy mode are argv-only refusals before QA
|
|
200
|
+
input resolution. They append no lifecycle entry. Accepted modes remain
|
|
201
|
+
`accept`, `decline`, and `both` (case-insensitive); browser `--test-order` still
|
|
202
|
+
takes precedence and does not require the legacy cart. API credentials are
|
|
203
|
+
still checked only inside the legacy handler and failures there are journaled.
|
|
204
|
+
The nested run-record refusal scope in session closeout guards against future
|
|
205
|
+
changes. No internal closeout can currently create a refusal before its
|
|
206
|
+
invoking command journals.
|
|
131
207
|
|
|
132
208
|
## How a row is proved
|
|
133
209
|
|
|
@@ -279,3 +355,8 @@ reason. What it may not be is silent.
|
|
|
279
355
|
Change the effect, change the row, in the same PR. The effect test will tell you
|
|
280
356
|
which row is wrong before review does: it names the path that moved and the row
|
|
281
357
|
that failed to declare it.
|
|
358
|
+
|
|
359
|
+
Local-spec QA retains its artifacts locally. It never sends a verdict or progress
|
|
360
|
+
to the Map portal, even when `--post-verdict` is supplied; `qa publish` refuses
|
|
361
|
+
local-spec packets. Commerce API reads, served-page probes and requested typed-card
|
|
362
|
+
orders keep their existing effects. Run Telemetry still follows its consent controls.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Local campaign setup
|
|
2
|
+
|
|
3
|
+
For a new campaign, choose its working folder and run this from that folder:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.43.2 next-campaign-page-kit@0.2.0 && npx --no-install campaigns-os tooling setup --target . --platform claude
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Review the release source/provenance before installation as described in
|
|
10
|
+
`AGENTS.md`. npm installs the dependencies first; `--no-install` then runs only
|
|
11
|
+
the project's installed CLI. Keep `package.json` and `package-lock.json` in
|
|
12
|
+
Git. For an existing project, preserve its reviewed pin: run `npm ci`, then
|
|
13
|
+
`npx --no-install campaigns-os tooling setup --target . --platform claude`
|
|
14
|
+
on a release that supports setup. Changing the pin is a separate update.
|
|
15
|
+
|
|
16
|
+
Setup checks the exact toolkit pin, its lockfile version and the installed
|
|
17
|
+
page-kit dependency before it changes files. It composes the existing
|
|
18
|
+
installers to:
|
|
19
|
+
|
|
20
|
+
1. Install the QA browser through this toolkit's own Playwright package.
|
|
21
|
+
2. Install the bundled skills into `~/.claude/skills` (same-name skills are
|
|
22
|
+
refreshed just as with `install-skills`).
|
|
23
|
+
3. Install the four context files under `.campaign-runtime/agent-context`
|
|
24
|
+
and the managed runtime ignore block.
|
|
25
|
+
4. Append one import to the project's `CLAUDE.md`, preserving existing text.
|
|
26
|
+
|
|
27
|
+
The import uses Claude Code's documented
|
|
28
|
+
[`@path` syntax](https://code.claude.com/docs/en/memory#import-additional-files).
|
|
29
|
+
Existing context that differs from the bundle and symlink destinations require
|
|
30
|
+
reconciliation before setup; setup does not overwrite them. A repeated run
|
|
31
|
+
preserves campaign pages, authored decisions and project instructions. If the
|
|
32
|
+
browser download fails, fix that error and rerun setup; shared skills and
|
|
33
|
+
project files have not been changed. If the runtime ignore block cannot be
|
|
34
|
+
written, setup reports `context_install_failed`; fix `.gitignore` and rerun.
|
|
35
|
+
`--dry-run --json` previews setup without any writes or browser download.
|
|
36
|
+
|
|
37
|
+
Restart Claude Code in the campaign folder. Use the `next-campaigns-os` skill
|
|
38
|
+
and provide the configured campaign details, HTML/assets and brief. The agent
|
|
39
|
+
authors a local CampaignSpec if there is no saved Map export; follow the
|
|
40
|
+
[local-spec entry](build-packet.md#local-spec-entry). The skill checks its
|
|
41
|
+
loaded bundle revision against the project copy.
|
|
42
|
+
`restart_required` means the files are installed; it does not prove that the
|
|
43
|
+
running agent has loaded them. Check Claude's `/context` view if the project
|
|
44
|
+
instructions are missing.
|
|
45
|
+
|
|
46
|
+
Setup does not scaffold template pages, create a CampaignSpec, connect the
|
|
47
|
+
gateway, change a saved Map, run a campaign session, remit telemetry, or prove
|
|
48
|
+
checkout. The agent performs intake and chooses the template before assembly.
|
|
49
|
+
A local spec uses `spec_identity.local_spec_id` and keeps its evidence in the
|
|
50
|
+
repository. Existing doctor/QA gates still apply. This entry is Claude Code first; other agents retain their existing
|
|
51
|
+
manual installation path.
|