@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,562 @@
2
2
 
3
3
  Notable supported-surface changes are recorded here.
4
4
 
5
+ ## [1.41.2] - 2026-09-23
6
+
7
+ ### Fixed
8
+
9
+ - Every command Campaigns OS prints for a project-local install is spelled
10
+ `npx --no-install campaigns-os …`, and so is every command in the bundled
11
+ skills, the README, `AGENTS.md` and the docs. 1.41.1 made this change only
12
+ for the revision check in the skill header. `campaigns-os` is only the bin
13
+ name of `@nextcommerce/campaigns-os`. In a folder where the package is not
14
+ installed (another folder, or one where `npm install` has not run yet), a
15
+ plain `npx campaigns-os …` looks the bin name up as a registry package and,
16
+ with no terminal to ask, installs whatever it finds and runs it. With
17
+ `--no-install`, npx runs the pinned copy or stops with an error.
18
+ - For a `node_modules` install, `tooling status` reports
19
+ `cli.invocation_prefix` as `npx --no-install campaigns-os` and
20
+ `cli.invocation` as `npx --no-install campaigns-os <command>`.
21
+ - Every command spelled with that prefix follows: `next` text and `--json`,
22
+ doctor required actions, gate and checkpoint remediations, the
23
+ skill-refresh and gateway login actions of `tooling status`, and the
24
+ browser-missing hints.
25
+ - The PATH warnings of `tooling status` and its action for a stale project
26
+ pin name the same spelling.
27
+ - A checkout, a global install and an npx cache keep their spellings.
28
+ - Run-session deviation tracking reads the command word through the new
29
+ prefix, and still through the old one in sessions recorded by earlier
30
+ versions.
31
+
32
+ Commands printed or documented by earlier releases lack the flag; add
33
+ `--no-install` after `npx` when you reuse one.
34
+ - Skills bundle revision `1.41.2+skills.1`. Every skill's version advances by
35
+ one patch.
36
+
37
+ ## [1.41.1] - 2026-09-23
38
+
39
+ ### Fixed
40
+
41
+ - `tooling status` without `--platform` or `--target` checks skill freshness
42
+ only on the platform directories where Campaigns OS skills are installed. A
43
+ directory counts when a skill sits under one of the bundled names or under a
44
+ retired name when it is our own copy. Before this, a Claude Code only install (the
45
+ documented path) was read as stale for Codex and the shared directory, so
46
+ the revision check the skills ask for exited 2 and printed an action to
47
+ install skills for every platform. Now:
48
+ - A `Ready:` line names the skipped platforms.
49
+ - The refresh action names each stale installed platform (`install-skills
50
+ --platform claude`), or `--platform all` when all three are installed and
51
+ stale.
52
+ - When no platform has Campaigns OS skills, the action asks for an install
53
+ on the harness in use (`install-skills --platform claude`, with `codex` and
54
+ `agents` named as the alternatives)
55
+ rather than on all three.
56
+ - `--platform all` still checks every platform.
57
+ - `--json` adds `skills.scope` (`requested`, `installed_platforms` or
58
+ `no_platform_installed`) and `skills.not_installed_platforms`.
59
+ - `tooling diagnose` forwards `--platform` only when one is given, and its
60
+ export reports the unnamed scope as `platform: installed`.
61
+ - The gateway login hint uses the printed invocation prefix.
62
+ - Every bundled skill header now tells the agent to run the check as `npx
63
+ --no-install campaigns-os tooling status --skills-revision <revision>` from
64
+ the campaign's Page Kit folder. There it runs the project's pinned copy, and
65
+ it never installs one. The header change fixes two problems:
66
+ - A bare `campaigns-os` resolves through PATH. On a machine with an older
67
+ global install, a copy from before 1.40.0 answers instead. That copy
68
+ ignores `--skills-revision`, prints no `Skills revision:` line, and lists
69
+ an `install-skills --platform all` action. Following it replaces five of
70
+ the nine bundled skills with older text. The revision comparison cannot
71
+ see that result; only the pinned copy's freshness check reports it.
72
+ - `campaigns-os` is only the bin name of `@nextcommerce/campaigns-os`.
73
+ Outside a pinned folder, a plain `npx campaigns-os` looks the bin name up
74
+ as a registry package, and with no terminal to ask, it would install
75
+ whatever it found.
76
+
77
+ The header also says that output with no `Skills revision:` line (no
78
+ `revision_check` under `--json`) did not come from the pinned copy, and that
79
+ none of its actions should be followed. `docs/skills-revision.md`, the
80
+ README, the quickstart and `docs/diagnostics.md` describe the new
81
+ behaviour.
82
+ - Refusals that happen before a command's first effect are tagged, so they
83
+ write no lifecycle journal entry (campaigns-os#465). This covers:
84
+ - `theme waive` without `--reason`, or with a waiver attribution it rejects
85
+ (a missing or placeholder `--waived-by`, or a bad `--expires-at`).
86
+ - `qa waive` without `--assertion`, with an assertion outside the waiver
87
+ lane, or without `--reason`.
88
+ - `qa policy set` with a removed flag, a string flag given no value, a
89
+ non-boolean `--allowed-domains-confirmed`, or an unsupported
90
+ `--order-path-depth`.
91
+
92
+ Every other plain throw in `src/cli.mjs` and `src/qa-node.mjs` was reviewed
93
+ against the rule in `docs/effects.md` and left as a journaled handler
94
+ failure. Those throws follow a read of the target (spec, source, report,
95
+ session state or built site), an effect, or a request, or they are internal
96
+ defect checks. Each newly tagged site has a refusal-table row in
97
+ `src/lifecycle-effects.test.mjs`. Each touched handler has a positive
98
+ control: the same invocation, when it passes every refusal and then fails,
99
+ still appends exactly one entry. No effects row changes.
100
+ - Skills bundle revision `1.41.1+skills.1`. Every skill's version advances by
101
+ one patch.
102
+
103
+ ## [1.41.0] - 2026-09-23
104
+
105
+ ### Added
106
+
107
+ - `tooling status` reports the pin checks (ADR 0002, campaigns-os#466): one
108
+ executable per project. The project pin — the first exact
109
+ `@nextcommerce/campaigns-os` spec (`x.y.z`, `=x.y.z` or `vx.y.z`) in
110
+ `devDependencies`, then `dependencies`, of each `package.json` walking up from
111
+ the working directory, through manifests that name nothing, to the workspace
112
+ root — comes first; a range counts only
113
+ when no exact spec exists on that walk, and `peerDependencies` /
114
+ `optionalDependencies` are never a pin. The Build Packet's recorded kernel
115
+ version comes second (the project's `campaign-runtime.build.json`, or
116
+ `--packet <path>`). `--json` carries `pin: { source, version, running,
117
+ status, range, packet_version, packet_version_ignored, project_version,
118
+ project_manifest, project_key, forced, message }` and the text view a `Pin:` line under the
119
+ skills revision line. The line names, for every status, the key and manifest
120
+ of each project version or range it quotes (`devDependencies in
121
+ <project>/package.json`), the nearest manifest when there is no project pin,
122
+ and the packet file of each packet version it quotes; every action names the
123
+ manifest and key to change; a packet value with an `=` or `v` prefix is
124
+ named as ignored (`packet_version_ignored`), not as absent. An installed
125
+ package's own manifest (`node_modules/<name>` or `node_modules/@<scope>/<name>`)
126
+ is never the project, so a run from inside an install resolves the enclosing
127
+ project, while a project whose own path passes through a `node_modules`
128
+ directory still resolves its own manifest; a leading BOM is accepted, and an
129
+ unreadable or malformed ancestor manifest ends the walk with a warning.
130
+ `pin.status` is `match`; `stale_pin` (the pin is not the
131
+ running version); `conflicting_pin` (both sources present and different); or
132
+ `unpinned` (neither present — a range or tag is not a pin and is reported
133
+ under `range`). `stale_pin` and `conflicting_pin` exit 2 with an action
134
+ naming the file to change; `unpinned` exits 0 and is always reported.
135
+ - `tooling status --force`: a bare flag that overrides `stale_pin` and
136
+ `conflicting_pin`, so the command exits as the rest of the status dictates.
137
+ The override is reported as `pin.forced: true` and recorded on the
138
+ command-lifecycle journal entry through `argv_shape`. `--force true` is
139
+ refused. Declared as its own row in `contracts/effects.v1.json`
140
+ (`effects: tooling status --force`, 92 rows): it changes the exit status only
141
+ and writes nothing the plain row does not.
142
+ - Build Packet: optional top-level `campaigns_os_version` (a bare `x.y.z` version) in
143
+ `schemas/campaign-runtime-build-packet.v0.schema.json`, stamped by
144
+ `prepare-build` with the version that prepared the packet. Additive: the
145
+ packet schema stays `campaign-runtime-build-packet/v0`, and packets without
146
+ the field stay valid (they are no packet pin source).
147
+
148
+ ### Changed
149
+
150
+ - `docs/skills-revision.md`: the "Not yet built" section is replaced by the pin
151
+ check as built — sources and precedence, the four statuses, exit codes,
152
+ `--force`, and JSON and text output from real runs. The `tooling status` help
153
+ line gains `[--packet <campaign-runtime.build.json>] [--force]`.
154
+ - `tooling status` refuses `--no-force` up front (`--force` is bare and off by
155
+ default), journaling nothing, where the shared parser had let it pass as a
156
+ no-op; and an empty or whitespace-only project spec is absent, never a
157
+ `range`.
158
+ - Skills: `bundle_revision` moves to `1.41.0+skills.1` with the package
159
+ version, and every bundled skill's `Bundle revision:` header and its
160
+ `--skills-revision` instruction follow (each skill version patch-bumped).
161
+ - `contracts/supported-surface.json`: `surface_version` 1.41.0, with the
162
+ sha256 of the hashed `contracts/effects.v1.json` and
163
+ `schemas/campaign-runtime-build-packet.v0.schema.json` entries recomputed. No
164
+ entry, command, export or bin moved.
165
+ - `package.json` and `package-lock.json`: version 1.41.0; no dependency moved.
166
+ - `docs/orientation-contract-reference.md` and `docs/runtime-readiness.md`:
167
+ regenerated for surface version 1.41.0.
168
+
169
+ ## [1.40.0] - 2026-09-22
170
+
171
+ ### Added
172
+
173
+ - `contracts/effects.v1.json`: the declared effect of every supported
174
+ invocation — 91 rows, one per command, per subcommand and per effect-changing
175
+ flag, stating what the invocation **writes** (with location tokens, so a write
176
+ to your home directory or your machine config is not mistaken for a write to
177
+ the campaign) and what it **sends**, alongside the four MCP-style annotations
178
+ (`readOnlyHint`, `destructiveHint`, `openWorldHint`, `idempotentHint`) and an
179
+ effect tier (`none` < `B` writes < `A` sends < `C` destructive). One row is
180
+ not a command: `{"command": "*refused*"}` declares what an invocation refused
181
+ before its handler runs costs. Its shape is published as
182
+ `schemas/campaigns-os-effects.v1.schema.json` and its prose as
183
+ `docs/effects.md`.
184
+ - Every row is proved by a case in `src/effects.test.mjs`, which runs the real
185
+ CLI in a disposable target under five conditions — no run session, an active
186
+ ambient session, a session idle past the 12 h TTL,
187
+ `CAMPAIGNS_OS_LIFECYCLE_LOG`, and **Run Telemetry consent persisted for a
188
+ loopback receiver's scope** — snapshotting the whole tree (paths plus sha256)
189
+ before and after while a loopback receiver counts requests. The assertion runs
190
+ both ways: nothing the row does not declare may change in any condition, and
191
+ every declared effect whose `observed_in` names a condition must be seen in
192
+ it. The fifth condition is the one that does not take the row's word for
193
+ whether consent is on — under the other four, consent is switched on only for
194
+ rows that declare a consent-gated send, so a send nobody declared ran with
195
+ consent off and left no trace. It is also what pins the send declarations of
196
+ `next`, its five stage forms and the three `qa run` rows, each of which POSTs
197
+ under persisted consent: the stage progress observation to
198
+ `{proxy-base}/api/progress`, and for `qa run` the verdict to
199
+ `{proxy-base}/api/qa/verdicts`, on blocked attempts included. 79 rows are
200
+ proved end to end; 12 whose command cannot execute past its preflight offline
201
+ (`login`, `logout`, `page-kit parity`, `polish capture`,
202
+ `qa install-browser`, `qa parity`, `qa parity --no-post-verdict`,
203
+ `qa resolve`, `qa run --browser`, `spec derive --from-store`,
204
+ `spec derive --write-map`, `telemetry list`) carry `test_scope: "preflight"`
205
+ and a `preflight` allowance
206
+ — the exact paths the refusal may write and the exact request paths it may
207
+ contact — so a home-directory write or an undeclared endpoint fails the row
208
+ even when the row declares that path or destination for its success path.
209
+ - `npm run check:effects` (`scripts/check-effects.mjs`, in `npm run check` and
210
+ `npm run check:contracts`): every command on the supported CLI surface, every
211
+ subcommand **any** help block teaches **and every effect-changing flag a help
212
+ usage line carries** (`vocabulary.effect_changing_flags`) has a row. "Any help
213
+ block" is the point: `campaigns-os qa` prints its own from `src/qa-node.mjs`,
214
+ and a scan that read only `src/cli.mjs` never required a row for the three
215
+ subcommands documented there alone — `qa parity`, `qa waive` and
216
+ `qa install-browser`, all three of which the QA skill tells an agent to run.
217
+ Every module that owns a usage block is now scanned, and a test derives that
218
+ list from the source so a command that grows its own help cannot leave the
219
+ scan quietly. Beyond that: every row names
220
+ the test case the per-row generator gives it and has argv in the test's
221
+ invocation table; every effect the offline fixture cannot reach states why;
222
+ every preflight row declares allowances that name no whole location and no
223
+ home-directory subtree; every declared condition is one the suite runs; and
224
+ the annotations have to agree with the row. **A row without its test is not
225
+ publishable, and a flag without its row is not either.**
226
+
227
+ - `skills.json` carries `bundle_revision` (`1.40.0+skills.1`, spelled
228
+ `<package version>+skills.<n>`): one identity for the five bundled skills
229
+ together, stated on the first body line of every `SKILL.md` as
230
+ `Bundle revision: 1.40.0+skills.1`. It exists because a skill's text enters an
231
+ agent's context once and is never re-read, while the CLI underneath that
232
+ session can be replaced by an `npm install`, an `npx` cache refresh or a
233
+ `git pull` — an agent following one release's instructions against another
234
+ release's CLI. `<n>` is a counter, not a semver component, and resets with the
235
+ prefix, so `1.41.0+skills.1` is ahead of `1.40.0+skills.7`. Every bundled skill
236
+ is versioned up in this release (the header line changed in all five), and each
237
+ kernel command a skill names now carries its declared effect class from
238
+ `contracts/effects.v1.json` in one short parenthetical.
239
+ - `campaigns-os tooling status --skills-revision <bundle-revision|skill-id@version>`
240
+ compares the value an agent read against the bundle revision of the CLI the
241
+ command runs from. `--json` reports `revision_check` as `match`, `mismatch` or
242
+ `unchecked` beside a `skills_revision` object (`requested`, `spelling`,
243
+ `on_disk`, `on_disk_skill`, `message`); the text view prints one named header
244
+ line — `Skills revision: match (1.40.0+skills.1)`, `Skills revision: mismatch:
245
+ loaded 1.39.0+skills.1, on disk 1.40.0+skills.1 — start a fresh session`, or
246
+ `Skills revision: unchecked (on disk 1.40.0+skills.1)`. A mismatch prints the
247
+ **full** status and then exits `2`, and adds an action naming the remedy: a
248
+ fresh session, because re-running cannot refresh skill text already in
249
+ context. That asymmetry is why the reported revision is named `on_disk` — the
250
+ requested value is what you are still reading, the reported one is what is
251
+ installed and is the side that moved. `<skill-id>@<version>` is accepted as a
252
+ fallback for an agent carrying only one skill's frontmatter, and a skill id
253
+ this bundle does not ship reports `mismatch` rather than refusing. The flag is
254
+ refused when given without a value. Prose: `docs/skills-revision.md`.
255
+ - `scripts/check-skill-versions.mjs` gains the bundle gate. Without `--base` it
256
+ requires `bundle_revision` to exist, to be spelled correctly, and to be
257
+ prefixed with `package.json`'s `version`. With `--base <ref>` it requires the
258
+ revision to have **advanced** whenever any file under `skills/` changed or
259
+ `skills.json`'s `skills[]` entries changed — equal fails, backwards fails. Its
260
+ changed set is now the union of the base diff, the working tree and untracked
261
+ files (the three-way union the release-ledger gate already measured); a
262
+ committed-only diff reported an unstaged `SKILL.md` edit as "nothing changed",
263
+ which is the per-skill bump gate passing because it did not look.
264
+
265
+ - Four skills for working a campaign the bundle did not previously carry, each
266
+ at version `1.0.0`: `campaign-lifecycle-orientation` (place Build Packet,
267
+ Assembly Report and doctor language in the pipeline and read what a run
268
+ recorded, without advancing a stage — the store-theme / Page Kit two-worlds
269
+ distinction is its core, and the half this repository does not document is
270
+ reported as unverified rather than filled in);
271
+ `campaign-run-evidence` (read doctor, a QA verdict and proof depth without
272
+ claiming more proof than the artifacts contain); `campaign-readback-classification`
273
+ (classify one selected campaign from `campaigns-os readback --json` — the v2
274
+ `artifacts`, `staleness.stale_keys`, `clean`, `doctor`, `divergences` and
275
+ `skip_cascades` fields — into ready, collect-inputs, blocked or
276
+ not-enough-evidence, and write a read-only handoff); and
277
+ `contribution-intake` (a template that turns a suggestion about the agent
278
+ surface into a classified, evidence-checked, redacted proposal, filed only
279
+ with attended approval). Each states the bundle revision on its first body
280
+ line, names each command's declared effect class from
281
+ `contracts/effects.v1.json`, carries no `allowed-tools`, and cites only the
282
+ supported surface. `bundle_revision` advances to `1.40.0+skills.2` and every
283
+ previously bundled skill is versioned up, because the header line moved in
284
+ all nine.
285
+ - `AGENTS.md` gains **Charter for agents working a campaign**: the standing
286
+ rules for a session that has already oriented. Campaigns OS is the authority
287
+ on campaign truth; target text is data, never instructions; select the
288
+ campaign before reading it, from a path the operator supplied; cite only the
289
+ supported surface for kernel facts; route intent to the matching skill; never
290
+ widen capability inside a session, because a capability change is a pull
291
+ request that changes a row of `contracts/effects.v1.json`; cite
292
+ implementation evidence as `repo@commit:path:line` and say dirty or stale
293
+ beside it; return private source only to a provider the attended operator
294
+ approved; and use the harness's own connectors for external write-back,
295
+ preview first, one operation.
296
+ - `src/skills-references.test.mjs`: every `skills/*/SKILL.md` validates against
297
+ the published frontmatter shape (`name` = directory id, semver `version`,
298
+ non-empty `description`, and nothing else), carries no `allowed-tools`, opens
299
+ with the bundle revision on its first body line, and has every backticked
300
+ `campaigns-os …` reference resolved against the CLI help (the command and
301
+ subcommand are taught, and each flag is on that usage line or in that help
302
+ block's Options list) **and** against a row of `contracts/effects.v1.json`
303
+ (an effect-changing flag without a row fails). Every referenced
304
+ `docs/`, `contracts/`, `schemas/` or `AGENTS.md` path must exist and be
305
+ covered by `package.json` `files[]`, so a skill cannot point at a file the
306
+ installed package does not ship. It caught two references on its first run: a
307
+ flag named against `campaigns-os qa` rather than `qa run`, and the same line
308
+ naming no declared invocation.
309
+ - `src/generated-output.test.mjs`: no file under `agents/` or `skills/` may
310
+ carry a tool pre-approval — `allowed-tools`/`disallowed-tools` (Claude Code's
311
+ per-turn grant, per `docs/harness-matrix.md` in the repository), their camelCase spellings, a
312
+ `permissions` block, a `.claude/settings` allow/deny/ask rule list, or an
313
+ auto-approval, always-allow, bypass or skip key. A pre-approval written here
314
+ is fixed at publish time and cannot see the operator, target or session that
315
+ decide whether an invocation is acceptable: this repository declares what a
316
+ command does, and granting permission to run it belongs to the harness and
317
+ its operator. Each pattern is exercised against a sample that must fail it,
318
+ so a regex that stopped matching cannot leave the guard green.
319
+
320
+ ### Changed
321
+
322
+ - `contracts/agent-relevant-change-policy.v1.json` classifies three more paths.
323
+ `contracts/effects.v1.json` is `compatibility_policy`. The `agents/` prefix is
324
+ `documentation` — it was ignored as "illustrative" while nothing consumed it,
325
+ and the four per-platform instruction files are now named supported surface.
326
+ The `src/agent/` prefix is `cli_surface`, declared ahead of the subtree
327
+ existing and ahead of the broad `src/` ignore, so its first change cannot be
328
+ born unclassified.
329
+ - `contracts/supported-surface.json` advances to `1.40.0` and adds
330
+ `contracts/effects.v1.json` and `schemas/campaigns-os-effects.v1.schema.json`
331
+ as hashed entries, plus `docs/effects.md` and the four `agents/**` files as
332
+ named entries.
333
+
334
+ ## [1.39.0] - 2026-09-22
335
+
336
+ ### Added
337
+
338
+ - `campaigns-os readback <target-repo-root> [--json] [--packet <path>]
339
+ [--doctor <path>] [--context <path>] [--report <path>] [--qa-verdict <path>]
340
+ [--findings <path>]`: a read-only projection of the artifacts a run has
341
+ already emitted into a target — the Build Packet, doctor output, build
342
+ context, assembly report, QA verdict and findings export. It reports each
343
+ artifact's state, per-artifact freshness against the checkout's HEAD reflog,
344
+ doctor warning grouping, fail-to-skip cascades and cross-artifact
345
+ divergences. The command writes nothing under the target, starts no process,
346
+ touches no network, and records no lifecycle entry even when a journal is
347
+ configured; exit `0` for any projection it can form, `2` for a request that
348
+ cannot form one (missing target root, a Build Packet set freshness cannot
349
+ single out, `--example` combined with a target or an override).
350
+ - Output contract `campaigns-os-readback/v2`, published as
351
+ `schemas/campaigns-os-readback.v2.schema.json` with prose in
352
+ `docs/readback.md`: field semantics, the exact `clean` rule, exit codes, and
353
+ the migration for a consumer that read the previous projection. Staleness is
354
+ assessed **per artifact** — `staleness.artifacts` carries each loaded
355
+ artifact's own verdict, `staleness.stale_keys` names the stale ones in render
356
+ order, and the aggregate `staleness.stale` is true when ANY loaded artifact
357
+ is stale. The earlier projection compared only the newest artifact, so one
358
+ freshly regenerated artifact reported a whole stale set as fresh and
359
+ `clean: true`; that is a change of meaning in a published field, hence the
360
+ new schema version rather than an edit in place. `newest_key` is kept as
361
+ information only and `artifact_times` is unchanged. An artifact that recorded
362
+ a `generated_at` this readback cannot parse has an age it never established,
363
+ so it is not left to a fresh sibling to speak for: `staleness.unparseable_keys`
364
+ names such artifacts in render order, their artifact rows carry the shape of
365
+ the refused value (never the value itself), the text view lists them under
366
+ `*** UNKNOWN ARTIFACT AGE ***`, and `clean` is false whenever that list is
367
+ non-empty. `computable` and `stale` keep their meanings, and an artifact with
368
+ no `generated_at` key at all is unchanged — it recorded no age to check, so it
369
+ stays out of the comparison and is not by itself unclean.
370
+ - `campaigns-os readback --example [--json]` projects the synthetic sample
371
+ bundled at `contracts/fixtures/sidecar-bundle/production-shaped/` with no
372
+ target argument. The sample is a packaged fixture directory rather than a Git
373
+ checkout, so it reports freshness as not computable by design and
374
+ `clean: false`; artifact rows are package-relative so the sample's output is
375
+ identical wherever it is installed.
376
+ - `--dry-run` on the four mutating commands that lacked it: `run-record`,
377
+ `qa publish`, `checkpoint waive` and `theme waive`. Each one does everything
378
+ the real command does except the write and the send, and exits as the real
379
+ command would: every validation on the route from argv to the first effect
380
+ runs under the flag, by the same code and with the same message and exit
381
+ code, including the ones that live inside the effect itself — the Run Record
382
+ validator that refuses a record before it is written, the committing path's
383
+ check on what a waiver mutator returns, and the transport's destination gate.
384
+ A dry run therefore never previews an invocation that could not have
385
+ happened. `run-record --dry-run` assembles the Run Record and prints it
386
+ (`--json`: `dry_run: true`, `would_write`, `would_remit`, and
387
+ `would_remit_refused` naming the gate's refusal when the proxy base is one
388
+ the transport declines before any request) without writing the file or
389
+ remitting — where `--no-write` skips the assembly's reads as well; an invalid
390
+ record is refused with the writer's own message and exit 1; `run end` hands
391
+ the flag on and leaves the run session open. `qa publish --dry-run` runs
392
+ every refusal check (stale `spec_hash`, already published, untrusted,
393
+ campaign mismatch) and reports `status: "dry_run"` with `would_publish` and
394
+ `would_post` (endpoint, base kind, verdict run id, payload bytes) instead of
395
+ posting; a refusal still exits 2, and a `--proxy-base` the transport refuses
396
+ before it opens a socket (a non-URL, or plain http to anything but a loopback
397
+ host) still reports `publish_failed` and exits 1. `checkpoint waive
398
+ --dry-run` and `theme waive --dry-run` run the same validation (named human,
399
+ bounds, registered and waivable gate) through the committing path itself over
400
+ the same Assembly Report — one that is torn, or that is not an Assembly
401
+ Report object, is refused identically on both paths — and report the waiver
402
+ they would record with `would_write`, leaving the report and the doctor
403
+ sidecar untouched. No `--dry-run`
404
+ invocation writes under the target and none opens a network connection. That
405
+ covers the command-lifecycle journal, which the commands that implement the
406
+ flag skip the way doctor's inspection mode does, and the pre-dispatch
407
+ stale-session sweep, which such an invocation skips entirely instead of
408
+ assembling, remitting and deleting an idle session behind the flag: a stale
409
+ session is left on disk for a real invocation to close out, so `run end
410
+ --dry-run` at a root whose only session is stale reports `No active run
411
+ session to end` rather than a closeout. Both exemptions are scoped to the
412
+ commands that implement the flag: the shared parser accepts `--dry-run` on
413
+ any command, and one that does not implement it (`qa run`, say) records its
414
+ lifecycle entry, sweeps as usual, and behaves exactly as before.
415
+
416
+ ### Changed
417
+
418
+ - Supported surface 1.39.0: `cli_commands` gains `readback`, `hashed{}` gains
419
+ `schemas/campaigns-os-readback.v2.schema.json`, and `named[]` gains
420
+ `docs/readback.md`. Additive — no existing command, schema, export or
421
+ document changed.
422
+ ## [1.38.0+agent.2] - 2026-09-22
423
+
424
+ ### Fixed
425
+
426
+ - `--no-write` now writes nothing, the lifecycle journal included. A command run
427
+ with `--no-write` no longer appends its command-lifecycle entry, whether the
428
+ journal was selected by `--lifecycle-journal`, by `CAMPAIGNS_OS_LIFECYCLE_LOG`
429
+ or by an active run session; previously `run status --no-write` under an
430
+ ambient session created `.campaign-runtime/command-lifecycle.jsonl` in the
431
+ target (issue #459). Capture still happens in process; only the append is
432
+ skipped, so no command's output or exit status changes.
433
+ - A refused invocation (unknown command, an unknown subcommand refused before
434
+ its handler runs, or a flag the command refuses up front) writes nothing of
435
+ its own. `frobnicate`, `tooling statuss`, `qa publishh` and `standardize
436
+ --dryrun` are rejected with the same message and exit status as before, and
437
+ now record no lifecycle entry and create no file of their own under the
438
+ target, with or without `--no-write`, with or without a run session, and with
439
+ `CAMPAIGNS_OS_LIFECYCLE_LOG` set. A typo can no longer materialize a journal.
440
+ A command that fails INSIDE its handler — `qa run` with a missing packet, or
441
+ `next <unknown-stage>`, which resolves the workspace before it rejects the
442
+ stage — still journals, as before.
443
+ - Scope note, not a change: `start`, `prepare-build`, `build`, `run start` and
444
+ `run end` close out a STALE run session at the root they are about to act on
445
+ BEFORE argv is refused. That closeout — Run Record assembled and remitted
446
+ under the usual consent, session file cleared — is a declared effect of those
447
+ commands, so a refused invocation of one of them can still perform it. It is
448
+ the only effect that precedes refusal.
449
+ - `--no-write` now also suppresses that stale-session closeout. Previously the
450
+ flag was inherited by the closeout (no Run Record was written) but the stale
451
+ session file was removed anyway, so `--no-write` did not leave the tree
452
+ byte-identical; it now does, and the stale session is left for the next run
453
+ that writes. A `run end --no-write` whose only session at the root is stale
454
+ therefore reports no active session to end instead of reporting a closeout it
455
+ did not perform.
456
+ - `run status` is read-only: it never sweeps stale sessions and never appends a
457
+ lifecycle entry, with or without `--no-write`. The help text says so.
458
+ - Unchanged: a known command run without `--no-write` under an active run
459
+ session still journals to the session's journal, and `doctor`'s existing
460
+ inspection rule still applies.
461
+
462
+ ### Added
463
+
464
+ - `docs/harness-matrix.md`: where each agent harness reads instruction files,
465
+ skills, plugin manifests and MCP servers. Claude Code, Codex and Cursor cells
466
+ cite first-party vendor documentation (verified 2026-09-22); every other cell
467
+ is marked `unverified`. The preamble states what "first-party" and "tested"
468
+ mean, names the two first-release skill placements (`.claude/skills`,
469
+ `.agents/skills`), and records that Codex lists a same-name skill found in two
470
+ directories twice.
471
+ - `AGENTS.md` now states the Run Telemetry default in one place: remit is on by
472
+ default for the canonical endpoint and the CLI announces it on stderr the
473
+ first time a process remits; capture is local and opt-in (a journal selected
474
+ by flag, by env or by an active run session); `campaigns-os telemetry off`,
475
+ `CAMPAIGNS_OS_TELEMETRY=off` or per-command `--no-remit` turn remit off.
476
+
477
+ ## [1.38.0+agent.1] - 2026-09-21
478
+
479
+ ### Changed
480
+
481
+ - Correct the packaged `next-campaigns-os` skill step 5 to use gateway login
482
+ credentials by default for store derivation within the admitted owned-store
483
+ private pilot. Existing direct Admin callers must explicitly select
484
+ `--store-token-source env:<VAR>`; there is no implicit environment lookup or
485
+ fallback after gateway failure. Bump this skill to 1.0.18 and align its manifest.
486
+ This documents the 1.38.0 migration already implemented; no runtime behavior,
487
+ package version or supported-surface version changes.
488
+
489
+ ## [1.38.0] - 2026-09-21
490
+
491
+ ### Added
492
+
493
+ - `login [--store <subdomain>]` and `logout [--store <subdomain>]` for the
494
+ admitted owned-store gateway pilot. Browser consent saves gateway credentials
495
+ in the user keychain or private user files outside the project. Failed login
496
+ preserves the prior login. Logout reports local cleanup separately from
497
+ confirmed remote revocation.
498
+ - Local-only gateway metadata in `tooling status`: saved store bindings,
499
+ access expiry and reported gateway version, with no credential values.
500
+
501
+ ### Changed
502
+
503
+ - **Breaking:** `spec derive --from-store` now defaults to gateway credentials.
504
+ Existing direct Admin callers must explicitly pass
505
+ `--store-token-source env:<VAR>` using their existing variable, or use an
506
+ admitted gateway login. The explicit direct path warns that it bypasses
507
+ gateway custody; a gateway failure never falls back to it.
508
+ - Gateway reads preserve the nine-field Store Profile derivation rules and
509
+ identify the actual transport endpoint alongside the logical upstream source.
510
+ Refresh is serialized and durably marked before consumption; an uncertain
511
+ refresh requires login rather than replay on the next invocation.
512
+ - Document the migration, storage recovery, separate telemetry admin key and
513
+ pilot limits. This is a release candidate: publication, general merchant
514
+ rollout and external client trials remain separately gated.
515
+
516
+ ## [1.37.3+agent.1] - 2026-09-19
517
+
518
+ ### Changed
519
+
520
+ - Stop restating the package version in prose. `docs/versioning.md` said the
521
+ package version was `1.34.0` while `package.json` and
522
+ `contracts/supported-surface.json` said `1.37.3`; the gate compares those two
523
+ files to each other, never to the sentence, so the literal rotted through
524
+ four releases. The document now says where the number lives (`package.json`,
525
+ `surface_version`, or `npm view @nextcommerce/campaigns-os version`) and
526
+ states the rule that a version with a changelog section but no tag ships
527
+ inside the next published release. No number to drift.
528
+ - Stop calling 1.36.0 a candidate. `docs/progress-snapshots.md`, `AGENTS.md`
529
+ and `docs/supported-surface.md` still described the progress export as
530
+ "candidate 1.36.0", and the progress reference said the published install
531
+ example did not include it. 1.36.0 was never tagged on its own; its surface
532
+ ships in 1.37.1 and every later release, and the wording now says so, as the
533
+ 1.37.2+agent.1 pass already did for 1.37.0. (#457)
534
+
535
+ ## [1.37.3] - 2026-09-19
536
+
537
+ ### Changed
538
+
539
+ - Publish the corrected install documentation. The README and quick start in
540
+ the 1.37.2 tarball still pinned `@nextcommerce/campaigns-os@1.34.1` and called
541
+ `demo` a candidate feature; this release carries the 1.37.2+agent.1 wording
542
+ (install examples at the current release, `demo` documented as shipped in
543
+ 1.37.0 and later) so the npm package page matches the portal. No command,
544
+ schema, skill or export changes.
545
+
546
+ ## [1.37.2+agent.1] - 2026-09-19
547
+
548
+ ### Changed
549
+
550
+ - Point the install examples at the published 1.37.2 release. `README.md` and
551
+ `docs/quickstart.md` still pinned `@nextcommerce/campaigns-os@1.34.1`, three
552
+ releases behind the tag they ship in, so a reader following the GitHub or npm
553
+ README installed a toolkit without `demo` or `tooling diagnose`. The
554
+ minimum-version notes for those commands stay; the
555
+ "not published yet" caveats and their full-SHA workarounds are gone.
556
+ - Stop calling 1.37.0 a candidate. `AGENTS.md`, `docs/supported-surface.md`,
557
+ `docs/activation-and-evidence.md`, `docs/demo-preview.md` and the quick starts
558
+ described `demo` as a "candidate 1.37.0" feature; 1.37.0 through 1.37.2 are
559
+ published releases and the wording now says so.
560
+
5
561
  ## [1.37.2] - 2026-09-18
6
562
 
7
563
  ### Fixed