@nextcommerce/campaigns-os 1.37.2 → 1.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/AGENTS.md +115 -11
  2. package/CHANGELOG.md +556 -0
  3. package/README.md +43 -36
  4. package/contracts/agent-relevant-change-policy.v1.json +11 -1
  5. package/contracts/effects.v1.json +4794 -0
  6. package/contracts/release-ledger.json +873 -0
  7. package/contracts/supported-surface.json +25 -5
  8. package/docs/activation-and-evidence.md +1 -1
  9. package/docs/build-packet.md +27 -16
  10. package/docs/demo-preview.md +3 -4
  11. package/docs/diagnostics.md +7 -4
  12. package/docs/effects.md +281 -0
  13. package/docs/gateway-login.md +113 -0
  14. package/docs/orientation-contract-reference.md +4 -1
  15. package/docs/progress-snapshots.md +3 -3
  16. package/docs/qa-and-test-orders.md +3 -3
  17. package/docs/readback.md +523 -0
  18. package/docs/runtime-readiness.md +1 -1
  19. package/docs/sdk-storage-compatibility.md +1 -1
  20. package/docs/skills-revision.md +364 -0
  21. package/docs/supported-surface.md +12 -4
  22. package/docs/versioning.md +8 -4
  23. package/package.json +8 -3
  24. package/schemas/campaign-runtime-build-packet.v0.schema.json +5 -0
  25. package/schemas/campaigns-os-effects.v1.schema.json +211 -0
  26. package/schemas/campaigns-os-readback.v2.schema.json +267 -0
  27. package/skills/campaign-lifecycle-orientation/SKILL.md +174 -0
  28. package/skills/campaign-readback-classification/SKILL.md +230 -0
  29. package/skills/campaign-run-evidence/SKILL.md +140 -0
  30. package/skills/contribution-intake/SKILL.md +85 -0
  31. package/skills/next-campaigns-build/SKILL.md +33 -12
  32. package/skills/next-campaigns-os/SKILL.md +45 -21
  33. package/skills/next-campaigns-os-setup/SKILL.md +35 -14
  34. package/skills/next-campaigns-polish/SKILL.md +43 -17
  35. package/skills/next-campaigns-qa/SKILL.md +48 -24
  36. package/skills.json +39 -6
  37. package/src/admin-transport.mjs +123 -0
  38. package/src/cli.mjs +991 -200
  39. package/src/credential-store.mjs +183 -0
  40. package/src/deviation.mjs +3 -2
  41. package/src/diagnostic.mjs +4 -1
  42. package/src/gate-actions.mjs +2 -2
  43. package/src/install-mode.mjs +17 -9
  44. package/src/lifecycle.mjs +95 -0
  45. package/src/login.mjs +152 -0
  46. package/src/package-install-fixture.mjs +3 -2
  47. package/src/qa-node.mjs +56 -19
  48. package/src/qa-publish.mjs +108 -2
  49. package/src/readback.mjs +1936 -0
  50. package/src/remit.mjs +17 -3
