@nextcommerce/campaigns-os 1.37.3 → 1.43.1

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