@nextcommerce/campaigns-os 1.37.3 → 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.
- package/AGENTS.md +114 -10
- package/CHANGELOG.md +530 -0
- package/README.md +38 -27
- package/contracts/agent-relevant-change-policy.v1.json +11 -1
- package/contracts/effects.v1.json +4794 -0
- package/contracts/release-ledger.json +789 -0
- package/contracts/supported-surface.json +25 -5
- package/docs/build-packet.md +27 -16
- package/docs/demo-preview.md +1 -1
- package/docs/diagnostics.md +7 -4
- package/docs/effects.md +281 -0
- package/docs/gateway-login.md +113 -0
- package/docs/orientation-contract-reference.md +4 -1
- package/docs/progress-snapshots.md +3 -3
- package/docs/qa-and-test-orders.md +3 -3
- package/docs/readback.md +523 -0
- package/docs/runtime-readiness.md +1 -1
- package/docs/sdk-storage-compatibility.md +1 -1
- package/docs/skills-revision.md +364 -0
- package/docs/supported-surface.md +11 -3
- package/docs/versioning.md +8 -4
- package/package.json +8 -3
- package/schemas/campaign-runtime-build-packet.v0.schema.json +5 -0
- package/schemas/campaigns-os-effects.v1.schema.json +211 -0
- package/schemas/campaigns-os-readback.v2.schema.json +267 -0
- package/skills/campaign-lifecycle-orientation/SKILL.md +174 -0
- package/skills/campaign-readback-classification/SKILL.md +230 -0
- package/skills/campaign-run-evidence/SKILL.md +140 -0
- package/skills/contribution-intake/SKILL.md +85 -0
- package/skills/next-campaigns-build/SKILL.md +33 -12
- package/skills/next-campaigns-os/SKILL.md +45 -21
- package/skills/next-campaigns-os-setup/SKILL.md +35 -14
- package/skills/next-campaigns-polish/SKILL.md +43 -17
- package/skills/next-campaigns-qa/SKILL.md +48 -24
- package/skills.json +39 -6
- package/src/admin-transport.mjs +123 -0
- package/src/cli.mjs +991 -200
- package/src/credential-store.mjs +183 -0
- package/src/deviation.mjs +3 -2
- package/src/diagnostic.mjs +4 -1
- package/src/gate-actions.mjs +2 -2
- package/src/install-mode.mjs +17 -9
- package/src/lifecycle.mjs +95 -0
- package/src/login.mjs +152 -0
- package/src/package-install-fixture.mjs +3 -2
- package/src/qa-node.mjs +56 -19
- package/src/qa-publish.mjs +108 -2
- package/src/readback.mjs +1936 -0
- 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,7 +83,7 @@ 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
|
-
|
|
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
|
|
@@ -94,3 +94,11 @@ a hashed `demo/apollo-v0/provenance.json` and named attribution notice. The comm
|
|
|
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.
|
package/docs/versioning.md
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
This repo uses independent compatibility versions:
|
|
4
4
|
|
|
5
|
-
- package version: `
|
|
6
|
-
`contracts/supported-surface.json`
|
|
7
|
-
|
|
8
|
-
|
|
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.
|
|
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,
|