@@ -0,0 +1,364 @@
1
+ # Skills bundle revision
2
+
3
+ The skills Campaigns OS bundles (nine as of 1.40.0) are installed into shared agent skill
4
+ directories and then **read into an agent's context once**, at the start of a
5
+ task. They are not re-read afterwards. The CLI underneath that session, however,
6
+ can be replaced at any moment — an `npm install` in the campaign folder, an
7
+ `npx` cache refresh, a `git pull` in a toolkit checkout.
8
+
9
+ That is the drift this document is about: an agent following instructions from
10
+ one release while calling a CLI from another. Nothing in the per-skill versions
11
+ catches it, because the agent has no reason to look at them and no way to notice
12
+ that the copy on disk moved.
13
+
14
+ ## The identity
15
+
16
+ `skills.json` carries one top-level field:
17
+
18
+ ```json
19
+ "bundle_revision": "1.41.2+skills.1"
20
+ ```
21
+
22
+ The spelling is `<package version>+skills.<n>`:
23
+
24
+ - the **prefix** is this package's `version`, so a revision names the release its
25
+ skills ship with (`check-skill-versions.mjs` fails if the two disagree);
26
+ - `<n>` is a plain counter, not a semver component. It says "this is the *n*th
27
+ skill-text revision published against that package version" and it **resets
28
+ with the prefix**. `1.41.2+skills.1` is therefore ahead of `1.40.0+skills.7`.
29
+
30
+ It is one identity for the bundle as a whole, on purpose. Per-skill versions
31
+ still exist and still gate per-skill changes, but an agent that loaded one skill
32
+ cannot reconcile five versions; it can quote back one string.
33
+
34
+ ## The header line
35
+
36
+ The first body line of every bundled `SKILL.md`, immediately after the
37
+ frontmatter, is exactly:
38
+
39
+ ```
40
+ Bundle revision: 1.41.2+skills.1
41
+ ```
42
+
43
+ followed by a short paragraph telling the agent to run the check below at the
44
+ start of each task, through the project's pinned copy, and what an older copy's
45
+ answer looks like. This line is the value an agent has in hand: it comes from the
46
+ text the agent is actually reading, not from a file it would have to go and open.
47
+
48
+ ## The check
49
+
50
+ ```bash
51
+ npx --no-install campaigns-os tooling status --skills-revision 1.41.2+skills.1
52
+ ```
53
+
54
+ The value is compared against the bundle revision of the **CLI the command runs
55
+ from** — the `skills.json` inside the installed package, not the working
56
+ directory, which in a campaign repo has no `skills.json` at all.
57
+
58
+ Run it from the campaign's Page Kit folder, where `npx --no-install campaigns-os`
59
+ resolves the project's exact devDependency. `--no-install` keeps it there: the published
60
+ package is `@nextcommerce/campaigns-os` and `campaigns-os` is only its bin, so
61
+ outside a pinned folder a plain `npx campaigns-os` looks the bin name up as a
62
+ package on the registry and, without a terminal to ask, installs whatever it
63
+ finds. With `--no-install` it stops instead. A bare `campaigns-os` resolves
64
+ through PATH, and a machine that once installed the toolkit globally can answer
65
+ with that older copy.
66
+ A copy older than 1.40.0 does not know `--skills-revision`: it ignores the flag,
67
+ prints no `Skills revision:` line (and no `revision_check` under `--json`), and
68
+ may list actions of its own, such as an `install-skills` that replaces part of
69
+ this bundle with its older text. The revision comparison cannot see the result,
70
+ because the header an agent quotes still names this bundle; the pinned copy's
71
+ own freshness check does, and reports the replaced skills as stale. The skill
72
+ header says so: no `Skills revision:` line (no `revision_check` under `--json`)
73
+ means the pinned copy did not answer, and none of that output's actions should
74
+ be followed.
75
+
76
+ Without `--platform` or `--target`, the skill freshness part of the report checks
77
+ only the platform directories where Campaigns OS skills are installed (a skill
78
+ under one of the bundled names, or our own copy under a retired name), and a `Ready:`
79
+ line names the platforms it skipped. A Claude Code only install is therefore not
80
+ reported stale for Codex or the shared directory, and a stale install's refresh
81
+ action names each stale platform. `--platform all` checks all three, as it always
82
+ has. When no platform has Campaigns OS skills, the action asks for an install on
83
+ the harness in use (`install-skills --platform claude`, or `codex` / `agents`). `--json`
84
+ reports the choice as `skills.scope` (`requested`, `installed_platforms`, or
85
+ `no_platform_installed`) with `skills.not_installed_platforms`.
86
+
87
+ `--json` reports a bare status string alongside the detail:
88
+
89
+ ```json
90
+ "revision_check": "match",
91
+ "skills_revision": {
92
+ "status": "match",
93
+ "requested": "1.41.2+skills.1",
94
+ "spelling": "bundle",
95
+ "on_disk": "1.41.2+skills.1",
96
+ "on_disk_skill": null,
97
+ "message": "match (1.41.2+skills.1)"
98
+ }
99
+ ```
100
+
101
+ The text view prints one named line, as a header above the rest of the status:
102
+
103
+ ```
104
+ Skills revision: match (1.41.2+skills.1)
105
+ Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.41.2+skills.1 — start a fresh session
106
+ Skills revision: unchecked (on disk 1.41.2+skills.1)
107
+ ```
108
+
109
+ `unchecked` is the state when the flag is absent. It is not an error — an
110
+ operator running preflight by hand has no revision to offer — and the on-disk
111
+ revision is reported anyway, so the run still says what is installed.
112
+
113
+ ## Why the reported revision is named "on disk"
114
+
115
+ Because the two sides of the comparison are not symmetric, and naming them
116
+ symmetrically would hide the remedy.
117
+
118
+ The requested value is text **already in context**. Re-running the command
119
+ cannot change it; nothing can, short of a new session. The reported value is
120
+ what is **installed right now**, and it is the side that moved. So the field is
121
+ `on_disk`, the mismatch line says "on disk", and the remedy is not "update
122
+ something" but "start a fresh session" — a fresh session is what re-reads the
123
+ skill text.
124
+
125
+ This also explains why a mismatch is not merely a warning. A session that keeps
126
+ going is following instructions for a CLI that is no longer there.
127
+
128
+ ## Fallback spelling
129
+
130
+ An agent that carries only the frontmatter of the single skill it loaded can
131
+ pass that instead:
132
+
133
+ ```bash
134
+ npx --no-install campaigns-os tooling status --skills-revision next-campaigns-qa@1.3.4
135
+ ```
136
+
137
+ The version is checked against that skill's entry in the manifest, and the
138
+ bundle revision is still reported beside it. A skill id this bundle does not
139
+ ship reports `mismatch`, not a refusal: an agent quoting a skill that is not here
140
+ is reading text from some other bundle, which is the condition the flag exists to
141
+ catch.
142
+
143
+ ## Exit codes
144
+
145
+ | Outcome | Exit |
146
+ | --- | --- |
147
+ | `match` (and the rest of the status is clean) | `0` |
148
+ | `unchecked` (and the rest of the status is clean) | `0` |
149
+ | `mismatch` | `2`, after the full status has printed |
150
+ | `--skills-revision` given with no value | refused, non-zero, nothing inspected |
151
+
152
+ A mismatch prints the whole status first and sets the exit code afterwards: the
153
+ revision line is a header on the report, never a replacement for it. It also
154
+ adds an explicit action naming the fresh session, so a reader of `actions[]`
155
+ sees the remedy without parsing the header.
156
+
157
+ `tooling status` exits `2` for other reasons too (skills needing a refresh, a
158
+ checkout behind its upstream). Branch on `revision_check`, not on the exit code,
159
+ when you need to know which one happened.
160
+
161
+ ## The gate
162
+
163
+ `scripts/check-skill-versions.mjs` enforces both halves:
164
+
165
+ - **without `--base`** (this runs inside `npm run check`): `bundle_revision`
166
+ exists, is spelled correctly, and its prefix is `package.json`'s `version`.
167
+ - **with `--base <ref>`**: if any file under `skills/` changed since the base, or
168
+ if `skills.json`'s `skills[]` entries changed, `bundle_revision` must have
169
+ **advanced** — a newer prefix, or the same prefix with a higher counter. Equal
170
+ fails, and so does backwards.
171
+
172
+ The changed set is the union of what changed since the base, what is in the
173
+ working tree, and what is untracked, the same three-way union the release-ledger
174
+ gate measures. A committed-only diff answers the wrong question for a bump gate:
175
+ an unstaged `SKILL.md` edit is exactly the state a local run is asked about.
176
+
177
+ The failure this produces is the point of the whole mechanism: skill text moved,
178
+ the identity an agent quotes back did not, and a stale agent would have been told
179
+ it was current.
180
+
181
+ ## The pin check
182
+
183
+ The skills revision says which **text** an agent is reading. The pin says which
184
+ **executable** the project runs. ADR 0002 allows one executable per project,
185
+ and `tooling status` reports it on every run, alongside the revision check.
186
+
187
+ ### Sources and precedence
188
+
189
+ 1. **The project pin.** An entry for `@nextcommerce/campaigns-os` in
190
+ `devDependencies` or `dependencies`. Only an exact version is a pin:
191
+ `1.41.0`, and the forms npm reads as the same exact version, `=1.41.0` and
192
+ `v1.41.0` (reported as `project_version: "1.41.0"`). A range or a tag
193
+ (`^1.40.0`, `latest`, a git spec) names no single executable, so it is
194
+ reported under `range` and counts as no project pin. An empty or
195
+ whitespace-only spec names nothing at all: it is treated as absent, never
196
+ as a range, so it neither sets `range` nor hides a spec in the other key.
197
+ A `git+https://…#v1.0.0` or `file:../x` spec is likewise `unpinned`, with
198
+ the spec in `range`, and an exact spec in `dependencies` beside such a
199
+ `devDependencies` spec still wins.
200
+ `peerDependencies` and `optionalDependencies` are never a pin: ADR 0002 puts
201
+ the pin in `devDependencies`, and `dependencies` is read because it installs
202
+ the same executable.
203
+
204
+ The resolver starts at the nearest `package.json` above the working
205
+ directory (or above the packet's target repository when `--packet` is
206
+ given) and walks up toward the filesystem root. In each manifest it reads
207
+ `devDependencies`, then `dependencies`; the **first exact spec wins** and
208
+ ends the walk. A range does not end it: it is remembered, and
209
+ reported as `range` only if no exact spec turns up anywhere on the walk. So
210
+ a range in `devDependencies` never hides an exact pin in `dependencies`,
211
+ and a workspace package without an exact spec resolves its workspace root's
212
+ pin. A manifest that names the package in neither key and declares no
213
+ `workspaces` is neutral: it cannot supply a pin, so the walk goes on
214
+ through it, and a package nested inside a workspace package still reaches
215
+ the root's pin. The walk ends after a workspace root (a manifest declaring
216
+ `workspaces`, as an array or as `{ "packages": [...] }`), so a
217
+ `package.json` above a workspace root, even one with an exact pin, is never
218
+ read. It also ends at the filesystem root. One residual: outside a
219
+ workspace, a stray ancestor manifest that names the package (a
220
+ `package.json` in a home directory, say) is read when nothing exact turns
221
+ up below it, because the walk cannot tell it from the project's own root.
222
+
223
+ An installed package's own manifest is never the project: a `package.json`
224
+ whose directory sits directly in `node_modules`
225
+ (`node_modules/<name>/package.json`) or in a scope inside it
226
+ (`node_modules/@<scope>/<name>/package.json`) is skipped, so a run from
227
+ inside an install (for example
228
+ `<project>/node_modules/@nextcommerce/campaigns-os`) resolves the enclosing
229
+ project, packet home included, as if the working directory were that
230
+ project. Any other manifest is a candidate, even when a directory higher up
231
+ its path is named `node_modules` (`…/node_modules/work/site/package.json`
232
+ is its own project). A leading UTF-8 BOM is accepted, as npm accepts it. A
233
+ manifest on the walk that cannot be read, is not valid JSON or is not a
234
+ JSON object ends the walk with a warning naming the file (`Project pin
235
+ walk stopped at <path>: it is not valid JSON.`; for the nearest manifest,
236
+ `Project pin unavailable: <path> …`), since it might have held the pin.
237
+
238
+ `pin.project_manifest` is the manifest the pin (or range) came from, or the
239
+ nearest manifest when there is neither; `pin.project_key` is
240
+ `"devDependencies"`, `"dependencies"` or `null`. The `Pin:` line
241
+ (`pin.message`) names the key and manifest of every project version or
242
+ range it quotes (`devDependencies in <project>/package.json`), the nearest
243
+ manifest when it reports no project pin, and the packet file of every packet
244
+ version it quotes (`campaigns_os_version in
245
+ <project>/campaign-runtime.build.json`). Every action names the same
246
+ manifest and key: a pin read from `dependencies` says `set
247
+ dependencies[...]`.
248
+ 2. **The packet's recorded kernel version.** The Build Packet's optional
249
+ top-level `campaigns_os_version`, which `prepare-build` stamps with the
250
+ version that prepared it. The field is a bare `x.y.z` version (a
251
+ prerelease or build suffix allowed): the packet schema admits no `=` or `v`
252
+ prefix, so a packet value such as `=1.41.0` or `v1.41.0` is ignored with a
253
+ warning, reported verbatim as `pin.packet_version_ignored` (otherwise
254
+ `null`; a non-string value as its JSON text), and, when there is no project
255
+ pin, named on the `Pin:` line as ignored rather than absent. The
256
+ packet read is the project's `campaign-runtime.build.json` beside that
257
+ `package.json` (its contracted home), or the file `--packet <path>` names.
258
+ A missing packet, or one written before the field existed, is no packet
259
+ source.
260
+
261
+ The packet is read from beside the nearest `package.json`, whichever manifest
262
+ the project pin came from. The **running** version is the `package.json` of the
263
+ CLI the command runs from.
264
+ When both sources are present and equal, `source` is `"project"`.
265
+
266
+ ### The four statuses
267
+
268
+ | `pin.status` | When | Exit |
269
+ | --- | --- | --- |
270
+ | `match` | the pin (project first, packet second) is the running version | `0` (if the rest of the status is clean) |
271
+ | `stale_pin` | the pin is not the running version | `2`, with an action naming the file to change |
272
+ | `conflicting_pin` | the project pin and the packet version are both present and differ | `2`, with an action naming both files |
273
+ | `unpinned` | neither source is present (a range alone is not a pin) | `0` (if the rest of the status is clean), always reported |
274
+
275
+ `conflicting_pin` wins over `stale_pin`: two sources that disagree are reported
276
+ as a disagreement even when one of them is the running version.
277
+
278
+ ### `--force`
279
+
280
+ `--force` overrides `stale_pin` and `conflicting_pin`: the command then exits as
281
+ the rest of the status dictates, the pin line and `pin.message` say
282
+ `(overridden by --force)`, `pin.forced` is `true`, and a warning replaces the
283
+ action. `forced` is `true` only when the flag overrode something; on `match` or
284
+ `unpinned` it stays `false`. The override is recorded on the command-lifecycle
285
+ journal entry: `--force` appears in its `argv_shape` whenever a journal is
286
+ selected (an active run session, `--lifecycle-journal`, or
287
+ `CAMPAIGNS_OS_LIFECYCLE_LOG`). It is a bare flag and off by default. `--force true`
288
+ and `--no-force` are refused before anything is inspected, and a refused
289
+ invocation writes no journal entry.
290
+
291
+ ```bash
292
+ campaigns-os tooling status --json
293
+ campaigns-os tooling status --packet ./campaign-runtime.build.json --force
294
+ ```
295
+
296
+ ### Output
297
+
298
+ Taken from real runs of a 1.41.0 install inside a fixture project. `--json`
299
+ carries the full object:
300
+
301
+ ```json
302
+ "pin": {
303
+ "source": "project",
304
+ "version": "1.41.0",
305
+ "running": "1.41.0",
306
+ "status": "conflicting_pin",
307
+ "range": null,
308
+ "packet_version": "1.40.0",
309
+ "packet_version_ignored": null,
310
+ "project_version": "1.41.0",
311
+ "project_manifest": "<project>/package.json",
312
+ "project_key": "devDependencies",
313
+ "forced": false,
314
+ "message": "conflicting_pin — project pins 1.41.0 (devDependencies in <project>/package.json), packet records 1.40.0 (campaigns_os_version in <project>/campaign-runtime.build.json)"
315
+ }
316
+ ```
317
+
318
+ ```json
319
+ "pin": {
320
+ "source": null,
321
+ "version": null,
322
+ "running": "1.41.0",
323
+ "status": "unpinned",
324
+ "range": "^1.40.0",
325
+ "packet_version": null,
326
+ "packet_version_ignored": null,
327
+ "project_version": null,
328
+ "project_manifest": "<project>/package.json",
329
+ "project_key": "devDependencies",
330
+ "forced": false,
331
+ "message": "unpinned (project range ^1.40.0 (devDependencies in <project>/package.json) is not an exact version; no packet version)"
332
+ }
333
+ ```
334
+
335
+ The text view prints one named line under the skills revision line, naming
336
+ where each version it quotes was read:
337
+
338
+ ```
339
+ Pin: match (1.41.0 — devDependencies in <project>/package.json)
340
+ Pin: match (1.41.0 — dependencies in <project>/package.json)
341
+ Pin: match (1.41.0 — campaigns_os_version in <project>/campaign-runtime.build.json)
342
+ Pin: stale_pin — project pins 1.40.0 (devDependencies in <project>/package.json), running 1.41.0
343
+ Pin: stale_pin — packet records 1.40.0 (campaigns_os_version in <project>/campaign-runtime.build.json), running 1.41.0
344
+ Pin: stale_pin — project pins 1.40.0 (devDependencies in <project>/package.json), running 1.41.0 (overridden by --force)
345
+ Pin: conflicting_pin — project pins 1.41.0 (devDependencies in <project>/package.json), packet records 1.40.0 (campaigns_os_version in <project>/campaign-runtime.build.json)
346
+ Pin: unpinned (no project pin in <project>/package.json; no packet version)
347
+ Pin: unpinned (no project pin in <project>/package.json; no campaigns_os_version in <project>/campaign-runtime.build.json)
348
+ Pin: unpinned (no project pin in <project>/package.json; campaigns_os_version "v1.41.0" in <project>/campaign-runtime.build.json is not a bare x.y.z version and was ignored)
349
+ Pin: unpinned (project range ^1.40.0 (devDependencies in <project>/package.json) is not an exact version; no packet version)
350
+ ```
351
+
352
+ and, for a blocking status, an action such as:
353
+
354
+ ```
355
+ - Align the project pin: set devDependencies["@nextcommerce/campaigns-os"] in <project>/package.json to 1.40.0, or re-run prepare-build with 1.41.0 so <project>/campaign-runtime.build.json records it. Pass --force to proceed anyway (recorded).
356
+ ```
357
+
358
+ Branch on `pin.status`, not on the exit code: `tooling status` exits `2` for the
359
+ other reasons above as well.
360
+
361
+ This document is an entry in
362
+ [`contracts/supported-surface.json`](../contracts/supported-surface.json)
363
+ `named[]` as of 1.40.0: it ships in the npm pack, the CLI help points at it,
364
+ and a consumer may depend on it at this path.
@@ -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` | 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 four registered gates: `page_kit.store_profile`, `page_kit.sdk_version`, `polish.hidden_eager_media`, and `built_output.upsell_selector_scope`. `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. |
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 four registered gates: `page_kit.store_profile`, `page_kit.sdk_version`, `polish.hidden_eager_media`, and `built_output.upsell_selector_scope`. `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. |
@@ -22,7 +22,7 @@ implementation detail, however stable it looks.
22
22
  | Package export `./legacy-migration` and its three schema exports | Portable SDK 0.3.x migration inventory, preview-plan, receipt, Offer request/readback, and token-free evidence helpers. Guide: [`docs/legacy-migration.md`](legacy-migration.md). | Pure contract only: no authenticated transport, write executor, audit store, receipt store, sessions, deletes, or rollback. Consumers own execution and must retain the guarded apply protocol. |
23
23
  | Package export `./text-safety` | `singleLineField`, `singleLineFragment` and `singleLineDetail`: flatten a value this toolkit did not author to one line with no control characters before it is rendered into a single-line notice. `singleLineField` is for a value printed as its own field (a run id, a target path): every control character becomes U+FFFD and nothing else changes. `singleLineFragment` is for a value folded into a sentence (a gate's repair command or instruction): line breaks and tabs become spaces, runs of whitespace collapse, the ends are trimmed, and every other control character becomes U+FFFD. `singleLineDetail` is `singleLineFragment` plus Markdown escaping, a length cap and a placeholder for an empty value (a quoted loader message). | Pure string functions, no imports, no I/O. Added at surface 1.27.0; `singleLineFragment` added at 1.27.0+agent.2. The escape set may widen; a value that was already safe stays unchanged. |
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
- | `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. |
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
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. |
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. |
@@ -83,14 +83,22 @@ the package a consumer installs."
83
83
  `public-contracts.manifest.json`) update on their own cadence against a
