@nextcommerce/campaigns-os 1.43.2 → 1.47.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 +5 -0
- package/CHANGELOG.md +798 -5103
- package/README.md +33 -12
- 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/compatibility.json +1 -1
- package/contracts/archive/CHANGELOG.2026-09-30.md +5111 -0
- package/contracts/archive/release-ledger.2026-09-30.json +5068 -0
- package/contracts/commerce-surface-catalog.json +1204 -129
- package/contracts/effects.v1.json +1179 -116
- package/contracts/orientation-reason-codes.v1.json +7 -0
- package/contracts/release-ledger.json +2515 -5919
- package/contracts/supported-surface.json +7 -4
- package/contracts/template-brand-contract.shared-commerce.v0.json +3 -3
- 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 +180 -23
- package/docs/campaigns-os-build-flow.md +3 -3
- package/docs/design-source-package.md +73 -0
- package/docs/effects.md +50 -8
- package/docs/gateway-login.md +3 -0
- package/docs/local-setup.md +7 -4
- package/docs/orientation-contract-reference.md +42 -2
- package/docs/polish-evidence.md +74 -0
- package/docs/qa-and-test-orders.md +118 -14
- package/docs/release-ledger-authoring-guide.md +64 -4
- 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/built-script-syntax.mjs +116 -15
- package/src/built-site-scope.mjs +16 -4
- package/src/cli.mjs +280 -46
- package/src/commercial-parity.mjs +48 -2
- package/src/deviation.mjs +13 -1
- package/src/diagnostic.mjs +6 -2
- package/src/doctor/checks.mjs +319 -81
- package/src/doctor/inspect.mjs +55 -13
- package/src/doctor/source-provenance.mjs +184 -0
- package/src/invocation.mjs +4 -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/progress-node.mjs +3 -1
- package/src/qa-analytics-parity.mjs +37 -2
- package/src/qa-binding-evidence.mjs +4 -2
- package/src/qa-browser.mjs +612 -40
- package/src/qa-commercial-parity.mjs +48 -5
- package/src/qa-node.mjs +122 -7
- package/src/qa-test-order-topology.mjs +148 -0
- package/src/sdk-markup.mjs +32 -7
- package/src/sdk-storage-compatibility.mjs +3 -2
- package/src/source-html-intake.mjs +116 -0
- package/src/stage-record.mjs +551 -0
- package/src/tooling-setup.mjs +9 -0
- package/src/upsell-selector-scope.mjs +112 -2
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
contracts/orientation-reason-codes.v1.json
|
|
10
10
|
contracts/orientation-limits.v1.json
|
|
11
11
|
contracts/supported-surface.json
|
|
12
|
+
contracts/release-ledger.json (baseline_floor only)
|
|
12
13
|
contracts/fixtures/orientation/canonicalization/v1.json
|
|
13
14
|
Regenerate: node ./scripts/generate-orientation-reference.mjs --write
|
|
14
15
|
CI runs the same script with --check, so a stale copy of this file fails the build.
|
|
@@ -25,7 +26,7 @@ Ledger schema id: `campaigns-os-release-ledger/v1`
|
|
|
25
26
|
Change policy version: `1.0.0`
|
|
26
27
|
Reason-code vocabulary version: `1.0.0`
|
|
27
28
|
Limits version: `1.0.0`
|
|
28
|
-
Supported surface at generation time: `1.
|
|
29
|
+
Supported surface at generation time: `1.47.0`
|
|
29
30
|
|
|
30
31
|
## Forward compatibility
|
|
31
32
|
|
|
@@ -60,6 +61,43 @@ position, so a consumer reading history in array order reads it in `sequence` or
|
|
|
60
61
|
its number to `sequence`; the two diverge legitimately, because when concurrent pull requests
|
|
61
62
|
land the later one restamps its `sequence` to follow the earlier while keeping the id it was
|
|
62
63
|
written with. Order by `sequence`, identify by `id`, and do not infer one from the other.
|
|
64
|
+
After a baseline rotation the file starts at `baseline_floor.last_archived_sequence + 1`, not at 1;
|
|
65
|
+
entries are never renumbered, so position plus that offset is still the sequence.
|
|
66
|
+
|
|
67
|
+
## Baseline rotation
|
|
68
|
+
|
|
69
|
+
The ledger and the changelog are bounded as whole files, so they are rotated rather than allowed to
|
|
70
|
+
outgrow the limits. A rotation moves every entry up to a reviewed cut, and the changelog from the first section
|
|
71
|
+
those entries link to the end of the file (unlinked sections in that range included), byte-for-byte and in order
|
|
72
|
+
into a dated archive pair under `contracts/archive/`, and declares the cut as
|
|
73
|
+
`baseline_floor` in `contracts/release-ledger.json`. Archived entries keep their `sequence`, `entry_sha256` and
|
|
74
|
+
`changelog_sha256`; each section hash verifies against the archive changelog. The floor names the archive
|
|
75
|
+
files with their SHA-256, the last archived entry and its sequence, the first kept entry, the live entry that
|
|
76
|
+
recorded the rotation, and the reason code to refuse with. The floor only moves forward, only with a new
|
|
77
|
+
rotation entry, and an archive file never changes once merged; the next rotation writes a new dated pair.
|
|
78
|
+
|
|
79
|
+
Rotation changes no mandatory read. The reading order in `AGENTS.md` is unchanged, and `source_bytes` measures
|
|
80
|
+
exactly its data files (steps 1 to 8):
|
|
81
|
+
|
|
82
|
+
- `contracts/supported-surface.json`
|
|
83
|
+
- `contracts/release-ledger.json`
|
|
84
|
+
- `CHANGELOG.md`
|
|
85
|
+
- `contracts/orientation-limits.v1.json`
|
|
86
|
+
- `contracts/orientation-reason-codes.v1.json`
|
|
87
|
+
- `schemas/campaigns-os-tooling-orientation.v1.schema.json`
|
|
88
|
+
- `schemas/campaigns-os-release-ledger.v1.schema.json`
|
|
89
|
+
- `contracts/agent-relevant-change-policy.v1.json`
|
|
90
|
+
|
|
91
|
+
The archive files are not among them: they are optional history reads and count against no limit, and their
|
|
92
|
+
sections and entries are not in `section_count` or `ledger_entries`.
|
|
93
|
+
|
|
94
|
+
Current floor: last archived entry `RL-0124` (sequence 124), first kept entry
|
|
95
|
+
`RL-0125`, recorded by `RL-0190`. Archives, oldest rotation first:
|
|
96
|
+
|
|
97
|
+
- [`contracts/archive/release-ledger.2026-09-30.json`](../contracts/archive/release-ledger.2026-09-30.json) and [`contracts/archive/CHANGELOG.2026-09-30.md`](../contracts/archive/CHANGELOG.2026-09-30.md)
|
|
98
|
+
|
|
99
|
+
A consumer whose reviewed baseline's newest ledger entry is older than `RL-0124` refuses with
|
|
100
|
+
`baseline_below_floor`. Adopt a newer reviewed baseline whose ledger reaches the floor's last_archived_id; every commit at or after the floor's first_kept_id qualifies. Do not orient on the partial live window, and do not stitch the archive in as a substitute: the archive holds the history for reference, not for this read.
|
|
63
101
|
|
|
64
102
|
## Release-ledger digest canonicalization
|
|
65
103
|
|
|
@@ -120,6 +158,7 @@ consumer's parser tests.
|
|
|
120
158
|
|---|---|---|---|---|---|
|
|
121
159
|
| `ahead_of_upstream` | `refused` | campaigns-agent | `TP-D4-ahead-of-upstream` | The checkout carries commits the upstream default branch does not, so its contracts are not a published generation. | Push or drop the local commits, or run against a managed generation at the published OID. |
|
|
122
160
|
| `already_current` | `current` | campaigns-agent | `TP-B3-already-current` | The verified target equals the active generation; nothing changed and no restart is required. | None. |
|
|
161
|
+
| `baseline_below_floor` | `refused` | campaigns-os | `A1-baseline-below-floor` | The target ledger declares a baseline_floor, and the reviewed baseline's newest ledger entry is older than the floor's last archived entry. Part of the baseline-to-target window was rotated into the archive files the floor names, which are not mandatory orientation reads, so the window cannot be read from the live ledger and changelog. | Adopt a newer reviewed baseline whose ledger reaches the floor's last_archived_id; every commit at or after the floor's first_kept_id qualifies. Do not orient on the partial live window, and do not stitch the archive in as a substitute: the archive holds the history for reference, not for this read. |
|
|
123
162
|
| `checkout_acquisition_failed` | `refused` | campaigns-agent | `TP-D1-acquisition-failed` | Managed acquisition did not complete: interrupted clone, remote validation failure, or a broken linked-worktree backpointer. Partial state is quarantined, never exposed as ready. | Rerun acquisition. If it fails repeatedly, inspect the quarantined partial named in the diagnostic and confirm remote reachability. |
|
|
124
163
|
| `checkout_already_exists` | `refused` | campaigns-agent | `TP-D1-checkout-already-exists` | The managed root reserved for acquisition appeared concurrently and is not an empty reservation this run owns. | Rerun so the existing root is validated as a managed store, or remove the unexpected directory after confirming it holds no needed state. |
|
|
125
164
|
| `checkout_missing` | `refused` | campaigns-agent | `TP-D1-checkout-missing` | No checkout exists at the configured location and the mode does not authorize acquiring one. | Supply the operator checkout at the configured path, or switch to managed mode so a generation can be acquired. |
|
|
@@ -205,7 +244,7 @@ nowhere in the schemas, or in the schemas and not here, is a generation failure.
|
|
|
205
244
|
| `$defs.baseline.properties.kind` | `legacy_commit`, `supported_surface` |
|
|
206
245
|
| `$defs.change_class` | `schema`, `hashed_surface`, `named_surface`, `cli_surface`, `skill`, `package_export`, `compatibility_policy`, `documentation`, `workflow`, `generated_runtime` |
|
|
207
246
|
| `$defs.disposition` | `current`, `orientation_available`, `updated`, `restart_required`, `recovered_interrupted_update`, `legacy_baseline`, `freshness_unknown`, `refused` |
|
|
208
|
-
| `$defs.reason_code` | `already_current`, `ahead_of_upstream`, `checkout_acquisition_failed`, `checkout_already_exists`, `checkout_missing`, `checkout_mutation_not_authorized`, `detached_head`, `dirty_checkout`, `diverged_history`, `evidence_budget_exceeded`, `fetch_failed`, `git_environment_unsafe`, `history_incomplete`, `missing_upstream`, `orientation_contract_missing`, `orientation_in_progress`, `orientation_incomplete`, `orientation_rendered`, `orientation_too_large`, `pointer_race`, `runtime_commit_mismatch`, `runtime_refresh_failed`, `runtime_refresh_required`, `surface_incompatible`, `transaction_incomplete`, `transaction_reconciled`, `wrong_remote` |
|
|
247
|
+
| `$defs.reason_code` | `already_current`, `ahead_of_upstream`, `baseline_below_floor`, `checkout_acquisition_failed`, `checkout_already_exists`, `checkout_missing`, `checkout_mutation_not_authorized`, `detached_head`, `dirty_checkout`, `diverged_history`, `evidence_budget_exceeded`, `fetch_failed`, `git_environment_unsafe`, `history_incomplete`, `missing_upstream`, `orientation_contract_missing`, `orientation_in_progress`, `orientation_incomplete`, `orientation_rendered`, `orientation_too_large`, `pointer_race`, `runtime_commit_mismatch`, `runtime_refresh_failed`, `runtime_refresh_required`, `surface_incompatible`, `transaction_incomplete`, `transaction_reconciled`, `wrong_remote` |
|
|
209
248
|
|
|
210
249
|
### `schemas/campaigns-os-release-ledger.v1.schema.json`
|
|
211
250
|
|
|
@@ -225,6 +264,7 @@ so a renamed command fails here as well as at the supported-surface gate.
|
|
|
225
264
|
- `campaigns-os prepare-build`
|
|
226
265
|
- `campaigns-os build`
|
|
227
266
|
- `campaigns-os polish`
|
|
267
|
+
- `campaigns-os record`
|
|
228
268
|
- `campaigns-os checkpoint`
|
|
229
269
|
- `campaigns-os page-kit`
|
|
230
270
|
- `campaigns-os spec`
|
package/docs/polish-evidence.md
CHANGED
|
@@ -21,6 +21,11 @@ Three layers must all be satisfied:
|
|
|
21
21
|
`campaigns-os polish capture` from the current packet, report, served build,
|
|
22
22
|
mapped routes, and fixed desktop/mobile viewports.
|
|
23
23
|
|
|
24
|
+
Record all three with `campaigns-os record polish` (§7) rather than editing
|
|
25
|
+
the report by hand: it stamps the stage-record fields from doctor's current
|
|
26
|
+
state, keeps the captured `page_load`, and writes nothing unless this gate would
|
|
27
|
+
pass on the result.
|
|
28
|
+
|
|
24
29
|
## 1. Stage-record requirements
|
|
25
30
|
|
|
26
31
|
The gate only applies once assembly is complete
|
|
@@ -500,3 +505,72 @@ current value.
|
|
|
500
505
|
Polish evidence certifies the polish pass only — it is not QA and does not
|
|
501
506
|
certify launch readiness (`docs/qa-and-test-orders.md` owns the QA proof
|
|
502
507
|
stack).
|
|
508
|
+
|
|
509
|
+
## 7. Recording with `record polish`
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
campaigns-os record polish --packet <campaign-runtime.build.json> --evidence <polish-evidence.json> [--report <json>] [--dry-run] [--json]
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Run it after `campaigns-os polish capture`, against the build `campaigns-os
|
|
516
|
+
record build` recorded. The `--evidence` file is a JSON object with these keys;
|
|
517
|
+
any other key is refused:
|
|
518
|
+
|
|
519
|
+
| Key | Required | Written to |
|
|
520
|
+
|---|---|---|
|
|
521
|
+
| `status` | no (default `completed`) | `stages.polish.status`; `completed`, `completed_with_warnings`, `blocked` or `skipped` |
|
|
522
|
+
| `evidence` | for `completed*` | `stages.polish.evidence`, the seven categories of §2. Leave out `visual_review.page_load`: the value `polish capture` recorded is kept, and a file that carries one is refused. Without it, a `blocked` or `skipped` record keeps the evidence already on the report. |
|
|
523
|
+
| `blockers` | for `blocked` only | `stages.polish.blockers`: a non-empty array of `{"code", "message"}` objects. |
|
|
524
|
+
| `skip_reason` | for `skipped` only | `stages.polish.skip_reason`: a non-empty string. |
|
|
525
|
+
| `repair_loop_defect` | no | `report.theme.repair_loop_defect`: `null`, or the first brand-layer repair-loop defect as an object (`code`, `message`, `path`, `detail`). A non-null defect needs a recorded `report.theme`. |
|
|
526
|
+
|
|
527
|
+
The command itself stamps `performed_by: "next-campaigns-polish"`,
|
|
528
|
+
`source_build_fingerprint` (doctor's `derived.build_output_fingerprint.value`,
|
|
529
|
+
which must equal the recorded `stages.assembly.build_fingerprint`),
|
|
530
|
+
`source_package_material_fingerprint` when the report fingerprints a Design
|
|
531
|
+
Source Package, and `completed_at` (not for `blocked`). It then validates the
|
|
532
|
+
report it would write against
|
|
533
|
+
`schemas/campaign-runtime-assembly-report.v0.schema.json` and doctor's report
|
|
534
|
+
checks and, for a `completed*` status, evaluates this gate and the hidden
|
|
535
|
+
eager-media checkpoint over it exactly as doctor does. A `blocked` or `skipped`
|
|
536
|
+
record keeps this gate blocked, so `next` stays at Polish and QA stays blocked. Any failure is printed by field or gate code
|
|
537
|
+
(for example `repair_loop_defect must be null or an object ... (got string)`, or
|
|
538
|
+
`polish.evidence_incomplete` with its per-field problems), the command exits
|
|
539
|
+
non-zero, and nothing is written. `--dry-run` runs every check and writes
|
|
540
|
+
nothing. Like `record setup` and `record build`, it refuses a report that is not
|
|
541
|
+
bound to the packet (`docs/build-packet.md`, "Recording stage completion") and
|
|
542
|
+
output that changes while it records.
|
|
543
|
+
|
|
544
|
+
A complete file:
|
|
545
|
+
|
|
546
|
+
```json
|
|
547
|
+
{
|
|
548
|
+
"status": "completed",
|
|
549
|
+
"evidence": {
|
|
550
|
+
"visual_review": {
|
|
551
|
+
"screenshots": ["qa-output/polish/landing-desktop.png", "qa-output/polish/landing-mobile.png"]
|
|
552
|
+
},
|
|
553
|
+
"brand_review": {
|
|
554
|
+
"logo_checked": true,
|
|
555
|
+
"favicon": { "status": "confirmed_non_template" },
|
|
556
|
+
"colors": ["#1f4d3a"],
|
|
557
|
+
"brand_bleed": { "cleared": true }
|
|
558
|
+
},
|
|
559
|
+
"checkout_review": {
|
|
560
|
+
"field_labels": "initial field labels and placeholders are legible on desktop and mobile",
|
|
561
|
+
"phone_alignment": "checked",
|
|
562
|
+
"payment_display": "checked",
|
|
563
|
+
"bump_compare_price_rule": "no equal or no-discount compare price renders"
|
|
564
|
+
},
|
|
565
|
+
"template_residue_review": {
|
|
566
|
+
"next_blue": "not found",
|
|
567
|
+
"starter_favicon": { "status": "confirmed_non_template" },
|
|
568
|
+
"lorem": "not found"
|
|
569
|
+
},
|
|
570
|
+
"commerce_flow_review": "direct-entry package selection reviewed",
|
|
571
|
+
"issues": [],
|
|
572
|
+
"commands": ["next-campaigns-polish", "campaigns-os polish capture"]
|
|
573
|
+
},
|
|
574
|
+
"repair_loop_defect": null
|
|
575
|
+
}
|
|
576
|
+
```
|
|
@@ -246,12 +246,18 @@ before QA with the relevant gate ID:
|
|
|
246
246
|
```bash
|
|
247
247
|
campaigns-os checkpoint waive \
|
|
248
248
|
--packet campaign-runtime.build.json \
|
|
249
|
-
--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>] \
|
|
250
251
|
--reason "<why>" \
|
|
251
252
|
--waived-by "<named human>" \
|
|
252
253
|
--review-condition "<specific re-evaluation trigger>"
|
|
253
254
|
```
|
|
254
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
|
+
|
|
255
261
|
Legacy source/theme/QA waiver commands and artifact lanes remain in place until
|
|
256
262
|
those gates are registered. Store Profile evidence includes only the governed
|
|
257
263
|
nine-field matrix plus status, normalized slug, relative target path,
|
|
@@ -432,7 +438,8 @@ work that lands with the receiver's connection contract. This section
|
|
|
432
438
|
documents the semantics of the stamps the receiver already applies.
|
|
433
439
|
|
|
434
440
|
Add `--browser --test-order common` for the normal proof pass: first-party
|
|
435
|
-
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
|
|
436
443
|
browser binary is missing, the CLI will prompt you to run
|
|
437
444
|
`npm run qa:install-browser`:
|
|
438
445
|
|
|
@@ -452,6 +459,30 @@ machine-checkable `agentContract.qaStructure` selectors in the commerce surface
|
|
|
452
459
|
catalog. If the family contract is silent, the assertion returns
|
|
453
460
|
`manual_review`, not `pass`; if declared required structure is missing, it
|
|
454
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.
|
|
455
486
|
Promoted template families must also have
|
|
456
487
|
`contracts/template-brand-contract.<family>.v0.json`; QA emits a blocker if the
|
|
457
488
|
selected family is missing its brand/residue/pricing contract instead of
|
|
@@ -973,6 +1004,22 @@ the baseline, and when no candidate page is captured the leg emits
|
|
|
973
1004
|
`no_in_scope_page_captured` (skipped) or `no_capture_page_answered`
|
|
974
1005
|
(`FAIL`/`BLOCKER`).
|
|
975
1006
|
|
|
1007
|
+
The automatic candidate is never a receipt page: the campaign root is not one,
|
|
1008
|
+
and a built entry counts as one only when its topology `page_type` is `receipt`
|
|
1009
|
+
or `thankyou`. The leg does not substitute the topology's receipt step, because
|
|
1010
|
+
a receipt loaded without an order fires no Purchase. So when the automatic
|
|
1011
|
+
candidate is not a receipt and fires no Purchase, `purchase-present` is
|
|
1012
|
+
`MANUAL_REVIEW`/`WARN` instead of a blocker, and its `evidence.page_mismatch`
|
|
1013
|
+
names the mismatch: `reason` (`receipt_baseline_non_receipt_candidate` when the
|
|
1014
|
+
baseline fired a Purchase, else `candidate_not_receipt`),
|
|
1015
|
+
`baseline_fired_purchase`, `candidate_receipt: false`, `candidate_source`
|
|
1016
|
+
(`campaign_root` or `built_entry`) and `candidate_page_type`. To compare
|
|
1017
|
+
Purchase, pass the candidate receipt with `--analytics-candidate`. An explicit
|
|
1018
|
+
candidate, or an automatic one whose page type is a receipt, still blocks on a
|
|
1019
|
+
missing Purchase, and a non-receipt candidate that does fire a Purchase gets the
|
|
1020
|
+
full set of Purchase checks, with its page recorded in `evidence.candidate_page`
|
|
1021
|
+
on `purchase-present`.
|
|
1022
|
+
|
|
976
1023
|
| Flag | Meaning |
|
|
977
1024
|
|---|---|
|
|
978
1025
|
| `--analytics-baseline <url>` | Legacy funnel URL to capture as the parity baseline (enables the leg) |
|
|
@@ -988,7 +1035,9 @@ Point both at the **thank-you / receipt page** for the highest-value `dl_purchas
|
|
|
988
1035
|
check, or drive the same offer through each funnel so client-fired values line up.
|
|
989
1036
|
|
|
990
1037
|
What the diff asserts (BLOCKER unless noted):
|
|
991
|
-
- `purchase-present` — candidate fires a purchase event
|
|
1038
|
+
- `purchase-present` — candidate fires a purchase event (`MANUAL_REVIEW`
|
|
1039
|
+
naming the page mismatch when the automatic candidate is not a receipt and
|
|
1040
|
+
fires none; see above).
|
|
992
1041
|
- `purchase-value` / `purchase-currency` — match the baseline's **client-fired**
|
|
993
1042
|
value (compared client-vs-client; never vs a backend total, since tax is
|
|
994
1043
|
computed backend and is not in the client value on headless checkouts).
|
|
@@ -1107,13 +1156,64 @@ npm run campaigns-os -- qa run \
|
|
|
1107
1156
|
--test-order common
|
|
1108
1157
|
```
|
|
1109
1158
|
|
|
1110
|
-
The default mode is **`common`** (also what bare `--test-order` runs)
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1159
|
+
The default mode is **`common`** (also what bare `--test-order` runs). It
|
|
1160
|
+
starts from the selected checkout's declared topology:
|
|
1161
|
+
|
|
1162
|
+
- When every actual terminal path (the `full` set, checkout baseline included)
|
|
1163
|
+
fits under the flood cap (`--max-test-orders`, default `6`), `common` runs
|
|
1164
|
+
them all. The run says so on stderr, and on a funnel with offer pages the
|
|
1165
|
+
coverage row below records
|
|
1166
|
+
`order_path_depth_effective: "full"` with `order_path_depth_reason:
|
|
1167
|
+
"under_cap"`.
|
|
1168
|
+
- Above the cap, or when the topology cannot be walked exhaustively, `common`
|
|
1169
|
+
runs the sample: the checkout baseline, first-offer `accept` and `decline`
|
|
1170
|
+
when `expected_next_url` reaches an upsell/downsell, and the shortest
|
|
1171
|
+
declared path that actually reaches a receipt/thank-you page (deduplicated
|
|
1172
|
+
when it is already `accept` or `decline`; Campaigns OS never invents a
|
|
1173
|
+
receipt path from offer count alone). It then adds, for each offer or
|
|
1174
|
+
downsell page whose decline no planned path clicks yet, the shortest actual
|
|
1175
|
+
terminal path that clicks it, until the plan reaches the cap. Pages still
|
|
1176
|
+
left out are named on stderr and in the coverage row. The sample itself is
|
|
1177
|
+
never trimmed, so a `--max-test-orders` below it is refused before launch as
|
|
1178
|
+
before.
|
|
1179
|
+
|
|
1180
|
+
A page counts as covered only when a path clicks its **decline** control.
|
|
1181
|
+
Reaching a page, or clicking only its accept, does not count: a broken decline
|
|
1182
|
+
link strands the shopper even when accept works.
|
|
1183
|
+
|
|
1184
|
+
Every browser test-order run whose funnels include offer pages records
|
|
1185
|
+
`browser-test-order:upsell-action-coverage`, read from the clicks the runner
|
|
1186
|
+
actually made in orders it placed, not from the plan. It lists the offer pages
|
|
1187
|
+
of every funnel in the run, including funnels the orders do not drive, and a
|
|
1188
|
+
click credits only the funnel whose order made it, never another funnel's page
|
|
1189
|
+
at the same URL. The row is conservative: it is `pass` or `warn` only when
|
|
1190
|
+
coverage is certain, and `manual_review` in every other case.
|
|
1191
|
+
|
|
1192
|
+
Coverage is certain when all of these hold: at least one order was placed;
|
|
1193
|
+
every funnel lists its pages; every page is either a known non-offer type
|
|
1194
|
+
(checkout, landing, thank-you and the like) or an upsell/downsell page with its
|
|
1195
|
+
own absolute http(s) URL that no other page shares; every planned order belongs
|
|
1196
|
+
to exactly one funnel (its checkout is that funnel's checkout and it was
|
|
1197
|
+
planned over that funnel's page list); and every click an order recorded lands
|
|
1198
|
+
on a declared offer page of that order's own funnel. Then the row is `warn`
|
|
1199
|
+
(severity `warn`) naming each page whose decline no order of its funnel
|
|
1200
|
+
clicked, and each funnel no order ran through, or `pass` when every page's
|
|
1201
|
+
decline was clicked.
|
|
1202
|
+
|
|
1203
|
+
Otherwise the row is `manual_review` (severity `warn`) naming the pages not
|
|
1204
|
+
proved clicked through, with `evidence.reason`: `test_orders_off` for a
|
|
1205
|
+
`--test-order off` run, `no_order_recorded` when no order was placed (an
|
|
1206
|
+
attempt that failed before an order reference counts as none), `no_topology`
|
|
1207
|
+
when no funnel topology reached the check, or `not_assessable` for the rest.
|
|
1208
|
+
`evidence.not_assessable[]` names each page that cannot be matched to a click,
|
|
1209
|
+
with a reason: `no_url` or `unresolvable_url`, `shared_url` (the URL belongs to
|
|
1210
|
+
another page too), `unknown_page_type` (neither an offer type nor a known
|
|
1211
|
+
non-offer type), or `no_pages` (a funnel with no page list).
|
|
1212
|
+
`evidence.uncertainty[]` lists the run-level reasons: `unattributed_plan` (a
|
|
1213
|
+
plan that does not match exactly one funnel's checkout and page list),
|
|
1214
|
+
`unattributed_click` (a click with no usable URL, or from such a plan's order)
|
|
1215
|
+
and `undeclared_click` (an order clicked a page its funnel does not declare).
|
|
1216
|
+
`evidence.pages[]` gives `accept_clicked` / `decline_clicked` per page.
|
|
1117
1217
|
|
|
1118
1218
|
Other modes: `checkout` (base order redirect only), `accept`/`decline` (click the
|
|
1119
1219
|
rendered control on the first upsell page), `both` (two fresh orders for those
|
|
@@ -1547,8 +1647,11 @@ npm run campaigns-os -- qa run \
|
|
|
1547
1647
|
```
|
|
1548
1648
|
|
|
1549
1649
|
`--max-test-orders` (default `6`) is an **accidental-flood guard, not a permission
|
|
1550
|
-
gate**. A single checkout's `common`
|
|
1551
|
-
|
|
1650
|
+
gate**. A single checkout's `common` plan always keeps its checkout,
|
|
1651
|
+
first-offer accept/decline and shortest-receipt sample. If that sample alone
|
|
1652
|
+
exceeds the cap (a `--max-test-orders` below it), the run is refused before
|
|
1653
|
+
browser launch, as before. Otherwise the added decline paths stop at the cap.
|
|
1654
|
+
Tier expansion can exceed it. If `full` expands past the cap, the command stops before
|
|
1552
1655
|
browser launch, prints the planned count, lists the planned paths (up to 40 ids;
|
|
1553
1656
|
past that the remainder is counted, never cut silently, and `--select-package
|
|
1554
1657
|
<ref[:qty]>` lists one tier's paths), and names the exact `--max-test-orders <count>` raise. For example, a linear three-offer graph has
|
|
@@ -1729,8 +1832,9 @@ use the declared topology instead of a single happy path:
|
|
|
1729
1832
|
|
|
1730
1833
|
1. Checkout-only with the base cart.
|
|
1731
1834
|
2. Checkout-only with the base cart plus bump when the bump is in scope.
|
|
1732
|
-
3. Base cart through
|
|
1733
|
-
|
|
1835
|
+
3. Base cart through `--test-order common`: every actual terminal path when
|
|
1836
|
+
they fit under the cap, otherwise the checkout/first-action sample, the
|
|
1837
|
+
shortest real receipt path and one decline path per uncovered offer page.
|
|
1734
1838
|
4. Base plus bump cart through the same sample matrix when bump behavior is
|
|
1735
1839
|
launch-relevant.
|
|
1736
1840
|
5. Use `full` when you want every actual terminal path, raising the flood cap to
|
|
@@ -146,7 +146,7 @@ incomplete, append a correction:
|
|
|
146
146
|
"amends": "RL-0007",
|
|
147
147
|
"amendment_reason": "RL-0007 was recorded as compatible; it removed a documented guarantee.",
|
|
148
148
|
"surface_version": null,
|
|
149
|
-
"changelog_section": "1.
|
|
149
|
+
"changelog_section": "1.16.0+agent.1",
|
|
150
150
|
"compatibility": "breaking",
|
|
151
151
|
"migration": "Stop relying on the removed guarantee; see docs/build-packet.md.",
|
|
152
152
|
"agent_impact": "Treat the 1.15.0 packet doc change as breaking, not compatible.",
|
|
@@ -154,6 +154,10 @@ incomplete, append a correction:
|
|
|
154
154
|
}
|
|
155
155
|
```
|
|
156
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
|
+
|
|
157
161
|
An amendment is the only entry kind whose change items may map to no changed
|
|
158
162
|
path in its own range, because it corrects meaning rather than moving bytes.
|
|
159
163
|
|
|
@@ -171,13 +175,25 @@ entry currently holding the link, may re-link a section; any other second link
|
|
|
171
175
|
still fails the one-to-one rule. Say in `amendment_reason` what changed in the
|
|
172
176
|
section and why.
|
|
173
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
|
+
|
|
174
185
|
`scripts/check-changelog-structure.mjs` (part of `npm run check`) refuses the
|
|
175
186
|
marker lines outright, in `CHANGELOG.md` and under `docs/`, and also holds the
|
|
176
187
|
section layout: identifiers unique, `+agent.N` sections in one run directly
|
|
177
188
|
above their release with N descending (newest first), and every ledger
|
|
178
|
-
`changelog_section` naming a section that exists.
|
|
179
|
-
|
|
180
|
-
|
|
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.
|
|
181
197
|
|
|
182
198
|
## Running the gate
|
|
183
199
|
|
|
@@ -274,3 +290,47 @@ a quiet raise.
|
|
|
274
290
|
|
|
275
291
|
Raising a limit is a reviewed policy change: advance `limits_version`, and the
|
|
276
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.47.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. A release manifest covers the target range it declares, which may end below its own SDK version: the v0.4.40 manifest, for example, covers 0.4.38 only. Targets outside the manifest's supported SDK range report unknown. A manifest whose SDK version is below its supported maximum is invalid.
|
|
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.47.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.47.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.47.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.47.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.47.0+skills.1",
|
|
94
94
|
"spelling": "bundle",
|
|
95
|
-
"on_disk": "1.
|
|
95
|
+
"on_disk": "1.47.0+skills.1",
|
|
96
96
|
"on_disk_skill": null,
|
|
97
|
-
"message": "match (1.
|
|
97
|
+
"message": "match (1.47.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.47.0+skills.1)
|
|
105
|
+
Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.47.0+skills.1 — start a fresh session
|
|
106
|
+
Skills revision: unchecked (on disk 1.47.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
|