@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
|
@@ -55,12 +55,17 @@ contract. A packet found only at
|
|
|
55
55
|
remedy; conformance does not silently widen discovery.
|
|
56
56
|
|
|
57
57
|
The checker validates canonical paths, declared schema versions, strict UTC
|
|
58
|
-
timestamps, cross-artifact Map ID, public slug, campaign directory, live URL
|
|
58
|
+
timestamps, cross-artifact Map ID or local-spec ID, public slug, campaign directory, live URL
|
|
59
59
|
path, template family, and spec identity, doctor freshness, and the URL/order-
|
|
60
60
|
free QA projection. Safe repository-relative spellings such as
|
|
61
61
|
`campaign-runtime.build.json` and `./campaign-runtime.build.json` are
|
|
62
62
|
equivalent; absolute paths, URIs, backslashes, and parent traversal are not.
|
|
63
63
|
|
|
64
|
+
Local-spec bundles compare `local_spec_id` across the packet, report, doctor
|
|
65
|
+
output and QA sidecar. Their Map IDs remain null; the QA verdict's
|
|
66
|
+
`campaign_slug` is the storage key `local-spec-<local_spec_id>`. Mixing local
|
|
67
|
+
and saved-Map identities fails conformance; a shared public route is not enough.
|
|
68
|
+
|
|
64
69
|
Spec identity has two deliberately separate meanings. Build Context
|
|
65
70
|
`spec.hash` and Assembly Report `identity.spec_hash` retain exact raw-byte
|
|
66
71
|
integrity. Build Context `spec.material_hash`, Assembly Report
|
|
@@ -25,7 +25,7 @@ Ledger schema id: `campaigns-os-release-ledger/v1`
|
|
|
25
25
|
Change policy version: `1.0.0`
|
|
26
26
|
Reason-code vocabulary version: `1.0.0`
|
|
27
27
|
Limits version: `1.0.0`
|
|
28
|
-
Supported surface at generation time: `1.
|
|
28
|
+
Supported surface at generation time: `1.43.2`
|
|
29
29
|
|
|
30
30
|
## Forward compatibility
|
|
31
31
|
|
|
@@ -59,12 +59,16 @@ across them. A progress stream is independent of a run-session ID.
|
|
|
59
59
|
|
|
60
60
|
Sanitized immutable snapshots are written under the target repository's
|
|
61
61
|
`.campaign-runtime/progress/` before any request. Allocation uses an exclusive
|
|
62
|
-
local lock with a process owner
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
local lock with a process owner; the lock directory and its owner record are
|
|
63
|
+
published together in one rename, so a lock never exists without its owner.
|
|
64
|
+
Dead owners are recovered through an exclusive recovery claim and an atomic
|
|
65
|
+
rename; a live process is never evicted. A lock directory with no owner record
|
|
66
|
+
(left by an older release) is never taken over: capture refuses it after about
|
|
67
|
+
a second. If one is found, or if recovery itself is interrupted, capture fails
|
|
68
|
+
closed and the warning names the affected `.allocation-lock` directory: stop
|
|
69
|
+
all Campaigns OS writers for that target, then remove that directory before
|
|
70
|
+
retrying `next`. Do not remove a lock while a writer is active. Do not run an
|
|
71
|
+
older Campaigns OS release against the same target at the same time.
|
|
68
72
|
|
|
69
73
|
An unchanged projection reuses its ID, timestamp and sequence. Identity
|
|
70
74
|
changes start a new stream. Each local scope retains at most 32 snapshots and
|
|
@@ -140,3 +144,9 @@ The planned immutable receiver key is
|
|
|
140
144
|
revision. A key match identifies scope; it is not authentication or trust. The
|
|
141
145
|
receiver must verify the digest and authorized Map scope and stamp its own trust.
|
|
142
146
|
Unknown, incomplete or conflicted histories must never yield a ready workspace.
|
|
147
|
+
|
|
148
|
+
Local-spec packets add optional `identity.local_spec_id`. Report binding compares
|
|
149
|
+
that ID and the local material hash, so local stages can be observed without a
|
|
150
|
+
saved Map. `map_id` and `map_revision_hash` remain null and
|
|
151
|
+
`saved_revision_alignment` remains `unconfirmed`; these observations stay on disk
|
|
152
|
+
with `map_id_missing` and have no portal storage key.
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# QA And Test Orders
|
|
2
2
|
|
|
3
|
+
For packet-based partial builds, QA honors the Assembly Report's recorded
|
|
4
|
+
`stages.prepare_build.declared_out_of_scope` declarations together with the
|
|
5
|
+
packet's skip mappings. Unbuilt declared pages emit `skipped` evidence with
|
|
6
|
+
reason `out_of_build_scope`; HTTP, browser and commercial checks do not request
|
|
7
|
+
those routes, and entry URLs come from the remaining pages. A materialized
|
|
8
|
+
stock page rejoins QA. Missing in-scope pages still fail normally. A raw skip
|
|
9
|
+
mapping without a recorded declaration does not suppress checks.
|
|
10
|
+
|
|
3
11
|
The public v0 QA runner is Node/npm-based and does not require access to a private runtime repo.
|
|
4
12
|
|
|
5
13
|
> **Commerce QA requires network; it cannot run in a no-outbound sandbox.** The SDK, product images, fonts, the Netlify preview, and the Playwright typed-card test order all need outbound network. A build environment without it can only validate markup/build/CSS — the commerce runtime and the typed-card test order (the Campaigns OS control) must be deferred to a deployed preview. Always run the QA runner against a `--base-url` preview/production origin (e.g. `npm run campaigns-os -- qa run --packet campaign-runtime.build.json --base-url https://deploy-preview-7--your-site.netlify.app/ --browser --test-order common`); never report commerce-runtime QA as passed from an offline build.
|
|
@@ -98,6 +106,11 @@ npm run campaigns-os -- qa resolve --packet campaign-runtime.build.json
|
|
|
98
106
|
|
|
99
107
|
Resolve reads the packet, loads the local CampaignSpec when available, derives deployed page URLs from the packet deploy URL or `--base-url`, probes the entry URLs it derived, and prints the funnel topology. It does not create a verdict.
|
|
100
108
|
|
|
109
|
+
Local-spec QA requires `--packet` and matching `local_spec_id` values in the
|
|
110
|
+
packet, CampaignSpec and Assembly Report, with a current report material hash.
|
|
111
|
+
A Map ID override is refused. See the [local-spec entry](build-packet.md#local-spec-entry)
|
|
112
|
+
for preparation and identity rules.
|
|
113
|
+
|
|
101
114
|
### Route reachability
|
|
102
115
|
|
|
103
116
|
A route set derived from the packet is not evidence that the deployment serves
|
|
@@ -289,7 +302,7 @@ npm run campaigns-os -- qa run \
|
|
|
289
302
|
--base-url https://preview.example.com/campaign/
|
|
290
303
|
```
|
|
291
304
|
|
|
292
|
-
The runner fetches deployed pages, checks route availability, verifies CampaignSpec `sdk_hints.meta_tags` (a key the Campaign Cart SDK does not read, `next-currency` or `next-predictive-address` from `src/sdk-meta-tags.mjs`, is a `warn` row at severity `warn` carrying the shared note, never `manual_review` and never a blocker, whether or not the tag rendered; doctor reports the same key as `sdk_hints.meta_tags.ignored_by_sdk`), writes a local verdict JSON under `<target-repo>/qa-output/<
|
|
305
|
+
The runner fetches deployed pages, checks route availability, verifies CampaignSpec `sdk_hints.meta_tags` (a key the Campaign Cart SDK does not read, `next-currency` or `next-predictive-address` from `src/sdk-meta-tags.mjs`, is a `warn` row at severity `warn` carrying the shared note, never `manual_review` and never a blocker, whether or not the tag rendered; doctor reports the same key as `sdk_hints.meta_tags.ignored_by_sdk`), writes a local verdict JSON under `<target-repo>/qa-output/<qa-storage-key>/<run-id>.json` (the packet's `assembly.target_repo`, else the packet's directory; `--output-dir` overrides it, and a packet-less run uses `qa-output/` under the current directory), and returns exit code `4` when the verdict is blocked. The storage key is the Map ID for saved-Map QA and `local-spec-<local_spec_id>` for local-spec packet QA. The target's managed ignore block lists `qa-output/`, because full verdicts carry live storefront URLs; the committed form is the `.campaign-runtime/qa-verdict.json` projection.
|
|
293
306
|
|
|
294
307
|
### Automatic commercial parity
|
|
295
308
|
|
|
@@ -353,7 +366,7 @@ nothing is ever selected by mtime or "latest":
|
|
|
353
366
|
```bash
|
|
354
367
|
campaigns-os qa promote \
|
|
355
368
|
--packet campaign-runtime.build.json \
|
|
356
|
-
--verdict qa-output/<
|
|
369
|
+
--verdict qa-output/<qa-storage-key>/<run-id>.json \
|
|
357
370
|
--json
|
|
358
371
|
```
|
|
359
372
|
|
|
@@ -381,10 +394,12 @@ Report's `identity.spec_material_hash` and the Build Context's
|
|
|
381
394
|
raw-byte digest of the spec file (the two meanings are deliberate; see
|
|
382
395
|
[docs/migration-sidecar-bundle.md](./migration-sidecar-bundle.md)).
|
|
383
396
|
`campaign_ref_id` is copied from the CampaignSpec's `campaign.ref_id` and
|
|
384
|
-
identifies the platform campaign
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
`
|
|
397
|
+
identifies the configured platform campaign, not this build: two specs for one
|
|
398
|
+
platform campaign share it by design, and it is `null` when the spec carries
|
|
399
|
+
none. `campaign_slug` carries the Map ID for saved-Map verdicts. Local-spec
|
|
400
|
+
verdicts carry explicit `local_spec_id` and use
|
|
401
|
+
`campaign_slug: "local-spec-<local_spec_id>"`; both survive sidecar projection.
|
|
402
|
+
`public_route_slug` is the route, not a substitute for either stable identity.
|
|
388
403
|
|
|
389
404
|
**Trust is stamped by the receiver, never by this CLI.** The QA portal
|
|
390
405
|
receiver accepts verdict posts publicly (after shape/size/rate checks) and
|
|
@@ -512,7 +527,8 @@ Exactly one comparison, against exactly one earlier run:
|
|
|
512
527
|
|
|
513
528
|
1. **Find the previous run.** The most recent Run Record under the Build
|
|
514
529
|
Packet's `.campaign-runtime/run-records/` whose `identity.map_id` matches
|
|
515
|
-
this
|
|
530
|
+
this saved Map, or whose `identity.local_spec_id` matches this local spec
|
|
531
|
+
with no Map ID. Only the first match counts — walking further back to find a
|
|
516
532
|
record that happens to carry usable evidence would compare this run against
|
|
517
533
|
a non-adjacent one and report anything introduced in between as
|
|
518
534
|
pre-existing.
|
|
@@ -624,7 +640,7 @@ QA evidence redacts checkout request bodies and generated QA emails. Verdict art
|
|
|
624
640
|
keep method, URL, response summaries, order refs, line-item summaries, and card last4,
|
|
625
641
|
but they should not contain full customer address/payment payloads.
|
|
626
642
|
|
|
627
|
-
QA runs **publish to the QA portal by default** — they appear in the Campaign Map
|
|
643
|
+
Saved-Map QA runs **publish to the QA portal by default** — they appear in the Campaign Map
|
|
628
644
|
QA tab and the run picker, and the command prints the portal link. No flag needed.
|
|
629
645
|
Pass `--no-post-verdict` (or `--local-only`) for offline / dev / CI runs that should
|
|
630
646
|
stay local-only; publishing never fails the QA run if the portal is unreachable.
|
|
@@ -636,7 +652,14 @@ local-only, and the output names the destination plus the opt-in
|
|
|
636
652
|
(`--post-verdict`, or `campaigns-os telemetry on`). Portal-managed campaigns —
|
|
637
653
|
spec resolved from the portal for the run — keep publish-by-default regardless
|
|
638
654
|
of consent: those verdicts are the QA tab's product surface, not telemetry.
|
|
639
|
-
Explicit flags always win in both directions.
|
|
655
|
+
Explicit flags always win in both directions for saved-Map QA.
|
|
656
|
+
|
|
657
|
+
A packet with `local_spec_id` always keeps its verdict and progress local.
|
|
658
|
+
`qa run` suppresses portal publication even with `--post-verdict`; the flag
|
|
659
|
+
does not turn a local ID into a Map destination. `qa publish` refuses such a
|
|
660
|
+
packet with `refusal.code: local_spec`, including under `--dry-run` or
|
|
661
|
+
`--republish`. Commerce reads, served-page probes and requested typed-card
|
|
662
|
+
orders still run; Run Telemetry follows its existing consent controls.
|
|
640
663
|
|
|
641
664
|
```bash
|
|
642
665
|
npm run campaigns-os -- qa run \
|
|
@@ -646,7 +669,7 @@ npm run campaigns-os -- qa run \
|
|
|
646
669
|
|
|
647
670
|
### Publish a stored verdict (`qa publish`)
|
|
648
671
|
|
|
649
|
-
"
|
|
672
|
+
For saved-Map packets, "run local, publish when clean" is one command, not a rerun. A run kept local
|
|
650
673
|
with `--no-post-verdict` writes the same full verdict under `qa-output/` and
|
|
651
674
|
the same committed sidecar as a publishing run; `qa publish` posts that stored
|
|
652
675
|
verdict to the QA portal through the rail `qa run` uses, without re-running
|
|
@@ -678,6 +701,7 @@ order placed — with a named `refusal.code`:
|
|
|
678
701
|
|
|
679
702
|
| `refusal.code` | What it means |
|
|
680
703
|
|---|---|
|
|
704
|
+
| `local_spec` | The packet has `local_spec_id` and no saved Map destination. Keep the verdict local; moving to a saved Map requires fresh preparation and evidence. |
|
|
681
705
|
| `spec_hash_mismatch` | The verdict's `spec_hash` is not the packet's current spec (`spec.local_path`, hashed the way every spec-identity check hashes it, so `sha256:` prefix and case do not matter). A verdict for a spec that has since changed is not evidence about the current one: re-run `qa run`, which publishes by default. The result carries both hashes. |
|
|
682
706
|
| `spec_hash_absent` | The verdict carries no `spec_hash`. Re-run `qa run`. |
|
|
683
707
|
| `already_published` | The run's Run Record records this verdict's `run_id` as published (by the run itself, or by an earlier `qa publish`). Pass `--republish` to post it again; the existing portal link is in the output either way. |
|
|
@@ -777,9 +801,12 @@ retire that guard once #36 ships and `cartLines` is populated.
|
|
|
777
801
|
Analytics correctness has two deliberately separate evidence phases in one QA
|
|
778
802
|
run:
|
|
779
803
|
|
|
780
|
-
1. The
|
|
781
|
-
and other observable tags
|
|
782
|
-
|
|
804
|
+
1. The inventory visit inventories declared providers, containers, pixels,
|
|
805
|
+
and other observable tags on one page: the campaign root, or a built entry
|
|
806
|
+
when the root cannot be captured (see
|
|
807
|
+
[Which page the inventory captures](#which-page-the-inventory-captures)).
|
|
808
|
+
It does not prove or disprove Purchase, even if a stray Purchase-shaped
|
|
809
|
+
event appears there.
|
|
783
810
|
2. The existing canonical typed-card order run supplies Purchase evidence. For
|
|
784
811
|
each planned order, the topology classifier must recognize the final URL as
|
|
785
812
|
that plan's receipt, then the runner waits the full `--analytics-settle`
|
|
@@ -823,6 +850,98 @@ analytics block to gate. The SDK's own data layer is a separate, always-on
|
|
|
823
850
|
reading taken on the same order — see [Purchase data layer](#purchase-data-layer-dl_purchase)
|
|
824
851
|
under Test Orders.
|
|
825
852
|
|
|
853
|
+
### Which page the inventory captures
|
|
854
|
+
|
|
855
|
+
The inventory starts at the campaign root composed from the campaign identity
|
|
856
|
+
(`public_route_slug` plus `route_root`). A partial build (a topology with a
|
|
857
|
+
partial build scope, or pages excluded from the build) may have no page there:
|
|
858
|
+
the root is then whatever the host answers, such as a directory index or a
|
|
859
|
+
generic fallback. So the root is visited only when it is in scope. On a full
|
|
860
|
+
build it always is; on a partial build it is in scope only when a built,
|
|
861
|
+
in-scope topology page is served at the root. When the root is out of scope,
|
|
862
|
+
answers non-2xx, or fails to load (a timeout, a refused connection), the leg
|
|
863
|
+
tries each funnel's built entry in turn: the first in-scope page on a partial
|
|
864
|
+
build (the same entry the partial-scope planner selects), otherwise the first
|
|
865
|
+
entry-like page. It captures the first page that answers 2xx. A response with no
|
|
866
|
+
HTTP status counts as an answer. A closed page or a disconnected browser is not
|
|
867
|
+
a per-page failure: it is the `analytics-correctness:runner` blocker.
|
|
868
|
+
|
|
869
|
+
`analytics-correctness:capture` records the page it used:
|
|
870
|
+
`evidence.capture_page` holds `url` (the URL requested, query redacted),
|
|
871
|
+
`source` (`campaign_root` or `built_entry`), `page_id`, `funnel_id` and
|
|
872
|
+
`http_status`. `evidence.final_url` is the page URL after redirects and
|
|
873
|
+
settling. When a built entry was used, `evidence.root_fallback` says why the
|
|
874
|
+
root was not: `reason` is `out_of_built_scope`, `non_2xx` (with the root's
|
|
875
|
+
`http_status`), or `navigation_error` (with its `error_code`).
|
|
876
|
+
|
|
877
|
+
When no page is captured, the leg emits `analytics-correctness:capture` alone,
|
|
878
|
+
with no per-vendor assertion measured against an empty page. There are two
|
|
879
|
+
outcomes, and `evidence.attempts` lists each page tried:
|
|
880
|
+
|
|
881
|
+
| `evidence.reason` | When | Result |
|
|
882
|
+
|---|---|---|
|
|
883
|
+
| `no_in_scope_page_captured` | The root is out of the built scope and no built entry other than the root is left to try, so nothing was loaded | `skipped`: there is no page whose tags could be measured |
|
|
884
|
+
| `no_capture_page_answered` | At least one page was tried, and every one answered non-2xx or failed to load | `FAIL`/`BLOCKER`: the declared analytics went unmeasured, so a later passing order cannot report the run ready |
|
|
885
|
+
|
|
886
|
+
Step routing is path-based in every certified family. Page-kit builds each
|
|
887
|
+
page to its own `<route>/index.html`, and QA strips the query string from a
|
|
888
|
+
CampaignSpec route. So `/campaign`, `/campaign/` and `/campaign/index.html` are
|
|
889
|
+
one page, and a query string does not name a different page. A topology page
|
|
890
|
+
whose own URL declares a query (for example `/campaign/?step=checkout`) is
|
|
891
|
+
still never merged into the root on its path alone. Unless its query is
|
|
892
|
+
exactly the root's own (parameter order aside), it does not put the root in
|
|
893
|
+
scope, it is captured as its own entry, and its `capture_page` carries
|
|
894
|
+
`query_routed: true`, since the redacted URL alone would read as the root. An
|
|
895
|
+
entry on a different path is never marked `query_routed`, whatever query it
|
|
896
|
+
carries. A URL with no query of its own names the page at that path whatever
|
|
897
|
+
query the other URL carries.
|
|
898
|
+
|
|
899
|
+
### Local-serve review (`manual_review`)
|
|
900
|
+
|
|
901
|
+
A local proof run renders the development environment on purpose (see
|
|
902
|
+
[Local proof mode](#local-proof-mode-deploytarget-local-serve)), and the
|
|
903
|
+
starter templates gate every vendor loader out of that render. A pixel that
|
|
904
|
+
did not fire there is the render's design, not a campaign defect. So on a
|
|
905
|
+
local-serve run, a failing fire-dependent check becomes `manual_review` at
|
|
906
|
+
`warn` instead of a blocker. The fire-dependent checks are
|
|
907
|
+
`analytics-correctness:tag:*`, `analytics-correctness:oob:*` and
|
|
908
|
+
`analytics-correctness:purchase-fires`.
|
|
909
|
+
|
|
910
|
+
The run qualifies only when all of these hold:
|
|
911
|
+
|
|
912
|
+
- the packet's `deploy.target` is `local-serve`;
|
|
913
|
+
- the analytics capture target (else the base URL) is a loopback URL;
|
|
914
|
+
- the Assembly Report records the development render:
|
|
915
|
+
`stages.assembly.evidence.build_environment` is `development`. A production
|
|
916
|
+
build served on localhost, or a build whose environment was never recorded,
|
|
917
|
+
keeps its blockers.
|
|
918
|
+
|
|
919
|
+
Each failing check is then downgraded only when the page it measured is on
|
|
920
|
+
record as loopback. For `tag:*` and `oob:*`, the check's own URL, the passing
|
|
921
|
+
capture's `capture_page.url` and its `final_url` must all be loopback, so a
|
|
922
|
+
built-entry fallback on a remote host, or a localhost root that redirected to
|
|
923
|
+
a production host, keeps the blocker. For `purchase-fires`, there must be at
|
|
924
|
+
least one judged receipt, and every one needs a loopback `receipt_url` and
|
|
925
|
+
`receipt_document_url` (the page URL read after the receipt analytics
|
|
926
|
+
settled). A missing or unparseable URL keeps the blocker.
|
|
927
|
+
|
|
928
|
+
What always stays a blocker:
|
|
929
|
+
|
|
930
|
+
- `analytics-correctness:data-layer-purchase:<path>`. It counts the SDK's own
|
|
931
|
+
`dl_purchase`, which the development render still pushes, so a miss on
|
|
932
|
+
localhost can be a real defect.
|
|
933
|
+
- A capture or runner failure: `analytics-correctness:runner`, a check whose
|
|
934
|
+
evidence carries an `error_code`, and a `purchase-fires` reading whose
|
|
935
|
+
`capture_error_plan_ids` is missing or not empty. The environment explains a
|
|
936
|
+
silent pixel, not an unmeasured one.
|
|
937
|
+
|
|
938
|
+
A downgraded check keeps its evidence and adds `reason:
|
|
939
|
+
local_serve_development_render`, `local_serve_status: fail`, the recorded
|
|
940
|
+
`build_environment`, the recorded `production_parity` (`status:
|
|
941
|
+
not_recorded` when none is on the report, with a note unless it passed), and a
|
|
942
|
+
`follow_up`: re-run `qa run` against the PR preview (a production render) with
|
|
943
|
+
`--base-url <preview-url>`. That run gates these checks.
|
|
944
|
+
|
|
826
945
|
## Analytics parity (dataLayer / GTM)
|
|
827
946
|
|
|
828
947
|
The analytics-parity leg proves the live **dataLayer event stream + GTM/pixel
|
|
@@ -842,14 +961,22 @@ npm run campaigns-os -- qa run \
|
|
|
842
961
|
```
|
|
843
962
|
|
|
844
963
|
This receipt-to-receipt parity example names `--analytics-candidate`
|
|
845
|
-
explicitly. When that flag is omitted, the
|
|
846
|
-
|
|
847
|
-
|
|
964
|
+
explicitly, and that URL is captured as given. When that flag is omitted, the
|
|
965
|
+
candidate is chosen the same way as the correctness inventory (see
|
|
966
|
+
[Which page the inventory captures](#which-page-the-inventory-captures)): the
|
|
967
|
+
campaign identity's composed root (`public_route_slug` plus `route_root`), not
|
|
968
|
+
the raw `--base-url` value, when it is in scope and answers 2xx, else the first
|
|
969
|
+
built entry that does. `analytics-parity:capture` then records the same
|
|
970
|
+
`capture_page` and `root_fallback` evidence. The candidate is captured before
|
|
971
|
+
the baseline, and when no candidate page is captured the leg emits
|
|
972
|
+
`analytics-parity:capture` alone, without loading the baseline:
|
|
973
|
+
`no_in_scope_page_captured` (skipped) or `no_capture_page_answered`
|
|
974
|
+
(`FAIL`/`BLOCKER`).
|
|
848
975
|
|
|
849
976
|
| Flag | Meaning |
|
|
850
977
|
|---|---|
|
|
851
978
|
| `--analytics-baseline <url>` | Legacy funnel URL to capture as the parity baseline (enables the leg) |
|
|
852
|
-
| `--analytics-candidate <url>` | Migrated URL to capture; defaults to the identity-composed campaign root |
|
|
979
|
+
| `--analytics-candidate <url>` | Migrated URL to capture as given; defaults to the identity-composed campaign root, or the first built entry when that root is out of the built scope or does not answer |
|
|
853
980
|
| `--analytics-hosts a,b` | Extra host substrings to treat as analytics tag-fires (Everflow is built in) |
|
|
854
981
|
| `--analytics-settle <ms>` | Wait after analytics page loads and after a recognized typed-order receipt for async tags to fire (default 5000); receipt settling must fit inside the order deadline |
|
|
855
982
|
|
|
@@ -1674,6 +1801,19 @@ review. Nested Google Maps/payment keys and inert HTML do not count as campaign
|
|
|
1674
1801
|
credentials. This small static grammar deliberately leaves many real pages
|
|
1675
1802
|
unknown; a literal inside arbitrary code is not proof of effective configuration.
|
|
1676
1803
|
|
|
1804
|
+
A page script that does not parse is not treated as dynamic. The browser throws
|
|
1805
|
+
a `SyntaxError` on it and nothing in it runs, so the binding reads its
|
|
1806
|
+
declarations as unavailable (`script_unavailable_or_limit`) and QA adds a
|
|
1807
|
+
separate `script-parse:<page_id>` blocker in the same `api-metadata` family.
|
|
1808
|
+
Its `actual` names each script by path (inline scripts as `inline script`)
|
|
1809
|
+
with the line and column, and its evidence lists a fixed diagnostic category
|
|
1810
|
+
per script, never text from the script. Classic scripts are parsed as scripts
|
|
1811
|
+
and `type="module"` scripts as modules, with the type stripped of surrounding
|
|
1812
|
+
ASCII whitespace and compared case-insensitively as the browser does; classic
|
|
1813
|
+
`nomodule` scripts are not fetched or parsed (a module script ignores
|
|
1814
|
+
`nomodule` and is parsed), and script srcs resolve against the page's first `<base href>`. Doctor runs the same parse over the built output before deploy; see
|
|
1815
|
+
`built_output.script_syntax` in [the Build Packet doc](build-packet.md).
|
|
1816
|
+
|
|
1677
1817
|
External executable scripts other than the recognized jsDelivr Campaign Cart
|
|
1678
1818
|
loader/index are inspected only on the page's origin. Each page admits at most
|
|
1679
1819
|
6 such references; each run fetches at most 24 distinct URLs (deduplicated),
|
|
@@ -118,7 +118,8 @@ be recorded without the bytes actually moving somewhere.
|
|
|
118
118
|
### Fixes that touch only policy-ignored paths
|
|
119
119
|
|
|
120
120
|
A fix living entirely in paths the policy ignores — `src/` other than
|
|
121
|
-
`src/cli.mjs`, `scripts/`, tests and
|
|
121
|
+
`src/cli.mjs`, `src/agent/` and `src/doctor/`, `scripts/`, tests and
|
|
122
|
+
fixtures — carries a same-surface CHANGELOG
|
|
122
123
|
section (`X.Y.Z+agent.N`) and **no ledger entry**. There is nothing for an entry
|
|
123
124
|
to claim: every change item must map to a classified changed path in the range,
|
|
124
125
|
and an ignored path is never classified, so an entry written for such a PR is
|
|
@@ -127,9 +128,10 @@ path-less item fails the same way, because no classified change of its class
|
|
|
127
128
|
exists in the range. The ignore list and its stated reasons are in
|
|
128
129
|
[`contracts/agent-relevant-change-policy.v1.json`](../contracts/agent-relevant-change-policy.v1.json).
|
|
129
130
|
|
|
130
|
-
The
|
|
131
|
-
as `cli_surface`, so any change
|
|
132
|
-
|
|
131
|
+
The classified paths inside `src/` are `src/cli.mjs`, `src/agent/` and
|
|
132
|
+
`src/doctor/`: an explicit rule classifies each as `cli_surface`, so any change
|
|
133
|
+
there is agent-relevant and owes an entry, even when the behaviour change
|
|
134
|
+
originates in a helper module beside it.
|
|
133
135
|
|
|
134
136
|
### Amendments
|
|
135
137
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
How a checkout of this repository at one commit becomes a usable installed runtime, and how a consumer decides whether a prepared one is still trustworthy. Everything below is generated from `contracts/runtime-recipe.campaigns-os-node-v1.json`, which is the only authority for these values.
|
|
10
10
|
|
|
11
|
-
Recipe kind `campaigns-os-node-v1`, revision `1.0.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.
|
|
11
|
+
Recipe kind `campaigns-os-node-v1`, revision `1.0.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.43.2`.
|
|
12
12
|
|
|
13
13
|
## What this is
|
|
14
14
|
|
package/docs/skills-revision.md
CHANGED
|
@@ -16,7 +16,7 @@ that the copy on disk moved.
|
|
|
16
16
|
`skills.json` carries one top-level field:
|
|
17
17
|
|
|
18
18
|
```json
|
|
19
|
-
"bundle_revision": "1.
|
|
19
|
+
"bundle_revision": "1.43.2+skills.1"
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
The spelling is `<package version>+skills.<n>`:
|
|
@@ -25,7 +25,7 @@ The spelling is `<package version>+skills.<n>`:
|
|
|
25
25
|
skills ship with (`check-skill-versions.mjs` fails if the two disagree);
|
|
26
26
|
- `<n>` is a plain counter, not a semver component. It says "this is the *n*th
|
|
27
27
|
skill-text revision published against that package version" and it **resets
|
|
28
|
-
with the prefix**. `1.
|
|
28
|
+
with the prefix**. `1.43.2+skills.1` is therefore ahead of `1.40.0+skills.7`.
|
|
29
29
|
|
|
30
30
|
It is one identity for the bundle as a whole, on purpose. Per-skill versions
|
|
31
31
|
still exist and still gate per-skill changes, but an agent that loaded one skill
|
|
@@ -37,7 +37,7 @@ The first body line of every bundled `SKILL.md`, immediately after the
|
|
|
37
37
|
frontmatter, is exactly:
|
|
38
38
|
|
|
39
39
|
```
|
|
40
|
-
Bundle revision: 1.
|
|
40
|
+
Bundle revision: 1.43.2+skills.1
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
followed by a short paragraph telling the agent to run the check below at the
|
|
@@ -48,7 +48,7 @@ text the agent is actually reading, not from a file it would have to go and open
|
|
|
48
48
|
## The check
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
51
|
+
npx --no-install campaigns-os tooling status --skills-revision 1.43.2+skills.1
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
The value is compared against the bundle revision of the **CLI the command runs
|
|
@@ -90,20 +90,20 @@ reports the choice as `skills.scope` (`requested`, `installed_platforms`, or
|
|
|
90
90
|
"revision_check": "match",
|
|
91
91
|
"skills_revision": {
|
|
92
92
|
"status": "match",
|
|
93
|
-
"requested": "1.
|
|
93
|
+
"requested": "1.43.2+skills.1",
|
|
94
94
|
"spelling": "bundle",
|
|
95
|
-
"on_disk": "1.
|
|
95
|
+
"on_disk": "1.43.2+skills.1",
|
|
96
96
|
"on_disk_skill": null,
|
|
97
|
-
"message": "match (1.
|
|
97
|
+
"message": "match (1.43.2+skills.1)"
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
The text view prints one named line, as a header above the rest of the status:
|
|
102
102
|
|
|
103
103
|
```
|
|
104
|
-
Skills revision: match (1.
|
|
105
|
-
Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.
|
|
106
|
-
Skills revision: unchecked (on disk 1.
|
|
104
|
+
Skills revision: match (1.43.2+skills.1)
|
|
105
|
+
Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.43.2+skills.1 — start a fresh session
|
|
106
|
+
Skills revision: unchecked (on disk 1.43.2+skills.1)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
`unchecked` is the state when the flag is absent. It is not an error — an
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextcommerce/campaigns-os",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.43.2",
|
|
4
4
|
"description": "Toolkit for agent-assisted NEXT campaign builds.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -149,6 +149,7 @@
|
|
|
149
149
|
"docs/diagnostics.md",
|
|
150
150
|
"docs/progress-snapshots.md",
|
|
151
151
|
"demo",
|
|
152
|
-
"docs/demo-preview.md"
|
|
152
|
+
"docs/demo-preview.md",
|
|
153
|
+
"docs/local-setup.md"
|
|
153
154
|
]
|
|
154
155
|
}
|
|
@@ -28,8 +28,13 @@
|
|
|
28
28
|
"type": "object",
|
|
29
29
|
"additionalProperties": true,
|
|
30
30
|
"required": ["map_id", "public_route_slug", "campaign_directory", "live_url_path", "spec_hash"],
|
|
31
|
+
"oneOf": [
|
|
32
|
+
{ "properties": { "map_id": { "type": "string", "minLength": 1 } }, "not": { "required": ["local_spec_id"] } },
|
|
33
|
+
{ "required": ["local_spec_id", "spec_material_hash"], "properties": { "map_id": { "type": "null" } } }
|
|
34
|
+
],
|
|
31
35
|
"properties": {
|
|
32
|
-
"
|
|
36
|
+
"local_spec_id": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$", "description": "Stable repository-owned CampaignSpec identity; never a saved Map ID. Keep unchanged across local revisions." },
|
|
37
|
+
"map_id": { "type": ["string", "null"], "minLength": 1 },
|
|
33
38
|
"public_route_slug": { "type": "string", "minLength": 1 },
|
|
34
39
|
"campaign_directory": { "type": "string", "minLength": 1 },
|
|
35
40
|
"live_url_path": { "type": "string", "minLength": 1 },
|
|
@@ -66,9 +66,14 @@
|
|
|
66
66
|
"type": "object",
|
|
67
67
|
"additionalProperties": false,
|
|
68
68
|
"required": ["map_id"],
|
|
69
|
+
"oneOf": [
|
|
70
|
+
{ "properties": { "map_id": { "type": "string", "minLength": 1 } }, "not": { "required": ["local_spec_id"] } },
|
|
71
|
+
{ "required": ["local_spec_id", "local_path"], "properties": { "map_id": { "type": "null" }, "local_path": { "type": "string", "minLength": 1 }, "spec_url": { "type": "null" } } }
|
|
72
|
+
],
|
|
69
73
|
"properties": {
|
|
74
|
+
"local_spec_id": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$", "description": "Stable repository-owned CampaignSpec identity; never a saved Map ID. Keep unchanged across local revisions." },
|
|
70
75
|
"map_id": {
|
|
71
|
-
"type": "string",
|
|
76
|
+
"type": ["string", "null"],
|
|
72
77
|
"minLength": 1,
|
|
73
78
|
"description": "Saved Map Builder identity used for reopen, /api/spec/<map-id>, QA verdicts, and provenance."
|
|
74
79
|
},
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": true,
|
|
8
8
|
"required": ["schema_version", "funnels"],
|
|
9
|
+
"if": { "required": ["spec_identity"], "properties": { "spec_identity": { "required": ["local_spec_id"] } } },
|
|
10
|
+
"then": { "not": { "required": ["map_id"] } },
|
|
9
11
|
"properties": {
|
|
10
12
|
"schema_version": {
|
|
11
13
|
"enum": ["4.2", "4.3"],
|
|
@@ -364,8 +366,10 @@
|
|
|
364
366
|
"specIdentity": {
|
|
365
367
|
"type": "object",
|
|
366
368
|
"additionalProperties": true,
|
|
369
|
+
"not": { "required": ["map_id", "local_spec_id"] },
|
|
367
370
|
"properties": {
|
|
368
371
|
"source": { "type": "string", "description": "Observed values: campaign-map-builder, hand-authored-simulation, local-experimental." },
|
|
372
|
+
"local_spec_id": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$", "description": "Stable repository-owned CampaignSpec identity; never a saved Map ID. Keep unchanged across local revisions." },
|
|
369
373
|
"map_id": { "type": "string" },
|
|
370
374
|
"id": { "type": "string" },
|
|
371
375
|
"map_url": { "type": "string" },
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"description": "Same literal as the full verdict — the sidecar is the same contract projected, not a second schema lineage."
|
|
30
30
|
},
|
|
31
31
|
"run_id": { "type": "string", "minLength": 1 },
|
|
32
|
+
"local_spec_id": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$", "description": "Stable repository-owned CampaignSpec identity; never a saved Map ID. Keep unchanged across local revisions." },
|
|
32
33
|
"campaign_slug": {
|
|
33
34
|
"type": "string",
|
|
34
35
|
"minLength": 1,
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"minLength": 1,
|
|
30
30
|
"description": "The QA run identity, also the verdict filename under qa-output/<map-id>/. The portal receiver additionally restricts it to [A-Za-z0-9_-]{1,64}."
|
|
31
31
|
},
|
|
32
|
+
"local_spec_id": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$", "description": "Stable repository-owned CampaignSpec identity; never a saved Map ID. Keep unchanged across local revisions." },
|
|
32
33
|
"campaign_slug": {
|
|
33
34
|
"type": "string",
|
|
34
35
|
"minLength": 1,
|
|
@@ -155,6 +155,7 @@
|
|
|
155
155
|
"additionalProperties": false,
|
|
156
156
|
"description": "Best-effort run identity — the join keys that make telemetry useful. Missing identity never blocks capture.",
|
|
157
157
|
"properties": {
|
|
158
|
+
"local_spec_id": { "type": ["string", "null"], "pattern": "^[A-Za-z0-9_-]{1,64}$", "description": "Stable repository-owned CampaignSpec identity; never a saved Map ID. Keep unchanged across local revisions. Null or omitted means no local identity." },
|
|
158
159
|
"map_id": { "type": ["string", "null"] },
|
|
159
160
|
"campaign_slug": { "type": ["string", "null"] },
|
|
160
161
|
"template_family": { "type": ["string", "null"] },
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: campaign-lifecycle-orientation
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.9
|
|
4
4
|
description: Orient a reader to the Campaigns OS lifecycle artifacts a run has already emitted, without advancing any stage or changing any state.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.43.2+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.43.2+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|
|
@@ -70,9 +70,13 @@ output sidecar, theme evidence and normalized inputs
|
|
|
70
70
|
separate system, and QA then tests a deployed URL
|
|
71
71
|
(`docs/qa-and-test-orders.md`).
|
|
72
72
|
|
|
73
|
-
Keep
|
|
74
|
-
and keys QA evidence storage
|
|
75
|
-
|
|
73
|
+
Keep stable campaign identity separate from routing. The **Map ID** identifies
|
|
74
|
+
a saved campaign map and keys its QA evidence storage. A local-spec packet
|
|
75
|
+
instead carries **local_spec_id**, keeps `map_id` null, and stores QA under
|
|
76
|
+
`local-spec-<local_spec_id>`. The **public route slug** is the shopper-facing
|
|
77
|
+
path segment. Read the packet's identity kind and route separately; a shared
|
|
78
|
+
route cannot make evidence from another local spec belong to this campaign
|
|
79
|
+
(`docs/build-packet.md`, "Local-spec entry").
|
|
76
80
|
|
|
77
81
|
Campaign pages are typed. The page-type vocabulary is `presell`, `landing`,
|
|
78
82
|
`select`, `checkout`, `upsell`, `downsell` and `thankyou`; `select` is where a
|
|
@@ -95,8 +99,9 @@ only from implementation files this skill may not cite, so do not branch on one.
|
|
|
95
99
|
## Read the stage record
|
|
96
100
|
|
|
97
101
|
`campaigns-os next --packet <packet> --json` (tier `A`: it captures a progress
|
|
98
|
-
snapshot under the target and, under Run Telemetry
|
|
99
|
-
observation off the machine
|
|
102
|
+
snapshot under the target and, for saved-Map packets under Run Telemetry
|
|
103
|
+
consent, can POST that observation off the machine; local-spec observations
|
|
104
|
+
stay local) reads the recorded state and names the next
|
|
100
105
|
incomplete stage among `setup`, `build`, `polish`, `deploy` and `qa`. That is a
|
|
101
106
|
writing, potentially sending command. If all you need is where the run stands,
|
|
102
107
|
prefer the readback below.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: campaign-readback-classification
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.9
|
|
4
4
|
description: Classify a selected campaign from the readback projection's v2 fields and write a read-only handoff without turning diagnosis into permission.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Bundle revision: 1.
|
|
8
|
-
Run `npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
7
|
+
Bundle revision: 1.43.2+skills.1
|
|
8
|
+
Run `npx --no-install campaigns-os tooling status --skills-revision 1.43.2+skills.1`
|
|
9
9
|
from the campaign's Page Kit folder, where it runs the project's pinned copy and
|
|
10
10
|
never installs one, at the start of each task. Start a fresh session if it
|
|
11
11
|
reports `mismatch`: this text is already in your context and is never re-read
|