84
84
  version they can see move.
85
85
 
86
- Candidate 1.36.0 adds the portable `./progress` export, its strict v0 JSON schema,
86
+ 1.36.0 (published in 1.37.1 and later) adds the portable `./progress` export, its strict v0 JSON schema,
87
87
  [progress observation reference](progress-snapshots.md), and supported example
88
88
  fixture. Observations preserve canonical continuation and separate Map/spec/build
89
89
  identities; consumers must not treat history presence or scope-key matching as
90
90
  readiness or trust.
91
91
 
92
- Candidate 1.37.0 adds the `demo` CLI command, [offline sample reference](demo-preview.md),
92
+ 1.37.0 adds the `demo` CLI command, [offline sample reference](demo-preview.md),
93
93
  a hashed `demo/apollo-v0/provenance.json` and named attribution notice. The command
94
94
  exclusively creates a new target with validated inert pages. Its internal static
95
95
  files are covered by the provenance output hashes; they are not independent
96
96
  consumer interfaces. No campaign proof or session authority is granted.
97
+
98
+ The 1.38.0 candidate adds `login` and `logout` for the admitted owned-store
99
+ private gateway pilot. It also changes `spec derive --from-store` from an
100
+ implicit Admin-token environment variable to gateway login credentials. This
101
+ is a breaking default change: existing direct callers must explicitly select
102
+ `--store-token-source env:<VAR>` or migrate to an admitted gateway login. See
103
+ [gateway login and migration](gateway-login.md). This candidate does not
104
+ authorize publication, general merchant availability, or external client trials.
@@ -2,10 +2,14 @@
2
2
 
