@nextcommerce/campaigns-os 1.43.1 → 1.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -2
- package/CHANGELOG.md +1099 -5103
- package/README.md +34 -13
- package/agents/claude/CLAUDE.md +1 -1
- package/agents/codex/AGENTS.md +1 -1
- package/agents/copilot/copilot-instructions.md +1 -1
- package/agents/cursor/campaigns-os.mdc +1 -1
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +2 -2
- package/campaign-spec/dist/rules/analytics-contract-shape.js +2 -2
- package/campaign-spec/dist/rules/store-profile-shape.d.ts +5 -1
- package/campaign-spec/dist/rules/store-profile-shape.js +8 -10
- package/campaign-spec/dist/types.d.ts +2 -2
- package/contracts/agent-relevant-change-policy.v1.json +5 -0
- package/contracts/archive/CHANGELOG.2026-09-30.md +5111 -0
- package/contracts/archive/release-ledger.2026-09-30.json +5068 -0
- package/contracts/effects.v1.json +1184 -121
- package/contracts/orientation-reason-codes.v1.json +7 -0
- package/contracts/release-ledger.json +2190 -5260
- package/contracts/supported-surface.json +7 -4
- package/contracts/template-slot-manifest.shared-content-core.v0.json +403 -0
- package/docs/brand-theme-bridge.md +81 -0
- package/docs/build-packet.md +222 -23
- package/docs/campaigns-os-build-flow.md +4 -3
- package/docs/design-source-package.md +162 -15
- package/docs/effects.md +66 -12
- package/docs/gateway-login.md +3 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +42 -2
- package/docs/polish-evidence.md +74 -0
- package/docs/progress-snapshots.md +10 -6
- package/docs/qa-and-test-orders.md +230 -20
- package/docs/release-ledger-authoring-guide.md +70 -8
- package/docs/runtime-readiness.md +1 -1
- package/docs/sdk-storage-compatibility.md +1 -1
- package/docs/skills-revision.md +10 -10
- package/docs/supported-surface.md +2 -2
- package/docs/versioning.md +4 -1
- package/package.json +1 -1
- package/schemas/campaigns-os-release-ledger.v1.schema.json +32 -2
- package/schemas/campaigns-os-tooling-orientation.v1.schema.json +1 -0
- package/skills/campaign-lifecycle-orientation/SKILL.md +16 -5
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +7 -6
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +7 -6
- package/skills/next-campaigns-os/SKILL.md +7 -7
- package/skills/next-campaigns-os/references/session-intake.md +9 -3
- package/skills/next-campaigns-os-setup/SKILL.md +5 -5
- package/skills/next-campaigns-polish/SKILL.md +28 -9
- package/skills/next-campaigns-qa/SKILL.md +7 -4
- package/skills.json +10 -10
- package/src/brand-theme.mjs +320 -20
- package/src/build-brief.mjs +6 -4
- package/src/built-script-syntax.mjs +480 -0
- package/src/built-site-scope.mjs +16 -4
- package/src/campaigns-api-key.mjs +99 -0
- package/src/cli-helpers.mjs +118 -0
- package/src/cli.mjs +1530 -7580
- package/src/commercial-parity.mjs +48 -2
- package/src/design-source-package.mjs +1 -1
- package/src/design-source-publication.mjs +898 -0
- package/src/deviation.mjs +13 -1
- package/src/diagnostic.mjs +6 -2
- package/src/directory-lock.mjs +270 -0
- package/src/doctor/checks.mjs +4654 -0
- package/src/doctor/inspect.mjs +678 -0
- package/src/doctor/next-step.mjs +731 -0
- package/src/doctor/source-provenance.mjs +184 -0
- package/src/install-invocation.mjs +29 -0
- package/src/invocation.mjs +183 -0
- package/src/live-campaign-refs.mjs +466 -0
- package/src/login.mjs +2 -2
- package/src/page-kit-store-profile.mjs +69 -12
- package/src/page-kit-sync.mjs +31 -12
- package/src/private-template-source.mjs +1 -1
- package/src/progress-node.mjs +9 -36
- 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 +1316 -105
- package/src/qa-build-scope.mjs +47 -0
- package/src/qa-commercial-parity.mjs +48 -5
- package/src/qa-node.mjs +339 -19
- package/src/qa-test-order-topology.mjs +148 -0
- package/src/sdk-markup.mjs +72 -8
- package/src/source-html-intake.mjs +117 -1
- package/src/source-html-manifest.mjs +9 -2
- package/src/stage-ledger.mjs +28 -0
- package/src/stage-record.mjs +551 -0
- package/src/target-lock.mjs +54 -0
- package/src/template-brand-contract.mjs +17 -1
- package/src/upsell-selector-scope.mjs +112 -2
|
@@ -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.
|
|
@@ -238,12 +246,18 @@ before QA with the relevant gate ID:
|
|
|
238
246
|
```bash
|
|
239
247
|
campaigns-os checkpoint waive \
|
|
240
248
|
--packet campaign-runtime.build.json \
|
|
241
|
-
--gate <page_kit.store_profile|page_kit.sdk_version|polish.hidden_eager_media|built_output.upsell_selector_scope> \
|
|
249
|
+
--gate <page_kit.store_profile|page_kit.sdk_version|polish.hidden_eager_media|built_output.upsell_selector_scope|source_html.producer_provenance> \
|
|
250
|
+
[--page <page_id>] \
|
|
242
251
|
--reason "<why>" \
|
|
243
252
|
--waived-by "<named human>" \
|
|
244
253
|
--review-condition "<specific re-evaluation trigger>"
|
|
245
254
|
```
|
|
246
255
|
|
|
256
|
+
`--page` is required for `source_html.producer_provenance`, which is waived one
|
|
257
|
+
Figma-typed page at a time (see the
|
|
258
|
+
[Design Source Package](./design-source-package.md) hand-written HTML route),
|
|
259
|
+
and refused for the other gates.
|
|
260
|
+
|
|
247
261
|
Legacy source/theme/QA waiver commands and artifact lanes remain in place until
|
|
248
262
|
those gates are registered. Store Profile evidence includes only the governed
|
|
249
263
|
nine-field matrix plus status, normalized slug, relative target path,
|
|
@@ -424,7 +438,8 @@ work that lands with the receiver's connection contract. This section
|
|
|
424
438
|
documents the semantics of the stamps the receiver already applies.
|
|
425
439
|
|
|
426
440
|
Add `--browser --test-order common` for the normal proof pass: first-party
|
|
427
|
-
Playwright browser checks plus the default typed-card
|
|
441
|
+
Playwright browser checks plus the default `common` typed-card orders
|
|
442
|
+
(described under "Test Orders" below). If the
|
|
428
443
|
browser binary is missing, the CLI will prompt you to run
|
|
429
444
|
`npm run qa:install-browser`:
|
|
430
445
|
|
|
@@ -444,6 +459,30 @@ machine-checkable `agentContract.qaStructure` selectors in the commerce surface
|
|
|
444
459
|
catalog. If the family contract is silent, the assertion returns
|
|
445
460
|
`manual_review`, not `pass`; if declared required structure is missing, it
|
|
446
461
|
soft-fails with warning severity so the verdict becomes `ready_with_exceptions`.
|
|
462
|
+
The checkout wrapper and page composition are source-owned, so QA checks what
|
|
463
|
+
the checkout does rather than family class names. Family shell is a fixed list:
|
|
464
|
+
`.checkout-wrapper`, `.checkout-layout__left`, `.checkout-layout__right`,
|
|
465
|
+
`.checkout__layout`, `.checkout__column--left`, `.checkout__column--right` and
|
|
466
|
+
the family include's `[data-next-component="shipping-field-row"]` marker. When
|
|
467
|
+
family shell is all that is missing, the row is `warn` if the checkout also
|
|
468
|
+
passes three behaviour checks: a `<form data-next-checkout="form">` exists;
|
|
469
|
+
`email`, `fname`, `lname`, `country`, `address1`, `city`, `province` and
|
|
470
|
+
`postal` are each an input, select or textarea carrying that
|
|
471
|
+
`data-next-checkout-field` inside the form, not a `type="hidden"` input or a
|
|
472
|
+
disabled control (visibility is not required, so a field hidden until a country
|
|
473
|
+
is chosen still counts); and a cart-summary total
|
|
474
|
+
(`[data-next-display="cart.total"]` or
|
|
475
|
+
`[data-next-cart-summary] .order-totals__value--total`) is visible with text.
|
|
476
|
+
If any behaviour check fails, or no checkout form is found, the row stays
|
|
477
|
+
`fail`. Any other missing selector is always `fail`: the SDK selectors
|
|
478
|
+
(`[data-next-checkout="form"]`, `[os-checkout-payment]`,
|
|
479
|
+
`[data-next-cart-summary]`, `[data-next-bundle-slots-for]`) and any class the
|
|
480
|
+
list does not name, such as a hosted payment field class. `evidence.behaviour` records
|
|
481
|
+
the three checks and each `evidence.checks[]` entry carries `kind`
|
|
482
|
+
(`family_shell` or `sdk_wiring`).
|
|
483
|
+
The upsell and checkout bundle price checks count the SDK's
|
|
484
|
+
`[data-next-bundle-display*='price']` alongside the contract's price rows; a
|
|
485
|
+
hidden, zero-size or empty bundle-display node does not count.
|
|
447
486
|
Promoted template families must also have
|
|
448
487
|
`contracts/template-brand-contract.<family>.v0.json`; QA emits a blocker if the
|
|
449
488
|
selected family is missing its brand/residue/pricing contract instead of
|
|
@@ -793,9 +832,12 @@ retire that guard once #36 ships and `cartLines` is populated.
|
|
|
793
832
|
Analytics correctness has two deliberately separate evidence phases in one QA
|
|
794
833
|
run:
|
|
795
834
|
|
|
796
|
-
1. The
|
|
797
|
-
and other observable tags
|
|
798
|
-
|
|
835
|
+
1. The inventory visit inventories declared providers, containers, pixels,
|
|
836
|
+
and other observable tags on one page: the campaign root, or a built entry
|
|
837
|
+
when the root cannot be captured (see
|
|
838
|
+
[Which page the inventory captures](#which-page-the-inventory-captures)).
|
|
839
|
+
It does not prove or disprove Purchase, even if a stray Purchase-shaped
|
|
840
|
+
event appears there.
|
|
799
841
|
2. The existing canonical typed-card order run supplies Purchase evidence. For
|
|
800
842
|
each planned order, the topology classifier must recognize the final URL as
|
|
801
843
|
that plan's receipt, then the runner waits the full `--analytics-settle`
|
|
@@ -839,6 +881,98 @@ analytics block to gate. The SDK's own data layer is a separate, always-on
|
|
|
839
881
|
reading taken on the same order — see [Purchase data layer](#purchase-data-layer-dl_purchase)
|
|
840
882
|
under Test Orders.
|
|
841
883
|
|
|
884
|
+
### Which page the inventory captures
|
|
885
|
+
|
|
886
|
+
The inventory starts at the campaign root composed from the campaign identity
|
|
887
|
+
(`public_route_slug` plus `route_root`). A partial build (a topology with a
|
|
888
|
+
partial build scope, or pages excluded from the build) may have no page there:
|
|
889
|
+
the root is then whatever the host answers, such as a directory index or a
|
|
890
|
+
generic fallback. So the root is visited only when it is in scope. On a full
|
|
891
|
+
build it always is; on a partial build it is in scope only when a built,
|
|
892
|
+
in-scope topology page is served at the root. When the root is out of scope,
|
|
893
|
+
answers non-2xx, or fails to load (a timeout, a refused connection), the leg
|
|
894
|
+
tries each funnel's built entry in turn: the first in-scope page on a partial
|
|
895
|
+
build (the same entry the partial-scope planner selects), otherwise the first
|
|
896
|
+
entry-like page. It captures the first page that answers 2xx. A response with no
|
|
897
|
+
HTTP status counts as an answer. A closed page or a disconnected browser is not
|
|
898
|
+
a per-page failure: it is the `analytics-correctness:runner` blocker.
|
|
899
|
+
|
|
900
|
+
`analytics-correctness:capture` records the page it used:
|
|
901
|
+
`evidence.capture_page` holds `url` (the URL requested, query redacted),
|
|
902
|
+
`source` (`campaign_root` or `built_entry`), `page_id`, `funnel_id` and
|
|
903
|
+
`http_status`. `evidence.final_url` is the page URL after redirects and
|
|
904
|
+
settling. When a built entry was used, `evidence.root_fallback` says why the
|
|
905
|
+
root was not: `reason` is `out_of_built_scope`, `non_2xx` (with the root's
|
|
906
|
+
`http_status`), or `navigation_error` (with its `error_code`).
|
|
907
|
+
|
|
908
|
+
When no page is captured, the leg emits `analytics-correctness:capture` alone,
|
|
909
|
+
with no per-vendor assertion measured against an empty page. There are two
|
|
910
|
+
outcomes, and `evidence.attempts` lists each page tried:
|
|
911
|
+
|
|
912
|
+
| `evidence.reason` | When | Result |
|
|
913
|
+
|---|---|---|
|
|
914
|
+
| `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 |
|
|
915
|
+
| `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 |
|
|
916
|
+
|
|
917
|
+
Step routing is path-based in every certified family. Page-kit builds each
|
|
918
|
+
page to its own `<route>/index.html`, and QA strips the query string from a
|
|
919
|
+
CampaignSpec route. So `/campaign`, `/campaign/` and `/campaign/index.html` are
|
|
920
|
+
one page, and a query string does not name a different page. A topology page
|
|
921
|
+
whose own URL declares a query (for example `/campaign/?step=checkout`) is
|
|
922
|
+
still never merged into the root on its path alone. Unless its query is
|
|
923
|
+
exactly the root's own (parameter order aside), it does not put the root in
|
|
924
|
+
scope, it is captured as its own entry, and its `capture_page` carries
|
|
925
|
+
`query_routed: true`, since the redacted URL alone would read as the root. An
|
|
926
|
+
entry on a different path is never marked `query_routed`, whatever query it
|
|
927
|
+
carries. A URL with no query of its own names the page at that path whatever
|
|
928
|
+
query the other URL carries.
|
|
929
|
+
|
|
930
|
+
### Local-serve review (`manual_review`)
|
|
931
|
+
|
|
932
|
+
A local proof run renders the development environment on purpose (see
|
|
933
|
+
[Local proof mode](#local-proof-mode-deploytarget-local-serve)), and the
|
|
934
|
+
starter templates gate every vendor loader out of that render. A pixel that
|
|
935
|
+
did not fire there is the render's design, not a campaign defect. So on a
|
|
936
|
+
local-serve run, a failing fire-dependent check becomes `manual_review` at
|
|
937
|
+
`warn` instead of a blocker. The fire-dependent checks are
|
|
938
|
+
`analytics-correctness:tag:*`, `analytics-correctness:oob:*` and
|
|
939
|
+
`analytics-correctness:purchase-fires`.
|
|
940
|
+
|
|
941
|
+
The run qualifies only when all of these hold:
|
|
942
|
+
|
|
943
|
+
- the packet's `deploy.target` is `local-serve`;
|
|
944
|
+
- the analytics capture target (else the base URL) is a loopback URL;
|
|
945
|
+
- the Assembly Report records the development render:
|
|
946
|
+
`stages.assembly.evidence.build_environment` is `development`. A production
|
|
947
|
+
build served on localhost, or a build whose environment was never recorded,
|
|
948
|
+
keeps its blockers.
|
|
949
|
+
|
|
950
|
+
Each failing check is then downgraded only when the page it measured is on
|
|
951
|
+
record as loopback. For `tag:*` and `oob:*`, the check's own URL, the passing
|
|
952
|
+
capture's `capture_page.url` and its `final_url` must all be loopback, so a
|
|
953
|
+
built-entry fallback on a remote host, or a localhost root that redirected to
|
|
954
|
+
a production host, keeps the blocker. For `purchase-fires`, there must be at
|
|
955
|
+
least one judged receipt, and every one needs a loopback `receipt_url` and
|
|
956
|
+
`receipt_document_url` (the page URL read after the receipt analytics
|
|
957
|
+
settled). A missing or unparseable URL keeps the blocker.
|
|
958
|
+
|
|
959
|
+
What always stays a blocker:
|
|
960
|
+
|
|
961
|
+
- `analytics-correctness:data-layer-purchase:<path>`. It counts the SDK's own
|
|
962
|
+
`dl_purchase`, which the development render still pushes, so a miss on
|
|
963
|
+
localhost can be a real defect.
|
|
964
|
+
- A capture or runner failure: `analytics-correctness:runner`, a check whose
|
|
965
|
+
evidence carries an `error_code`, and a `purchase-fires` reading whose
|
|
966
|
+
`capture_error_plan_ids` is missing or not empty. The environment explains a
|
|
967
|
+
silent pixel, not an unmeasured one.
|
|
968
|
+
|
|
969
|
+
A downgraded check keeps its evidence and adds `reason:
|
|
970
|
+
local_serve_development_render`, `local_serve_status: fail`, the recorded
|
|
971
|
+
`build_environment`, the recorded `production_parity` (`status:
|
|
972
|
+
not_recorded` when none is on the report, with a note unless it passed), and a
|
|
973
|
+
`follow_up`: re-run `qa run` against the PR preview (a production render) with
|
|
974
|
+
`--base-url <preview-url>`. That run gates these checks.
|
|
975
|
+
|
|
842
976
|
## Analytics parity (dataLayer / GTM)
|
|
843
977
|
|
|
844
978
|
The analytics-parity leg proves the live **dataLayer event stream + GTM/pixel
|
|
@@ -858,14 +992,22 @@ npm run campaigns-os -- qa run \
|
|
|
858
992
|
```
|
|
859
993
|
|
|
860
994
|
This receipt-to-receipt parity example names `--analytics-candidate`
|
|
861
|
-
explicitly. When that flag is omitted, the
|
|
862
|
-
|
|
863
|
-
|
|
995
|
+
explicitly, and that URL is captured as given. When that flag is omitted, the
|
|
996
|
+
candidate is chosen the same way as the correctness inventory (see
|
|
997
|
+
[Which page the inventory captures](#which-page-the-inventory-captures)): the
|
|
998
|
+
campaign identity's composed root (`public_route_slug` plus `route_root`), not
|
|
999
|
+
the raw `--base-url` value, when it is in scope and answers 2xx, else the first
|
|
1000
|
+
built entry that does. `analytics-parity:capture` then records the same
|
|
1001
|
+
`capture_page` and `root_fallback` evidence. The candidate is captured before
|
|
1002
|
+
the baseline, and when no candidate page is captured the leg emits
|
|
1003
|
+
`analytics-parity:capture` alone, without loading the baseline:
|
|
1004
|
+
`no_in_scope_page_captured` (skipped) or `no_capture_page_answered`
|
|
1005
|
+
(`FAIL`/`BLOCKER`).
|
|
864
1006
|
|
|
865
1007
|
| Flag | Meaning |
|
|
866
1008
|
|---|---|
|
|
867
1009
|
| `--analytics-baseline <url>` | Legacy funnel URL to capture as the parity baseline (enables the leg) |
|
|
868
|
-
| `--analytics-candidate <url>` | Migrated URL to capture; defaults to the identity-composed campaign root |
|
|
1010
|
+
| `--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 |
|
|
869
1011
|
| `--analytics-hosts a,b` | Extra host substrings to treat as analytics tag-fires (Everflow is built in) |
|
|
870
1012
|
| `--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 |
|
|
871
1013
|
|
|
@@ -996,13 +1138,64 @@ npm run campaigns-os -- qa run \
|
|
|
996
1138
|
--test-order common
|
|
997
1139
|
```
|
|
998
1140
|
|
|
999
|
-
The default mode is **`common`** (also what bare `--test-order` runs)
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1141
|
+
The default mode is **`common`** (also what bare `--test-order` runs). It
|
|
1142
|
+
starts from the selected checkout's declared topology:
|
|
1143
|
+
|
|
1144
|
+
- When every actual terminal path (the `full` set, checkout baseline included)
|
|
1145
|
+
fits under the flood cap (`--max-test-orders`, default `6`), `common` runs
|
|
1146
|
+
them all. The run says so on stderr, and on a funnel with offer pages the
|
|
1147
|
+
coverage row below records
|
|
1148
|
+
`order_path_depth_effective: "full"` with `order_path_depth_reason:
|
|
1149
|
+
"under_cap"`.
|
|
1150
|
+
- Above the cap, or when the topology cannot be walked exhaustively, `common`
|
|
1151
|
+
runs the sample: the checkout baseline, first-offer `accept` and `decline`
|
|
1152
|
+
when `expected_next_url` reaches an upsell/downsell, and the shortest
|
|
1153
|
+
declared path that actually reaches a receipt/thank-you page (deduplicated
|
|
1154
|
+
when it is already `accept` or `decline`; Campaigns OS never invents a
|
|
1155
|
+
receipt path from offer count alone). It then adds, for each offer or
|
|
1156
|
+
downsell page whose decline no planned path clicks yet, the shortest actual
|
|
1157
|
+
terminal path that clicks it, until the plan reaches the cap. Pages still
|
|
1158
|
+
left out are named on stderr and in the coverage row. The sample itself is
|
|
1159
|
+
never trimmed, so a `--max-test-orders` below it is refused before launch as
|
|
1160
|
+
before.
|
|
1161
|
+
|
|
1162
|
+
A page counts as covered only when a path clicks its **decline** control.
|
|
1163
|
+
Reaching a page, or clicking only its accept, does not count: a broken decline
|
|
1164
|
+
link strands the shopper even when accept works.
|
|
1165
|
+
|
|
1166
|
+
Every browser test-order run whose funnels include offer pages records
|
|
1167
|
+
`browser-test-order:upsell-action-coverage`, read from the clicks the runner
|
|
1168
|
+
actually made in orders it placed, not from the plan. It lists the offer pages
|
|
1169
|
+
of every funnel in the run, including funnels the orders do not drive, and a
|
|
1170
|
+
click credits only the funnel whose order made it, never another funnel's page
|
|
1171
|
+
at the same URL. The row is conservative: it is `pass` or `warn` only when
|
|
1172
|
+
coverage is certain, and `manual_review` in every other case.
|
|
1173
|
+
|
|
1174
|
+
Coverage is certain when all of these hold: at least one order was placed;
|
|
1175
|
+
every funnel lists its pages; every page is either a known non-offer type
|
|
1176
|
+
(checkout, landing, thank-you and the like) or an upsell/downsell page with its
|
|
1177
|
+
own absolute http(s) URL that no other page shares; every planned order belongs
|
|
1178
|
+
to exactly one funnel (its checkout is that funnel's checkout and it was
|
|
1179
|
+
planned over that funnel's page list); and every click an order recorded lands
|
|
1180
|
+
on a declared offer page of that order's own funnel. Then the row is `warn`
|
|
1181
|
+
(severity `warn`) naming each page whose decline no order of its funnel
|
|
1182
|
+
clicked, and each funnel no order ran through, or `pass` when every page's
|
|
1183
|
+
decline was clicked.
|
|
1184
|
+
|
|
1185
|
+
Otherwise the row is `manual_review` (severity `warn`) naming the pages not
|
|
1186
|
+
proved clicked through, with `evidence.reason`: `test_orders_off` for a
|
|
1187
|
+
`--test-order off` run, `no_order_recorded` when no order was placed (an
|
|
1188
|
+
attempt that failed before an order reference counts as none), `no_topology`
|
|
1189
|
+
when no funnel topology reached the check, or `not_assessable` for the rest.
|
|
1190
|
+
`evidence.not_assessable[]` names each page that cannot be matched to a click,
|
|
1191
|
+
with a reason: `no_url` or `unresolvable_url`, `shared_url` (the URL belongs to
|
|
1192
|
+
another page too), `unknown_page_type` (neither an offer type nor a known
|
|
1193
|
+
non-offer type), or `no_pages` (a funnel with no page list).
|
|
1194
|
+
`evidence.uncertainty[]` lists the run-level reasons: `unattributed_plan` (a
|
|
1195
|
+
plan that does not match exactly one funnel's checkout and page list),
|
|
1196
|
+
`unattributed_click` (a click with no usable URL, or from such a plan's order)
|
|
1197
|
+
and `undeclared_click` (an order clicked a page its funnel does not declare).
|
|
1198
|
+
`evidence.pages[]` gives `accept_clicked` / `decline_clicked` per page.
|
|
1006
1199
|
|
|
1007
1200
|
Other modes: `checkout` (base order redirect only), `accept`/`decline` (click the
|
|
1008
1201
|
rendered control on the first upsell page), `both` (two fresh orders for those
|
|
@@ -1436,8 +1629,11 @@ npm run campaigns-os -- qa run \
|
|
|
1436
1629
|
```
|
|
1437
1630
|
|
|
1438
1631
|
`--max-test-orders` (default `6`) is an **accidental-flood guard, not a permission
|
|
1439
|
-
gate**. A single checkout's `common`
|
|
1440
|
-
|
|
1632
|
+
gate**. A single checkout's `common` plan always keeps its checkout,
|
|
1633
|
+
first-offer accept/decline and shortest-receipt sample. If that sample alone
|
|
1634
|
+
exceeds the cap (a `--max-test-orders` below it), the run is refused before
|
|
1635
|
+
browser launch, as before. Otherwise the added decline paths stop at the cap.
|
|
1636
|
+
Tier expansion can exceed it. If `full` expands past the cap, the command stops before
|
|
1441
1637
|
browser launch, prints the planned count, lists the planned paths (up to 40 ids;
|
|
1442
1638
|
past that the remainder is counted, never cut silently, and `--select-package
|
|
1443
1639
|
<ref[:qty]>` lists one tier's paths), and names the exact `--max-test-orders <count>` raise. For example, a linear three-offer graph has
|
|
@@ -1618,8 +1814,9 @@ use the declared topology instead of a single happy path:
|
|
|
1618
1814
|
|
|
1619
1815
|
1. Checkout-only with the base cart.
|
|
1620
1816
|
2. Checkout-only with the base cart plus bump when the bump is in scope.
|
|
1621
|
-
3. Base cart through
|
|
1622
|
-
|
|
1817
|
+
3. Base cart through `--test-order common`: every actual terminal path when
|
|
1818
|
+
they fit under the cap, otherwise the checkout/first-action sample, the
|
|
1819
|
+
shortest real receipt path and one decline path per uncovered offer page.
|
|
1623
1820
|
4. Base plus bump cart through the same sample matrix when bump behavior is
|
|
1624
1821
|
launch-relevant.
|
|
1625
1822
|
5. Use `full` when you want every actual terminal path, raising the flood cap to
|
|
@@ -1690,6 +1887,19 @@ review. Nested Google Maps/payment keys and inert HTML do not count as campaign
|
|
|
1690
1887
|
credentials. This small static grammar deliberately leaves many real pages
|
|
1691
1888
|
unknown; a literal inside arbitrary code is not proof of effective configuration.
|
|
1692
1889
|
|
|
1890
|
+
A page script that does not parse is not treated as dynamic. The browser throws
|
|
1891
|
+
a `SyntaxError` on it and nothing in it runs, so the binding reads its
|
|
1892
|
+
declarations as unavailable (`script_unavailable_or_limit`) and QA adds a
|
|
1893
|
+
separate `script-parse:<page_id>` blocker in the same `api-metadata` family.
|
|
1894
|
+
Its `actual` names each script by path (inline scripts as `inline script`)
|
|
1895
|
+
with the line and column, and its evidence lists a fixed diagnostic category
|
|
1896
|
+
per script, never text from the script. Classic scripts are parsed as scripts
|
|
1897
|
+
and `type="module"` scripts as modules, with the type stripped of surrounding
|
|
1898
|
+
ASCII whitespace and compared case-insensitively as the browser does; classic
|
|
1899
|
+
`nomodule` scripts are not fetched or parsed (a module script ignores
|
|
1900
|
+
`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
|
|
1901
|
+
`built_output.script_syntax` in [the Build Packet doc](build-packet.md).
|
|
1902
|
+
|
|
1693
1903
|
External executable scripts other than the recognized jsDelivr Campaign Cart
|
|
1694
1904
|
loader/index are inspected only on the page's origin. Each page admits at most
|
|
1695
1905
|
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
|
|
|
@@ -144,7 +146,7 @@ incomplete, append a correction:
|
|
|
144
146
|
"amends": "RL-0007",
|
|
145
147
|
"amendment_reason": "RL-0007 was recorded as compatible; it removed a documented guarantee.",
|
|
146
148
|
"surface_version": null,
|
|
147
|
-
"changelog_section": "1.
|
|
149
|
+
"changelog_section": "1.16.0+agent.1",
|
|
148
150
|
"compatibility": "breaking",
|
|
149
151
|
"migration": "Stop relying on the removed guarantee; see docs/build-packet.md.",
|
|
150
152
|
"agent_impact": "Treat the 1.15.0 packet doc change as breaking, not compatible.",
|
|
@@ -152,6 +154,10 @@ incomplete, append a correction:
|
|
|
152
154
|
}
|
|
153
155
|
```
|
|
154
156
|
|
|
157
|
+
The amendment corrects a 1.15.0 entry, but its own section is a new one at the
|
|
158
|
+
very top of `CHANGELOG.md`, numbered on the release on top when it is written
|
|
159
|
+
(`1.16.0` here). It is never inserted under the older release it corrects.
|
|
160
|
+
|
|
155
161
|
An amendment is the only entry kind whose change items may map to no changed
|
|
156
162
|
path in its own range, because it corrects meaning rather than moving bytes.
|
|
157
163
|
|
|
@@ -169,13 +175,25 @@ entry currently holding the link, may re-link a section; any other second link
|
|
|
169
175
|
still fails the one-to-one rule. Say in `amendment_reason` what changed in the
|
|
170
176
|
section and why.
|
|
171
177
|
|
|
178
|
+
This works only for a section still in the live `CHANGELOG.md`. An archived
|
|
179
|
+
section cannot be corrected in place: archive files are never edited, and each
|
|
180
|
+
is pinned by its SHA-256 in `baseline_floor.archives`, so any change to one
|
|
181
|
+
fails the gate. Correct archived history with a new live amendment entry that
|
|
182
|
+
links its own new section at the top of `CHANGELOG.md`, and say in
|
|
183
|
+
`amendment_reason` what is wrong in the archived section.
|
|
184
|
+
|
|
172
185
|
`scripts/check-changelog-structure.mjs` (part of `npm run check`) refuses the
|
|
173
186
|
marker lines outright, in `CHANGELOG.md` and under `docs/`, and also holds the
|
|
174
187
|
section layout: identifiers unique, `+agent.N` sections in one run directly
|
|
175
188
|
above their release with N descending (newest first), and every ledger
|
|
176
|
-
`changelog_section` naming a section that exists.
|
|
177
|
-
|
|
178
|
-
|
|
189
|
+
`changelog_section` naming a section that exists.
|
|
190
|
+
|
|
191
|
+
A new section always goes at the very top of `CHANGELOG.md`: either a new
|
|
192
|
+
release, or a `+agent.N` section numbered on the current top release (one above
|
|
193
|
+
its highest N, or `+agent.1` if it has none). Never add a `+agent.N` section
|
|
194
|
+
under an older release. Given `--base`, both `check-changelog-structure.mjs` and
|
|
195
|
+
the release-ledger gate refuse any new section that sits below a section base
|
|
196
|
+
already had.
|
|
179
197
|
|
|
180
198
|
## Running the gate
|
|
181
199
|
|
|
@@ -272,3 +290,47 @@ a quiet raise.
|
|
|
272
290
|
|
|
273
291
|
Raising a limit is a reviewed policy change: advance `limits_version`, and the
|
|
274
292
|
change owes its own ledger entry like anything else.
|
|
293
|
+
|
|
294
|
+
## Rotating the baseline
|
|
295
|
+
|
|
296
|
+
When the live ledger and changelog approach a bound, rotate them at a reviewed
|
|
297
|
+
cut instead of raising a limit. The first rotation (2026-09-30, `RL-0190`)
|
|
298
|
+
moved `RL-0001` through `RL-0124`; the ledger's `baseline_floor` records it.
|
|
299
|
+
|
|
300
|
+
1. Pick the cut: the last entry to archive. Everything up to it moves, and so
|
|
301
|
+
does the changelog from the first section those entries link down to the end
|
|
302
|
+
of the file, including sections no entry links. If a kept entry amends an
|
|
303
|
+
archived one, or links a section in that tail, move the cut earlier until the
|
|
304
|
+
pair is on one side. Never later.
|
|
305
|
+
2. Run the rotation through `rotateLedger` in `scripts/orientation-contract.mjs`
|
|
306
|
+
with a new dated pair under `contracts/archive/`, e.g.
|
|
307
|
+
`release-ledger.<date>.json` and `CHANGELOG.<date>.md`. It writes the entries
|
|
308
|
+
byte-for-byte (original `sequence` and hashes kept) and the changelog tail
|
|
309
|
+
verbatim, appends the pair and its SHA-256 to `baseline_floor.archives`, and
|
|
310
|
+
moves `last_archived_id`, `last_archived_sequence` and `first_kept_id`. It
|
|
311
|
+
refuses a cut that splits a pair.
|
|
312
|
+
3. Never edit an existing archive file, and never reuse a date: each rotation
|
|
313
|
+
adds a new pair. Add both new files to `named` in
|
|
314
|
+
`contracts/supported-surface.json`.
|
|
315
|
+
4. Record the rotation as its own entry, named in `baseline_floor.rotation_entry`,
|
|
316
|
+
with a change item for each new archive file and a CHANGELOG section of its
|
|
317
|
+
own. Its `agent_impact` tells consumers which baseline is now too old.
|
|
318
|
+
|
|
319
|
+
The gate accepts a base entry missing from the live ledger only when the head
|
|
320
|
+
floor covers it, the new archive holds it canonical-JSON-identical, and the floor
|
|
321
|
+
moved with a new rotation entry in the same range. Any other deletion, an
|
|
322
|
+
archive copy that differs from base, a floor that moves without a rotation
|
|
323
|
+
entry, a floor that moves back or is rewritten, an edited archive file, and a
|
|
324
|
+
cut that archives an entry while an entry that was live at base stays live and
|
|
325
|
+
amends it (the same refusal `rotateLedger` gives) all fail. A new entry added
|
|
326
|
+
after the rotation may still amend an archived entry, linking its own section
|
|
327
|
+
in the live changelog: that is how archived history is corrected. The live changelog and each archive changelog must each be well-formed on
|
|
328
|
+
their own, a section id may appear in only one of them, a section present
|
|
329
|
+
at base must still be in one of them, and the live changelog followed by the
|
|
330
|
+
archives, newest rotation first, must read as the base's sections in order
|
|
331
|
+
with new sections only at the top of the live file. So every live section is
|
|
332
|
+
newer than every archived one, and an archived section never moves back
|
|
333
|
+
(`check-changelog-structure.mjs --base` and the `--base` release-ledger gate).
|
|
334
|
+
The archive files are not mandatory
|
|
335
|
+
orientation reads and are not measured; a consumer whose reviewed baseline is older than the floor refuses
|
|
336
|
+
with `baseline_below_floor` and adopts a newer reviewed baseline.
|
|
@@ -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.46.0`.
|
|
12
12
|
|
|
13
13
|
## What this is
|
|
14
14
|
|
|
@@ -8,7 +8,7 @@ npx --no-install campaigns-os sdk storage-check --target . --target-sdk 0.4.38 -
|
|
|
8
8
|
|
|
9
9
|
Omit `--json` for the concise human report. Exit 0 means source-compatible; exit 2 means incompatible or unknown; invalid arguments or manifests exit 1. No merchant files or pins are rewritten. This is independent of doctor's built HTML markup check.
|
|
10
10
|
|
|
11
|
-
The manifest is generated and owned by Campaign Cart, from its storage registry. The scanner has no independent SDK key list and fetches no remote code. Supply
|
|
11
|
+
The manifest is generated and owned by Campaign Cart, from its storage registry. The scanner has no independent SDK key list and fetches no remote code. Supply `docs/compatibility/storage-migrations.v1.json` from an unmodified checkout of a Campaign Cart release tag, v0.4.40 or later. The report records the full file SHA-256, SDK version, supported target range, registry/extractor/input digest, and Git provenance. When the supplied bytes equal their repository HEAD blob, provenance is `verified-git-blob` with commit and repository path. Proposed, modified, or copied manifests are labeled `unverified-local-file`; the label never certifies a release. Review/pin SDK provenance separately before acting on findings. Targets outside the manifest's supported SDK range report unknown.
|
|
12
12
|
|
|
13
13
|
`--target` must name the Git root. `--scope` is mandatory: comma-separated literal repository-relative directory/file paths; use `.` only when the complete repository is intended. Add shared JavaScript directories explicitly. `--exclude` uses the same syntax and records explicit exclusions. Archives receive no implicit exemption. Only Git-tracked `.html`, `.htm`, `.js`, `.mjs`, and `.cjs` files in the selected scope are scanned, using current working-tree bytes and per-file digests; untracked files and built dependencies are outside this evidence. A selected HTML page's local script outside the selected files reports unknown. Relative scripts affected by an HTML `<base href>` also report unknown; review their actual dependency paths. Remote SDK and third-party scripts are not fetched or analyzed.
|
|
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.46.0+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.46.0+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.46.0+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.46.0+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.46.0+skills.1",
|
|
94
94
|
"spelling": "bundle",
|
|
95
|
-
"on_disk": "1.
|
|
95
|
+
"on_disk": "1.46.0+skills.1",
|
|
96
96
|
"on_disk_skill": null,
|
|
97
|
-
"message": "match (1.
|
|
97
|
+
"message": "match (1.46.0+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.46.0+skills.1)
|
|
105
|
+
Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.46.0+skills.1 — start a fresh session
|
|
106
|
+
Skills revision: unchecked (on disk 1.46.0+skills.1)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
`unchecked` is the state when the flag is absent. It is not an error — an
|
|
@@ -14,7 +14,7 @@ implementation detail, however stable it looks.
|
|
|
14
14
|
| Surface | Contract | Change discipline |
|
|
15
15
|
|---|---|---|
|
|
16
16
|
| `schemas/*.schema.json` (all of them) | The portable contract catalog: CampaignSpec, Design Source Package, Build Packet, Build Context, Assembly Report, Doctor Output, sidecar-bundle conformance, Run Record, Workflow Finding, Build Brief, Source-HTML Manifest, Tooling Orientation, Release Ledger, QA Verdict, the QA Verdict sidecar projection, Runtime Recipe, and the legacy-migration inventory/plan/receipt trio. | Hashed. Any content change requires updating the recorded hash **and** bumping `surface_version` in the same PR. A shape change that alters meaning gets a new schema-version const — one version identifier must never cover two shapes (the 2026-08 assembly-report drift is the incident this rule encodes). Additions to an open `v0` schema are expected and consumers must tolerate unknown fields; the security-sensitive legacy-migration schemas are closed, so additions there require a new lineage. 1.28.0 (RL entry `surface_version: 1.28.0`, breaking) removed the two required Build Packet booleans `qa.test_orders_allowed` and `qa.sandbox_test_card_confirmed` (nothing read them; test orders run from `--test-order <mode>` alone), added `local-serve` to the `deploy.target` enum, and added the optional `remit_result` / `remit_base_kind` fields to the Run Record. 1.30.0 (additive) added the optional `data_layer` record to the QA Verdict's `test_orders[]` entries — the order's `dl_purchase` reading (#325). 1.33.0 (additive) added the optional `qa_verdict_publish` block to the Run Record — which verdict was posted to the QA portal, by `qa run` or `qa publish`, and what the portal answered, in the `remit_result` vocabulary (#328). |
|
|
17
|
-
| CLI commands: `start`, `prepare-build`, `build`, `polish`, `checkpoint`, `page-kit`, `spec`, `doctor`, `bundle`, `next`, `theme`, `tooling`, `install-skills`, `install-agent-context`, `validate-assembly-report`, `telemetry`, `standardize`, `qa`, `findings`, `run-record`, `run`, `sdk`, `demo`, `login`, `logout` | Scriptable entry points. The founding list (1.0.0) was the argv surface Campaigns Agent's remit fixture pins; 1.25.0 adds the partner entry path the public guides instruct — `install-skills` (skill install), `tooling status` (preflight), `next --packet` (the runtime cursor) — plus `theme`, `install-agent-context`, `validate-assembly-report`, and `telemetry`, since consent is part of the partner contract. `validate-build-packet`, formerly an undocumented and unsupported alias of `doctor`, was removed in 1.25.0+agent.7 (RL-0036) and now gets the unknown-command error; use `doctor`. `standardization-report`, a supported but redundant second spelling of `standardize` — same flags, same output, same exit codes — was removed in 1.27.0 and now gets the same unknown-command error; use `standardize`. Removing a supported command is a breaking change and is why that release moved the minor. `bundle check` validates the canonical JSON readback set and makes QA required only under `--require-qa`. `polish capture` owns page-load evidence production. `checkpoint waive` currently accepts
|
|
17
|
+
| CLI commands: `start`, `prepare-build`, `build`, `polish`, `checkpoint`, `page-kit`, `spec`, `doctor`, `bundle`, `next`, `theme`, `tooling`, `install-skills`, `install-agent-context`, `validate-assembly-report`, `telemetry`, `standardize`, `qa`, `findings`, `run-record`, `run`, `sdk`, `demo`, `login`, `logout` | Scriptable entry points. The founding list (1.0.0) was the argv surface Campaigns Agent's remit fixture pins; 1.25.0 adds the partner entry path the public guides instruct — `install-skills` (skill install), `tooling status` (preflight), `next --packet` (the runtime cursor) — plus `theme`, `install-agent-context`, `validate-assembly-report`, and `telemetry`, since consent is part of the partner contract. `validate-build-packet`, formerly an undocumented and unsupported alias of `doctor`, was removed in 1.25.0+agent.7 (RL-0036) and now gets the unknown-command error; use `doctor`. `standardization-report`, a supported but redundant second spelling of `standardize` — same flags, same output, same exit codes — was removed in 1.27.0 and now gets the same unknown-command error; use `standardize`. Removing a supported command is a breaking change and is why that release moved the minor. `bundle check` validates the canonical JSON readback set and makes QA required only under `--require-qa`. `polish capture` owns page-load evidence production. `checkpoint waive` currently accepts five registered gates: `page_kit.store_profile`, `page_kit.sdk_version`, `polish.hidden_eager_media`, `built_output.upsell_selector_scope`, and `source_html.producer_provenance` (per page, with `--page <page_id>`). `page-kit sync` (added in 1.29.0) writes the CampaignSpec's Store Profile fields and SDK pin into the target's `_data/campaigns.json` entry — the repair the first two gates name — and is the only `page-kit` subcommand. `spec derive` (added in 1.31.0) is the reverse write for the repo-derived field class (#432): the target's SDK pin, page routes and analytics ids into the packet's local CampaignSpec; it is the only `spec` subcommand, and the `page_kit.sdk_version.repo_newer` advisory names it; its `--write-map` flag (1.33.0+agent.2) also records the derived pin in the saved Map's Build hints field through the proxy Worker, never moving a Map pin backwards. `qa publish` (added in 1.33.0) posts an already-stored verdict to the QA portal without a re-run or an order, refusing a stale `spec_hash` or a verdict its Run Record already records as published (#328). Within Polish, only the broader Source Freshness waiver remains on its existing report lane; theme and QA decisions also retain their existing lanes. | Any change to this list — adding, renaming, or removing a command — bumps `surface_version` in the same PR, and since 1.25.0 `check-supported-surface.mjs --base` enforces that (before, only hashed and named entries owed a bump, so `checkpoint` landed unbumped). Subcommands, registered gates, and flags may grow freely beneath a listed command. Removing a listed command from dispatch fails the gate outright. Do not infer support for an unregistered checkpoint from the top-level command. |
|
|
18
18
|
| `sdk storage-check` and `docs/sdk-storage-compatibility.md` | Read-only AST compatibility report for explicitly scoped tracked campaign HTML/JS before an SDK bump; consumes the supplied SDK-owned manifest and records its SHA-256 and verified or unverified local Git provenance. Incompatible and unknown findings exit 2; a clean scan is static source evidence only. | Additive supported CLI and named documentation; no automatic merchant integration repair. |
|
|
19
19
|
| `bin/campaigns-os.mjs` (`campaigns-os`) | The CLI entry itself. | Declared in `package.json` `bin`; the gate fails if it disappears. |
|
|
20
20
|
| Package export `./campaign-spec` | The versioned campaign-spec rule registry, consumed as `@nextcommerce/campaigns-os` (pinned by consumers' lockfiles; lockstep policy — ADR-003 in the ops repo). | Behavior-guarded from the consumer side by their contract tests; the export path itself is gated here. |
|
|
@@ -24,7 +24,7 @@ implementation detail, however stable it looks.
|
|
|
24
24
|
| Contract docs: `CONTEXT.md`, `docs/campaigns-os-build-flow.md`, `docs/build-packet.md`, `docs/migration-sidecar-bundle.md`, `docs/design-source-package.md`, `docs/campaign-build-brief.md`, `docs/campaign-standardization-report.md`, `docs/brand-theme-bridge.md`, `docs/qa-and-test-orders.md`, `docs/legacy-migration.md`, `docs/versioning.md`, `docs/workflow-findings-sidecar.md`, this file | Named entry points consumers pin for context. | Content evolves freely; the path must keep existing. |
|
|
25
25
|
| `skills.json` + `skills/` + `skills.sh` | Versioned skill packages and their installer. | Governed by `check-skill-versions.mjs` (parity + bump gate + reserved external names). `skills.json` ships in the npm pack as of surface 1.0.0. As of 1.40.0 the manifest also carries `bundle_revision` (`<package version>+skills.<n>`), one identity for the bundle that every `SKILL.md` states on its first body line and that `campaigns-os tooling status --skills-revision <value>` checks against the bundle on disk; it must advance whenever any bundled skill changes (`--base` mode), and `docs/skills-revision.md` is its prose. |
|
|
26
26
|
| `compatibility.json` | The published compatibility statement. | Named; must keep existing. |
|
|
27
|
-
| Orientation contract: `AGENTS.md`, `CHANGELOG.md`, `contracts/release-ledger.json`, `contracts/agent-relevant-change-policy.v1.json`, `contracts/orientation-limits.v1.json`, `contracts/orientation-reason-codes.v1.json`, `docs/orientation-contract-reference.md`, `docs/release-ledger-authoring-guide.md`, `contracts/fixtures/orientation/canonicalization/v1.json` | The declarative data a consumer reads from Git objects to decide whether a commit is safe to work against, without executing anything from this repository. Schema `campaigns-os-tooling-orientation/v1`; ledger schema `campaigns-os-release-ledger/v1`. The canonicalization fixture gives independent implementations exact ledger and changelog bytes plus their expected SHA-256 digests. Entry point: [`AGENTS.md`](../AGENTS.md). | Named. The ledger is append-only: a correction ships as a new amendment entry, never an edit. `scripts/check-release-ledger.mjs` enforces the two-way gate; `docs/orientation-contract-reference.md` and the canonicalization fixture are generated together, and CI fails if either is stale. |
|
|
27
|
+
| Orientation contract: `AGENTS.md`, `CHANGELOG.md`, `contracts/release-ledger.json`, `contracts/archive/release-ledger.2026-09-30.json`, `contracts/archive/CHANGELOG.2026-09-30.md`, `contracts/agent-relevant-change-policy.v1.json`, `contracts/orientation-limits.v1.json`, `contracts/orientation-reason-codes.v1.json`, `docs/orientation-contract-reference.md`, `docs/release-ledger-authoring-guide.md`, `contracts/fixtures/orientation/canonicalization/v1.json` | The declarative data a consumer reads from Git objects to decide whether a commit is safe to work against, without executing anything from this repository. Schema `campaigns-os-tooling-orientation/v1`; ledger schema `campaigns-os-release-ledger/v1`. The canonicalization fixture gives independent implementations exact ledger and changelog bytes plus their expected SHA-256 digests. Entry point: [`AGENTS.md`](../AGENTS.md). | Named. The ledger is append-only: a correction ships as a new amendment entry, never an edit. A baseline rotation moves older entries and changelog sections unchanged into dated files under `contracts/archive/`, which never change once merged and are not mandatory orientation reads. `scripts/check-release-ledger.mjs` enforces the two-way gate; `docs/orientation-contract-reference.md` and the canonicalization fixture are generated together, and CI fails if either is stale. |
|
|
28
28
|
| Orientation fixtures: `contracts/fixtures/orientation/envelope/*.json`, `contracts/fixtures/orientation/hostile-target/**` (as named) | The bytes a consumer's parser validates against: one envelope per terminal outcome, plus a hostile target carrying Git hooks, an executable file, and npm lifecycle scripts for proving a reader executes nothing. The hostile target carries a second invariant for the runtime recipe: preparing it must run the recipe's own two steps and no lifecycle script reachable from them. | Named. Regenerate the envelopes with `npm run generate:orientation-docs`. Fixtures under `contracts/fixtures/` that are **not** named here — including the legacy-migration conformance corpus — are this repo's own test data and are not supported. |
|
|
29
29
|
| Runtime recipe: `contracts/runtime-recipe.campaigns-os-node-v1.json` | The declarative description of how a checkout at one commit becomes a usable installed runtime: exact commands, accepted tool ranges, per-step network policy, the enumerated input set, the mandatory output checks, and the enforced bounds. This repository publishes it; the consumer bootstrap executes it. Guide: [`docs/runtime-readiness.md`](runtime-readiness.md). | **Hashed**, deliberately unlike the orientation policy contracts beside it. Any change to commands, network policy, tool versions, inputs, or output verification is an agent-relevant release event, and only a hashed entry makes such a change require `surface_version` to advance in the same PR. A recipe whose commands can change without a version bump is not a contract. |
|
|
30
30
|
| Runtime-recipe fixtures: `contracts/fixtures/runtime-recipe/**` (as named), `docs/runtime-readiness.md` | Accept and single-mutation reject documents a consumer's parser validates against, the prepared-runtime states its output checks must distinguish, and the generated guide. | Named. All of it is generated — regenerate with `npm run generate:runtime-docs`; CI fails on a stale copy. |
|
package/docs/versioning.md
CHANGED
|
@@ -42,7 +42,10 @@ consumer needs to know about an agent-relevant change even when
|
|
|
42
42
|
`CHANGELOG.md` narrates them, and the two are checked against each other in both
|
|
43
43
|
directions. Reason codes and semantic classes are append-only vocabularies —
|
|
44
44
|
renaming or removing one is a breaking change. Raising a limit advances
|
|
45
|
-
`limits_version` and owes its own ledger entry.
|
|
45
|
+
`limits_version` and owes its own ledger entry. Rotating the baseline (moving
|
|
46
|
+
old entries and their sections into a dated `contracts/archive/` pair under the
|
|
47
|
+
ledger's `baseline_floor`) changes no limit and no schema id; it is recorded as
|
|
48
|
+
its own ledger entry.
|
|
46
49
|
|
|
47
50
|
The runtime recipe carries two version identifiers because they gate different
|
|
48
51
|
things. The **kind** names what an installed consumer must already understand in
|