3
3
  This repo uses independent compatibility versions:
4
4
 
5
- - package version: `1.34.0` — equals `surface_version` in
6
- `contracts/supported-surface.json` (`check:supported-surface` enforces it)
7
- and is the version published to the npm registry; `+agent.N` changelog
8
- sections are same-surface changes and are not published on their own
5
+ - package version: the `version` in `package.json`, which equals
6
+ `surface_version` in `contracts/supported-surface.json`
7
+ (`check:supported-surface` enforces it) and is the latest release published
8
+ to the npm registry. This document does not restate the number: read it from
9
+ either file, or from `npm view @nextcommerce/campaigns-os version`.
10
+ `+agent.N` changelog sections are same-surface changes and are not published
11
+ on their own; a version with a changelog section but no tag ships inside the
12
+ next published release
9
13
  - Build Packet: `campaign-runtime-build-packet/v0`
10
14
  - Build Context: `campaign-runtime-build-context/v0`
11
15
  - Assembly Report: `campaign-runtime-assembly-report/v0`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextcommerce/campaigns-os",
3
- "version": "1.37.2",
3
+ "version": "1.41.2",
4
4
  "description": "Toolkit for agent-assisted NEXT campaign builds.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -64,7 +64,7 @@
64
64
  "build:spec": "tsc -p campaign-spec/tsconfig.build.json",
65
65
  "prepare": "npm run build:spec",
66
66
  "check:spec": "node --test \"campaign-spec/test/**/*.test.ts\"",
67
- "check": "npm run check:provenance && npm run check:workflows && npm run check:changelog-structure && npm run build:spec && npm run check:tests && npm run check:spec && npm run check:fixtures && npm run check:legacy-migration && npm run check:spec-conformance && npm run check:private-strings && npm run check:template-doctrine && npm run check:slot-manifest && npm run check:skill-versions && npm run check:cart-readiness && npm run check:sidecar-bundle && npm run check:supported-surface && npm run check:release-ledger && npm run check:runtime-recipe && npm run check:orientation-docs && npm run check:runtime-docs && npm run check:pack -- --skip-prepare",
67
+ "check": "npm run check:provenance && npm run check:workflows && npm run check:changelog-structure && npm run build:spec && npm run check:tests && npm run check:spec && npm run check:fixtures && npm run check:legacy-migration && npm run check:spec-conformance && npm run check:private-strings && npm run check:template-doctrine && npm run check:slot-manifest && npm run check:skill-versions && npm run check:cart-readiness && npm run check:sidecar-bundle && npm run check:supported-surface && npm run check:effects && npm run check:release-ledger && npm run check:runtime-recipe && npm run check:orientation-docs && npm run check:runtime-docs && npm run check:pack -- --skip-prepare",
68
68
  "check:tests": "node ./scripts/check-tests.mjs",
69
69
  "check:legacy-migration": "node ./scripts/check-legacy-migration.mjs",
70
70
  "check:fixtures": "node ./scripts/check-fixtures.mjs",
@@ -78,6 +78,7 @@
78
78
  "check:workflows": "node ./scripts/check-workflow-contracts.mjs",
79
79
  "check:skill-versions": "node ./scripts/check-skill-versions.mjs",
80
80
  "check:supported-surface": "node ./scripts/check-supported-surface.mjs",
81
+ "check:effects": "node ./scripts/check-effects.mjs",
81
82
  "check:release-ledger": "node ./scripts/check-release-ledger.mjs",
82
83
  "check:changelog-structure": "node ./scripts/check-changelog-structure.mjs",
83
84
  "check:orientation-docs": "node ./scripts/generate-orientation-reference.mjs --check",
@@ -88,7 +89,7 @@
88
89
  "check:sidecar-bundle": "node ./bin/campaigns-os.mjs bundle check --packet contracts/fixtures/sidecar-bundle/production-shaped/campaign-runtime.build.json --require-qa --json",
89
90
  "check:browser": "node ./scripts/check-tests.mjs --browser",
90
91
  "check:consumer": "node ./scripts/check-playwright-consumer.mjs",
91
- "check:contracts": "npm run check:provenance && npm run check:workflows && npm run check:changelog-structure && npm run check:fixtures && npm run check:legacy-migration && npm run check:spec-conformance && npm run check:private-strings && npm run check:template-doctrine && npm run check:slot-manifest && npm run check:skill-versions && npm run check:cart-readiness && npm run check:sidecar-bundle && npm run check:supported-surface && npm run check:release-ledger && npm run check:runtime-recipe && npm run check:orientation-docs && npm run check:runtime-docs"
92
+ "check:contracts": "npm run check:provenance && npm run check:workflows && npm run check:changelog-structure && npm run check:fixtures && npm run check:legacy-migration && npm run check:spec-conformance && npm run check:private-strings && npm run check:template-doctrine && npm run check:slot-manifest && npm run check:skill-versions && npm run check:cart-readiness && npm run check:sidecar-bundle && npm run check:supported-surface && npm run check:effects && npm run check:release-ledger && npm run check:runtime-recipe && npm run check:orientation-docs && npm run check:runtime-docs"
92
93
  },
93
94
  "engines": {
94
95
  "node": ">=20.19.0"
@@ -112,15 +113,19 @@
112
113
  "contracts",
113
114
  "docs/brand-theme-bridge.md",
114
115
  "docs/build-packet.md",
116
+ "docs/gateway-login.md",
115
117
  "docs/campaign-build-brief.md",
116
118
  "docs/campaign-standardization-report.md",
117
119
  "docs/campaigns-os-build-flow.md",
118
120
  "docs/design-source-package.md",
121
+ "docs/effects.md",
119
122
  "docs/legacy-migration.md",
120
123
  "docs/migration-sidecar-bundle.md",
121
124
  "docs/orientation-contract-reference.md",
122
125
  "docs/polish-evidence.md",
123
126
  "docs/qa-and-test-orders.md",
127
+ "docs/readback.md",
128
+ "docs/skills-revision.md",
124
129
  "docs/release-ledger-authoring-guide.md",
125
130
  "docs/runtime-readiness.md",
126
131
  "docs/supported-surface.md",
@@ -14,6 +14,11 @@
14
14
  "format": "date-time",
15
15
  "description": "UTC instant this packet was generated by prepare-build, ISO-8601 with a Z suffix. Optional for packets generated before it existed; always stamped on new packets. Downstream freshness (campaigns-agent readback staleness and multi-packet selection) reads this field, never file mtime."
16
16
  },
17
+ "campaigns_os_version": {
18
+ "type": "string",
19
+ "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$",
20
+ "description": "The Campaigns OS package version that prepared this packet, stamped by prepare-build. Optional for packets generated before it existed; always stamped on new packets. A bare x.y.z version (a prerelease or build suffix allowed, no `=` or `v` prefix). `tooling status` reads it as the second pin source, after the project pin (the first exact spec for @nextcommerce/campaigns-os on the walk up from the nearest package.json, devDependencies then dependencies in each, entering a workspace root and stopping there; never peerDependencies or optionalDependencies): both present and different is conflicting_pin, and this alone decides the pin when the project declares none."
21
+ },
17
22
  "campaign": {
18
23
  "type": "object",
19
24
  "additionalProperties": false,