@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_note": "The downstream contract manifest. Everything listed here is SUPPORTED SURFACE: consumers (campaigns-agent, campaign-builder, the private ops repo, page-kit campaign repos) may depend on it, and changing it is a deliberate act
|
|
3
|
-
"surface_version": "1.
|
|
2
|
+
"_note": "The downstream contract manifest. Everything listed here is SUPPORTED SURFACE: consumers (campaigns-agent, campaign-builder, the private ops repo, page-kit campaign repos) may depend on it, and changing it is a deliberate act — hashed entries require a surface_version bump in the same change (check-supported-surface.mjs --base, mirroring the skills.json bump gate), named entries must keep existing at their path, cli_commands must keep resolving in the CLI dispatch, package_exports must stay exported, and every entry must ship in the npm pack (files[] coverage). Anything NOT listed here — src/** internals, scripts/** checkers, examples/**, prompts/**, contracts/** other than this file and the entries named[] below (the orientation contract, the release ledger, and the consumer-facing orientation fixtures) — is implementation: consumers may read it for context but must not build on it, and it can change without notice. Rationale and the compatibility promise: docs/supported-surface.md.",
|
|
3
|
+
"surface_version": "1.41.2",
|
|
4
4
|
"package_exports": [
|
|
5
5
|
"./commercial-journey",
|
|
6
6
|
"./commercial-parity",
|
|
@@ -40,7 +40,10 @@
|
|
|
40
40
|
"findings",
|
|
41
41
|
"run-record",
|
|
42
42
|
"run",
|
|
43
|
-
"demo"
|
|
43
|
+
"demo",
|
|
44
|
+
"login",
|
|
45
|
+
"logout",
|
|
46
|
+
"readback"
|
|
44
47
|
],
|
|
45
48
|
"hashed": {
|
|
46
49
|
"contracts/migration-sidecar-bundle.v0.json": {
|
|
@@ -62,7 +65,7 @@
|
|
|
62
65
|
"sha256": "f310c1b7844d9b1994864d15858ff0c6f17efc1e5285ba193d181bdb2dd4be2f"
|
|
63
66
|
},
|
|
64
67
|
"schemas/campaign-runtime-build-packet.v0.schema.json": {
|
|
65
|
-
"sha256": "
|
|
68
|
+
"sha256": "886fb59b58787a3fd7a9c43ce4c676aeebbd52fd789269cfac700fcae42106d1"
|
|
66
69
|
},
|
|
67
70
|
"schemas/campaign-spec.v4.schema.json": {
|
|
68
71
|
"sha256": "12b57573739357acb2b007d00a737cd86face441c47f2719fc51b041dc8ea4a5"
|
|
@@ -111,6 +114,15 @@
|
|
|
111
114
|
},
|
|
112
115
|
"demo/apollo-v0/provenance.json": {
|
|
113
116
|
"sha256": "2e440be6513fe06b351c3a72c35424a04f807792621cb01e89803441fd925e06"
|
|
117
|
+
},
|
|
118
|
+
"schemas/campaigns-os-readback.v2.schema.json": {
|
|
119
|
+
"sha256": "dfc9abed38d456969e47a21f606d308a03bdf036f3466f7d6e47a602747dacf4"
|
|
120
|
+
},
|
|
121
|
+
"contracts/effects.v1.json": {
|
|
122
|
+
"sha256": "37c54f75731541f763aedac7b067506b3d0aa98e1083f285de87b9b32b027664"
|
|
123
|
+
},
|
|
124
|
+
"schemas/campaigns-os-effects.v1.schema.json": {
|
|
125
|
+
"sha256": "3eadd22169ab98bc7c2f682af2751267605170581182158d96be035b0cfe44dc"
|
|
114
126
|
}
|
|
115
127
|
},
|
|
116
128
|
"named": [
|
|
@@ -123,6 +135,7 @@
|
|
|
123
135
|
"docs/supported-surface.md",
|
|
124
136
|
"docs/campaigns-os-build-flow.md",
|
|
125
137
|
"docs/build-packet.md",
|
|
138
|
+
"docs/gateway-login.md",
|
|
126
139
|
"docs/migration-sidecar-bundle.md",
|
|
127
140
|
"docs/design-source-package.md",
|
|
128
141
|
"docs/campaign-build-brief.md",
|
|
@@ -192,6 +205,13 @@
|
|
|
192
205
|
"docs/progress-snapshots.md",
|
|
193
206
|
"contracts/fixtures/progress/observation.v0.json",
|
|
194
207
|
"docs/demo-preview.md",
|
|
195
|
-
"demo/apollo-v0/NOTICE.txt"
|
|
208
|
+
"demo/apollo-v0/NOTICE.txt",
|
|
209
|
+
"docs/readback.md",
|
|
210
|
+
"docs/effects.md",
|
|
211
|
+
"agents/claude/CLAUDE.md",
|
|
212
|
+
"agents/codex/AGENTS.md",
|
|
213
|
+
"agents/copilot/copilot-instructions.md",
|
|
214
|
+
"agents/cursor/campaigns-os.mdc",
|
|
215
|
+
"docs/skills-revision.md"
|
|
196
216
|
]
|
|
197
217
|
}
|
package/docs/build-packet.md
CHANGED
|
@@ -444,17 +444,25 @@ network, which the default run never touches, so it is opt-in:
|
|
|
444
444
|
campaigns-os spec derive --packet campaign-runtime.build.json --from-store <subdomain> [--store-token-source env:<VAR>] [--dry-run] [--json]
|
|
445
445
|
```
|
|
446
446
|
|
|
447
|
-
`<subdomain>` is the store's `<store>.29next.store` subdomain
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
447
|
+
`<subdomain>` is the store's `<store>.29next.store` subdomain. In the
|
|
448
|
+
1.38.0 candidate, the default read uses gateway credentials saved by
|
|
449
|
+
`campaigns-os login --store <subdomain>`, through
|
|
450
|
+
`https://mcp.nextcommerce.com/admin/`. This is an admitted owned-store
|
|
451
|
+
private pilot, not general merchant availability. Missing, expired or uncertain
|
|
452
|
+
credentials require login; gateway failure never falls back to an environment
|
|
453
|
+
token. See [gateway login and migration](gateway-login.md).
|
|
454
|
+
|
|
455
|
+
**Migration for existing direct callers:** add
|
|
456
|
+
`--store-token-source env:<VAR>` explicitly, naming your existing environment
|
|
457
|
+
variable (for example `EXAMPLE_ADMIN_TOKEN`). The former implicit
|
|
458
|
+
`<SUBDOMAIN>_ADMIN_TOKEN` lookup is removed. This break-glass path warns that it
|
|
459
|
+
bypasses gateway custody and contacts
|
|
460
|
+
`https://<subdomain>.29next.store/api/admin/` directly. A `store:read` and
|
|
461
|
+
`content:read` Admin token is sufficient. Tokens are never CLI arguments or
|
|
462
|
+
output; invalid bearer values are refused unsent. Both paths only read the store.
|
|
463
|
+
Gateway page pagination is consolidated by custody into a bounded list; the CLI
|
|
464
|
+
does not follow an upstream cursor on this path. The explicit direct path keeps
|
|
465
|
+
its existing bounded cursor traversal.
|
|
458
466
|
|
|
459
467
|
| Spec field | Store authority |
|
|
460
468
|
|---|---|
|
|
@@ -489,14 +497,17 @@ return). A slug that is not one honest path segment (a separator, `.` or
|
|
|
489
497
|
spec was stale and the diff is the correction, or `--from-store` names
|
|
490
498
|
another merchant's store and the spec should be restored.
|
|
491
499
|
|
|
492
|
-
The result carries a `store` block (`
|
|
493
|
-
|
|
500
|
+
The result carries a `store` block (gateway reads add `transport: "gateway"`
|
|
501
|
+
and the actual gateway `endpoint`; `admin_api` remains the logical upstream
|
|
502
|
+
source), with `subdomain`, `admin_api`,
|
|
503
|
+
`token_source`, `store_read`, `pages_read`, `primary_domain`, and the text
|
|
494
504
|
output a `Store:` line. After a write that moved a store field, `next` is
|
|
495
505
|
`page-kit sync` first (doctor's `page_kit.store_profile` gate now sees the
|
|
496
506
|
spec ahead of the repo and names sync as its repair), then doctor. A store
|
|
497
507
|
that cannot be read is a refusal with nothing written, repo fields included,
|
|
498
|
-
exit 2: `spec.derive.store_credential_missing` (
|
|
499
|
-
|
|
508
|
+
exit 2: `spec.derive.store_credential_missing` (no gateway login, or the explicitly
|
|
509
|
+
selected variable is unset or empty), `store_credential_unavailable` (local
|
|
510
|
+
storage is busy or unavailable), `store_unauthorized` (401/403), `store_not_found` (404: no store at
|
|
500
511
|
that subdomain), `store_unreachable` (transport, timeout, 5xx) or
|
|
501
512
|
`store_response_invalid`. Local preconditions (packet, spec, target entry, spec boundary, page tree)
|
|
502
513
|
are checked before the store is contacted, and a packet that names another
|
|
@@ -1290,7 +1301,7 @@ doctor read for the locked template family (`required`, `family`, `version`,
|
|
|
1290
1301
|
running). This is what `prepare-build` records by default. The catalog
|
|
1291
1302
|
travels with the toolkit, not with the campaign, so the packet does not
|
|
1292
1303
|
record where one machine's checkout or package install kept it, and the
|
|
1293
|
-
same packet resolves on any machine and under `npx campaigns-os`.
|
|
1304
|
+
same packet resolves on any machine and under `npx --no-install campaigns-os`.
|
|
1294
1305
|
- A string `path` is an operator-supplied `--commerce-catalog <path>`,
|
|
1295
1306
|
recorded relative to the packet (keep it inside the campaign repo). Doctor
|
|
1296
1307
|
resolves it against the packet's directory and blocks on
|
package/docs/demo-preview.md
CHANGED
|
@@ -6,7 +6,7 @@ release at least 1.37.0 (or a reviewed full-SHA source pin).
|
|
|
6
6
|
From the folder containing your exact project-local toolkit installation:
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
npx campaigns-os demo --target ./apollo-sample
|
|
9
|
+
npx --no-install campaigns-os demo --target ./apollo-sample
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Open the printed `landing/index.html` file directly. No server or browser
|
package/docs/diagnostics.md
CHANGED
|
@@ -6,12 +6,15 @@ npm, install a reviewed full-SHA source pin as described in the quickstart.
|
|
|
6
6
|
From the campaign folder:
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
npx campaigns-os tooling diagnose --platform codex --packet campaign-runtime.build.json
|
|
10
|
-
npx campaigns-os tooling diagnose --platform codex --packet campaign-runtime.build.json --json > diagnostic.json
|
|
9
|
+
npx --no-install campaigns-os tooling diagnose --platform codex --packet campaign-runtime.build.json
|
|
10
|
+
npx --no-install campaigns-os tooling diagnose --platform codex --packet campaign-runtime.build.json --json > diagnostic.json
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Omit `--packet` for installation and skill diagnostics only.
|
|
14
|
-
|
|
13
|
+
Omit `--packet` for installation and skill diagnostics only. Without
|
|
14
|
+
`--platform`, only the platforms where Campaigns OS skills are installed are
|
|
15
|
+
checked and the export reports `platform: installed` (or `all` when none are
|
|
16
|
+
installed, so every platform was checked); use `--platform claude` to
|
|
17
|
+
check one profile, or `--platform all` for every one. `--context` and `--report` may select existing
|
|
15
18
|
local sidecars; `--target` selects a local skills directory. These inputs are
|
|
16
19
|
never included in the export. The text and JSON forms are suitable for review
|
|
17
20
|
and copying to a support request. The command itself sends nothing.
|
package/docs/effects.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Declared command effects
|
|
2
|
+
|
|
3
|
+
`contracts/effects.v1.json` states, for every supported invocation of this
|
|
4
|
+
toolkit, what it **writes** and what it **sends**. It is the file to read before
|
|
5
|
+
you let an agent run a command it has not run before, and it is the file a tool
|
|
6
|
+
face would read to decide whether an invocation needs a human in the loop.
|
|
7
|
+
|
|
8
|
+
The point of the file is not the prose. It is that **every row is proved by a
|
|
9
|
+
test** (`src/effects.test.mjs`), and a row without its test cannot be published:
|
|
10
|
+
`npm run check:effects` refuses it.
|
|
11
|
+
|
|
12
|
+
- The contract: [`contracts/effects.v1.json`](../contracts/effects.v1.json)
|
|
13
|
+
- Its shape: [`schemas/campaigns-os-effects.v1.schema.json`](../schemas/campaigns-os-effects.v1.schema.json)
|
|
14
|
+
- The proof: `src/effects.test.mjs`
|
|
15
|
+
- The gate: `scripts/check-effects.mjs` (`npm run check:effects`)
|
|
16
|
+
|
|
17
|
+
## The vocabulary
|
|
18
|
+
|
|
19
|
+
### Annotations
|
|
20
|
+
|
|
21
|
+
Four booleans per row, spelled the way an MCP tool face spells them, so a host
|
|
22
|
+
that already understands those hints needs no translation layer.
|
|
23
|
+
|
|
24
|
+
| Annotation | Meaning |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `readOnlyHint` | The invocation changes **nothing**: no file under the target, the working directory or your machine, and no request off the machine. |
|
|
27
|
+
| `destructiveHint` | The invocation can overwrite, clear or discard state that existed before it ran. Only meaningful when `readOnlyHint` is false. |
|
|
28
|
+
| `openWorldHint` | The invocation can contact an endpoint off this machine. True exactly when the row declares at least one send. |
|
|
29
|
+
| `idempotentHint` | Repeating the invocation with the same arguments adds no effect beyond the first run. |
|
|
30
|
+
|
|
31
|
+
**`readOnlyHint` counts the command-lifecycle journal.** A journal append is a
|
|
32
|
+
write like any other, so every `readOnlyHint: true` row is an invocation the
|
|
33
|
+
CLI exempts from lifecycle capture (the converse does not hold: `demo` and the
|
|
34
|
+
`--no-write` forms skip the journal but still write other declared files): `help`, `readback`,
|
|
35
|
+
`run status`, `doctor` inspection, `doctor --no-write`, `sdk storage-check`,
|
|
36
|
+
`tooling diagnose`, a refused invocation, `run-record --no-write`, and every
|
|
37
|
+
`--dry-run` form on the commands that implement the flag. Everything else
|
|
38
|
+
appends an entry when a journal is selected — an active run session,
|
|
39
|
+
`--lifecycle-journal`, or `CAMPAIGNS_OS_LIFECYCLE_LOG` — and is therefore not
|
|
40
|
+
read-only, even when the command writes no artifact of its own. `standardize`
|
|
41
|
+
and `bundle check` are tier `B` for exactly that reason and nothing else; their
|
|
42
|
+
rows say so.
|
|
43
|
+
|
|
44
|
+
### Tiers
|
|
45
|
+
|
|
46
|
+
| Tier | Meaning |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `none` | No effect: nothing written anywhere, nothing sent. |
|
|
49
|
+
| `B` | Writes files under the target, the working directory or your machine. Nothing leaves the machine. |
|
|
50
|
+
| `A` | Can contact an endpoint off this machine (it may write locally too). |
|
|
51
|
+
| `C` | Destructive: overwrites, clears or discards state that was already there (it may send too). |
|
|
52
|
+
|
|
53
|
+
A row carries the **highest** tier it can reach, ranked `none < B < A < C`.
|
|
54
|
+
|
|
55
|
+
### Location tokens
|
|
56
|
+
|
|
57
|
+
A write path is a glob (`*` within one segment, `**` across segments) that opens
|
|
58
|
+
with one of these:
|
|
59
|
+
|
|
60
|
+
| Token | Resolves to |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `{target}` | The target the invocation names: the directory given to `--target`, or the Page Kit target repository the Build Packet points at. |
|
|
63
|
+
| `{cwd}` | The working directory the invocation runs in. |
|
|
64
|
+
| `{spec}` | The CampaignSpec file the Build Packet names (`spec.local_path`), which need not live inside the target repository. |
|
|
65
|
+
| `{home}` | Your machine: the home directory and the config root under it (`XDG_CONFIG_HOME` when set). |
|
|
66
|
+
| `{packet}` | The Build Packet the invocation names (`--packet`), wherever it lives — it need not be the copy inside the target repository. |
|
|
67
|
+
| `{lifecycle-journal}` | The command-lifecycle journal wherever it was selected for this invocation. |
|
|
68
|
+
| `{proxy-base}` | The endpoint `--proxy-base` names, or the canonical NEXT endpoint when it does not. |
|
|
69
|
+
| `{base-url}` | The campaign under test, as `--base-url` names it or as the packet derives it. |
|
|
70
|
+
| `{playwright-download-host}` | Where Playwright fetches browser builds from: `PLAYWRIGHT_DOWNLOAD_HOST` when set, else the Playwright CDN. The one destination in the file that is not a Campaigns OS endpoint — `qa install-browser` is the one supported invocation that downloads from a third party. |
|
|
71
|
+
|
|
72
|
+
The tokens matter because effects are not all under the target. `install-skills`
|
|
73
|
+
writes your **home** directory, not the campaign. `telemetry on` writes your
|
|
74
|
+
**machine** config. `run-record` writes beside the **working directory**, not the
|
|
75
|
+
target repo. A row that said "writes the target" would be wrong about all three.
|
|
76
|
+
|
|
77
|
+
## How to read a row
|
|
78
|
+
|
|
79
|
+
```jsonc
|
|
80
|
+
{
|
|
81
|
+
"command": "page-kit",
|
|
82
|
+
"subcommand": "sync",
|
|
83
|
+
"flags": [], // the base form; --dry-run is its own row
|
|
84
|
+
"annotations": { "readOnlyHint": false, "destructiveHint": false,
|
|
85
|
+
"openWorldHint": false, "idempotentHint": true },
|
|
86
|
+
"tier": "B",
|
|
87
|
+
"writes": [
|
|
88
|
+
{ "path": "{target}/_data/campaigns.json",
|
|
89
|
+
"when": "one of the ten Store Profile / SDK-pin fields is usable and differs from the entry",
|
|
90
|
+
"observed_in": ["no_session", "ambient_session", "stale_session", "lifecycle_log"] }
|
|
91
|
+
// …
|
|
92
|
+
],
|
|
93
|
+
"sends": [],
|
|
94
|
+
"effect_test": "effects: page-kit sync",
|
|
95
|
+
"test_scope": "full",
|
|
96
|
+
"notes": "Writes only those ten fields of the packet's route entry…"
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
There is **one row per command and per effect-changing flag combination**. The
|
|
101
|
+
flags that change what the invocation does to the world are listed once, in
|
|
102
|
+
`vocabulary.effect_changing_flags`: `--browser`, `--built`, `--dry-run`,
|
|
103
|
+
`--emit-packet`, `--example`, `--force`, `--from-store`, `--list`,
|
|
104
|
+
`--no-post-verdict`, `--no-probe`, `--no-remit`, `--no-run-session`,
|
|
105
|
+
`--no-write`, `--republish`, `--test-order`, `--write`, `--write-map`. Flags
|
|
106
|
+
that only change the output shape (`--json`, `--report`) deliberately do not.
|
|
107
|
+
|
|
108
|
+
**"The help text" is every help block the CLI prints**, not one file's.
|
|
109
|
+
`campaigns-os qa` prints its own from `src/qa-node.mjs`, and while the coverage
|
|
110
|
+
scan read only `src/cli.mjs` the three subcommands documented there alone — `qa
|
|
111
|
+
parity`, `qa waive` and `qa install-browser` — owed no row, had none, and the
|
|
112
|
+
gate stayed green. Every module that owns a usage block is listed in
|
|
113
|
+
`HELP_SOURCE_PATHS` and scanned the same way; a test derives that list from the
|
|
114
|
+
source, so a command that grows its own help cannot quietly leave the scan.
|
|
115
|
+
|
|
116
|
+
**Every one of those flags that a help usage line carries owes a row**, and
|
|
117
|
+
`scripts/check-effects.mjs` fails when one does not have it. Coverage by command
|
|
118
|
+
alone was not enough: deleting the `page-kit sync --dry-run` row, or the
|
|
119
|
+
`doctor --write` row, left the gate green while the file lost an effect —
|
|
120
|
+
`doctor --write` writes the doctor sidecar, the assembly report and the packet
|
|
121
|
+
that plain `doctor` does not. A flag that appears in a usage line for a command
|
|
122
|
+
that has only a base row is now the loudest kind of failure this gate has.
|
|
123
|
+
|
|
124
|
+
One row is not a command at all: `{"command": "*refused*"}` is any invocation
|
|
125
|
+
refused before its handler runs — an unknown command, an unknown subcommand, or
|
|
126
|
+
a flag the command rejects up front. It writes nothing, journals nothing, and is
|
|
127
|
+
the row to read when you want to know what a typo costs. The one exception is
|
|
128
|
+
declared on the rows it belongs to: `start`, `prepare-build`, `build`,
|
|
129
|
+
`run start` and `run end` close out a **stale** run session at the root they are
|
|
130
|
+
about to act on *before* argv is refused.
|
|
131
|
+
|
|
132
|
+
## How a row is proved
|
|
133
|
+
|
|
134
|
+
`src/effects.test.mjs` runs the real CLI in a disposable target seeded from
|
|
135
|
+
`examples/`, under **five conditions**, and snapshots the whole tree (paths plus
|
|
136
|
+
sha256) before and after while a loopback `node:http` receiver counts requests.
|
|
137
|
+
|
|
138
|
+
| Condition | What it sets up |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `no_session` | No run session at the target or the working directory. |
|
|
141
|
+
| `ambient_session` | An active ambient run session opened by `run start` at the target. |
|
|
142
|
+
| `stale_session` | A run session idle past the 12 h TTL, at the target and at the working directory. |
|
|
143
|
+
| `lifecycle_log` | `CAMPAIGNS_OS_LIFECYCLE_LOG` names a journal outside the runtime directory. |
|
|
144
|
+
| `persisted_consent` | Run Telemetry consent **persisted on the machine for the loopback receiver's scope**, a synthetic campaign key in the environment, no run session, and `--proxy-base <loopback>` wherever the command takes it. |
|
|
145
|
+
|
|
146
|
+
Five conditions rather than one, because the CLI's effects are not a function of
|
|
147
|
+
argv alone: an ambient session redirects the journal and is itself touched by
|
|
148
|
+
session resolution, and a stale session is closed out — Run Record assembled —
|
|
149
|
+
before some commands even read argv.
|
|
150
|
+
|
|
151
|
+
### Why the fifth condition exists
|
|
152
|
+
|
|
153
|
+
Under the first four, consent is `CAMPAIGNS_OS_TELEMETRY=off` unless the row
|
|
154
|
+
declares a consent-gated send it expects to see in that condition; then the row
|
|
155
|
+
runs with consent on and the loopback receiver as its endpoint, so "nothing was
|
|
156
|
+
sent" is not an artefact of consent being off **for a send that is declared**.
|
|
157
|
+
|
|
158
|
+
That took the row's word for which sends exist, and it hid real ones: `next` and
|
|
159
|
+
its five stage forms, and all three `qa run` rows, declared `sends: []` while
|
|
160
|
+
each of them POSTed — to `{proxy-base}/api/progress`, and for `qa run` to
|
|
161
|
+
`{proxy-base}/api/qa/verdicts` as well, on blocked attempts included.
|
|
162
|
+
|
|
163
|
+
`persisted_consent` does not read consent from the row. It persists consent the
|
|
164
|
+
way an operator does — `campaigns-os telemetry on --proxy-base <loopback>`,
|
|
165
|
+
which is a **scoped** record — and runs every row that way. An environment
|
|
166
|
+
override is not equivalent and is the reason the earlier probe found nothing: an
|
|
167
|
+
env grant carries no scope, so the remit refuses it for a non-canonical endpoint
|
|
168
|
+
(`scope_bypassed`) and delivers nothing. Any request the receiver sees that no
|
|
169
|
+
declared send covers fails the row.
|
|
170
|
+
|
|
171
|
+
The same scoping is what keeps the suite off the network. The remit endpoint is
|
|
172
|
+
a hard-coded constant with no environment override, so a command that falls back
|
|
173
|
+
to the canonical endpoint resolves consent **off** (the persisted grant covers
|
|
174
|
+
the loopback scope only) and sends nothing. That is asserted, not assumed: every
|
|
175
|
+
invocation in this condition runs under `NODE_DEBUG=net` and its connection log
|
|
176
|
+
must name no host but `127.0.0.1`, and one case states the claim directly for
|
|
177
|
+
`next` with no `--proxy-base` at all.
|
|
178
|
+
|
|
179
|
+
The assertion runs both ways, and that is what makes the file falsifiable:
|
|
180
|
+
|
|
181
|
+
1. **Nothing undeclared may change**, in any condition. A `readOnlyHint: true`
|
|
182
|
+
row declares no writes, so any byte that moves fails it.
|
|
183
|
+
2. **Every declared effect whose `observed_in` names a condition must be seen**
|
|
184
|
+
in it, so a row cannot be padded with effects that never happen.
|
|
185
|
+
|
|
186
|
+
`observed_in` is per effect, not per row: `install-agent-context` writes
|
|
187
|
+
`{target}/.gitignore` only when the target does not already ignore the runtime
|
|
188
|
+
directory, so that entry is observed in three conditions and not under
|
|
189
|
+
`ambient_session`, where `run start` has already added the line.
|
|
190
|
+
|
|
191
|
+
### Rows proved at the preflight
|
|
192
|
+
|
|
193
|
+
Some invocations cannot execute past their preflight with no network, no
|
|
194
|
+
browser, no renderer and no credentials. Those rows carry
|
|
195
|
+
`test_scope: "preflight"`. Their case proves the preflight refusal writes
|
|
196
|
+
nothing beyond what the row declares and — where a loopback receiver can stand
|
|
197
|
+
in for the destination — that the **declared destination is the one contacted**.
|
|
198
|
+
|
|
199
|
+
| Row | What the offline fixture cannot reach |
|
|
200
|
+
| --- | --- |
|
|
201
|
+
| `login` | A reachable login gateway and a human at a browser. Proved: the failure path writes nothing at all — no credential, no journal entry. |
|
|
202
|
+
| `logout` | A credential minted by a gateway login. Proved: the no-credential path writes nothing. |
|
|
203
|
+
| `page-kit parity` | A `local-serve` deploy target and a page-kit renderer to build the two renders with. Proved: the refusal writes nothing but the journal entry. |
|
|
204
|
+
| `polish capture` | An installed browser and a reachable `--base-url`. Proved: the refusal writes nothing but the journal entry and contacts nothing. |
|
|
205
|
+
| `qa install-browser` | The Playwright CDN, and the ~150 MB Chromium archive it serves. Proved: the failed download writes exactly one path under your machine — the registry's link entry — and nothing else anywhere, and leaves the machine zero times. |
|
|
206
|
+
| `qa parity` (and `--no-post-verdict`) | An installed Chromium and a reachable candidate funnel. Proved: the refusal writes nothing but the journal entry and contacts the stand-in for `--base-url` zero times. |
|
|
207
|
+
| `qa resolve` | A resolution that is not blocked before the probe. Proved: the blocked resolution contacts the stand-in zero times. |
|
|
208
|
+
| `qa run --browser` | An installed Chromium and a reachable campaign. Proved: the attempt is blocked at the same gate as the node run and writes exactly the blocked-attempt evidence. |
|
|
209
|
+
| `spec derive --from-store` | A live gateway and a real store credential. Proved: the credential refusal writes nothing under the target or the spec. |
|
|
210
|
+
| `spec derive --write-map` | A Map whose `spec_hash` precondition a loopback stand-in can satisfy, so the `PUT` is never reached. Proved: the declared destination **is** the one contacted (the receiver sees the Map read), and the refusal adds no report evidence. |
|
|
211
|
+
| `telemetry list` | A real ops admin key and a real endpoint. Proved: the receiver sees the declared `GET /api/runs`, and the refusal writes nothing but the journal entry. |
|
|
212
|
+
|
|
213
|
+
#### What a preflight row is allowed to touch
|
|
214
|
+
|
|
215
|
+
A preflight row declares its allowances **separately from its effects**, and the
|
|
216
|
+
test enforces them independently:
|
|
217
|
+
|
|
218
|
+
```jsonc
|
|
219
|
+
"preflight": {
|
|
220
|
+
"may_write": ["{lifecycle-journal}"], // the ONLY paths the refusal may write
|
|
221
|
+
"may_contact": ["/api/runs"] // the exact request paths the receiver may see
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Both halves close a hole that a declared effect used to open. `logout` declared
|
|
226
|
+
`{home}/**` for the credential a *completed* login writes — and that declaration
|
|
227
|
+
also licensed its refusal to write anywhere under the home directory, so a
|
|
228
|
+
home-directory write injected into the preflight passed. And a destination a
|
|
229
|
+
loopback receiver only stands in for (`{base-url}`, the login gateway) matched
|
|
230
|
+
**any** request path, so an injected endpoint passed too. Now:
|
|
231
|
+
|
|
232
|
+
- `may_write` is the whole permission. It may not name a whole location
|
|
233
|
+
(`{target}`, `{target}/**`) and may not span segments under `{home}` — the
|
|
234
|
+
skills directories, the credential store and the consent file all live there,
|
|
235
|
+
under the temporary `HOME` the test sets, and a preflight that writes one of
|
|
236
|
+
them has to say which.
|
|
237
|
+
- `may_contact` is matched literally against the request path, so an `/api/`
|
|
238
|
+
call nobody declared fails the row even when the row declares a stand-in
|
|
239
|
+
destination. (On a `full` row the same rule holds one step down: a stand-in
|
|
240
|
+
destination never covers an `/api/` path, because every API endpoint in this
|
|
241
|
+
contract is declared as `{proxy-base}/api/…`.)
|
|
242
|
+
- A write the row declares as observed must also be in `may_write`; the gate
|
|
243
|
+
refuses the contradiction rather than letting the test find it.
|
|
244
|
+
|
|
245
|
+
A `full` row may still carry an individual effect the offline fixture cannot
|
|
246
|
+
reach — the Map Builder spec fetch behind `--map-id`, the `codex` and `agents`
|
|
247
|
+
destinations of `install-skills`. Each such entry has an empty `observed_in`
|
|
248
|
+
**and** a `not_observed_reason`, and `check-effects.mjs` refuses one without the
|
|
249
|
+
reason. What it may not be is silent.
|
|
250
|
+
|
|
251
|
+
## The rule
|
|
252
|
+
|
|
253
|
+
**A row without its test is not published.** `scripts/check-effects.mjs` (in
|
|
254
|
+
`npm run check` and `npm run check:contracts`) fails when:
|
|
255
|
+
|
|
256
|
+
- a command on the supported CLI surface, a subcommand any help block teaches
|
|
257
|
+
(`src/cli.mjs` and `src/qa-node.mjs`), or an effect-changing flag a help usage
|
|
258
|
+
line carries, has no row;
|
|
259
|
+
- a row names no `effect_test`, names one `src/effects.test.mjs` does not
|
|
260
|
+
declare, or names one the per-row generator would not produce (the cases are
|
|
261
|
+
generated from this file, so an unchecked name made the link vacuous);
|
|
262
|
+
- a row has no entry in the test's `INVOCATIONS` table, or the table has an
|
|
263
|
+
entry no row claims — a generated case with no argv proves nothing;
|
|
264
|
+
- an effect declares no `observed_in` and no `not_observed_reason`;
|
|
265
|
+
- a `test_scope: "preflight"` row does not say in its own notes what it cannot
|
|
266
|
+
reach, declares no `preflight` allowances, licenses a whole location or a
|
|
267
|
+
home-directory subtree, names a `may_contact` entry that is not a request
|
|
268
|
+
path, or has an observed write its `may_write` does not allow; a
|
|
269
|
+
`test_scope: "full"` row carries allowances, or has no effect observed
|
|
270
|
+
anywhere;
|
|
271
|
+
- the annotations disagree with the row (`readOnlyHint` with declared effects,
|
|
272
|
+
`openWorldHint` without a send, `destructiveHint` off tier `C`);
|
|
273
|
+
- two rows claim the same invocation, or a write path opens with no known
|
|
274
|
+
location token;
|
|
275
|
+
- `vocabulary.conditions` names a condition `src/effects.test.mjs` does not run.
|
|
276
|
+
|
|
277
|
+
## When you change a command
|
|
278
|
+
|
|
279
|
+
Change the effect, change the row, in the same PR. The effect test will tell you
|
|
280
|
+
which row is wrong before review does: it names the path that moved and the row
|
|
281
|
+
that failed to declare it.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Gateway login and direct-token migration
|
|
2
|
+
|
|
3
|
+
The 1.38.0 candidate supports the admitted owned-store private gateway pilot
|
|
4
|
+
with the registered Campaigns OS CLI client. It is not general merchant
|
|
5
|
+
availability. Publication, external trials and additional clients are separately
|
|
6
|
+
gated. A successful owned-store drill does not prove automatic uninstall
|
|
7
|
+
handling or authorize other stores.
|
|
8
|
+
|
|
9
|
+
## Sign in
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
campaigns-os login --store example
|
|
13
|
+
# The equivalent canonical host is example.29next.store.
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`--store` is optional in an interactive terminal: one prompt asks for the store.
|
|
17
|
+
There is no discovery or guess from the current project. Noninteractive calls
|
|
18
|
+
must supply `--store`. URLs, paths and unrelated hosts are refused before any
|
|
19
|
+
request. Login uses the fixed `https://mcp.nextcommerce.com` gateway.
|
|
20
|
+
|
|
21
|
+
Open the displayed device page in one browser tab and enter the displayed code.
|
|
22
|
+
Keep that tab: if installation is needed, follow its Install Campaigns link,
|
|
23
|
+
sign in to the store dashboard and launch Campaigns. Match the code and explicitly
|
|
24
|
+
allow reads. Return to the CLI. Pilot admission is operator controlled; knowing
|
|
25
|
+
the store host or device-page URL is not an invitation. Never paste a dashboard
|
|
26
|
+
or Admin token into the CLI. Login waits for browser consent within the device
|
|
27
|
+
code's expiry; denial, timeout and failed persistence preserve the prior login.
|
|
28
|
+
|
|
29
|
+
Gateway access and refresh credentials are stored outside the project. On macOS,
|
|
30
|
+
the CLI prefers the user keychain; when unavailable it uses private user files
|
|
31
|
+
under `~/.campaigns-os/credentials` (directory `0700`, files `0600`). Keychain
|
|
32
|
+
selection metadata lives there too. The parent must be owned by the user and
|
|
33
|
+
not group/other writable. Credential paths reject symlinks; a symlinked home is
|
|
34
|
+
not supported in this pilot. Do not copy these files into a repository, support
|
|
35
|
+
export or CI secret bundle. These are gateway credentials, not platform Admin
|
|
36
|
+
tokens; platform OAuth custody stays server side.
|
|
37
|
+
|
|
38
|
+
## Migrate store reads
|
|
39
|
+
|
|
40
|
+
The default changed. A command that formerly read `EXAMPLE_ADMIN_TOKEN`
|
|
41
|
+
automatically now requires a gateway login for the selected store:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
campaigns-os login --store example
|
|
45
|
+
campaigns-os spec derive --packet campaign-runtime.build.json --from-store example --dry-run
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Existing direct callers, including callers outside the admitted pilot, can retain
|
|
49
|
+
the direct path by explicitly naming their existing environment variable:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
campaigns-os spec derive --packet campaign-runtime.build.json --from-store example --store-token-source env:EXAMPLE_ADMIN_TOKEN --dry-run
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This break-glass path emits a warning and bypasses gateway custody. Its Admin
|
|
56
|
+
token needs `store:read` and `content:read`. Do not put its value in argv.
|
|
57
|
+
The default never checks that variable or falls back to it after a gateway error.
|
|
58
|
+
An unavailable gateway fails closed. Gateway reads use `/admin/store/` and
|
|
59
|
+
`/admin/pages/`; custody consolidates bounded upstream page results. Derivation
|
|
60
|
+
still uses the same nine Store Profile fields and leaves missing or ambiguous
|
|
61
|
+
values unchanged. The output distinguishes the actual gateway endpoint from
|
|
62
|
+
the logical store Admin API source. See [Store Profile derivation](build-packet.md).
|
|
63
|
+
|
|
64
|
+
Refresh is serialized per store binding. The CLI records a pending state before
|
|
65
|
+
sending a refresh, then atomically saves the confirmed winning pair. A lost
|
|
66
|
+
response, interrupted process or uncertain save requires a new login; the next
|
|
67
|
+
invocation must not replay an old refresh. An expired absolute grant also requires
|
|
68
|
+
login. A refresh may happen before access expires, or once after an unauthorized
|
|
69
|
+
read; it is not an unlimited retry loop.
|
|
70
|
+
|
|
71
|
+
## Inspect and recover
|
|
72
|
+
|
|
73
|
+
`campaigns-os tooling status --json` includes `gateway_login` metadata for saved
|
|
74
|
+
bindings, without `--store` or project inference. It shows store, local access
|
|
75
|
+
expiry/remaining time and the gateway version reported when credentials were
|
|
76
|
+
issued. It makes no gateway validity request and exports no credential values.
|
|
77
|
+
`logged_in` means the saved access expiry is in the future, not that the remote
|
|
78
|
+
grant is still valid. `access_expired` can still refresh on use; `login_required`
|
|
79
|
+
means reauthorize. A reported version such as `a3-offline` is metadata, not proof
|
|
80
|
+
of deployed source identity. `tooling diagnose` remains a separate redacted
|
|
81
|
+
support export and omits gateway login/store metadata.
|
|
82
|
+
|
|
83
|
+
Storage contention waits up to three seconds, then reports unavailable/busy.
|
|
84
|
+
Retry after the other CLI finishes; check user-directory permissions and keychain
|
|
85
|
+
access. One malformed or unreadable record makes the whole gateway status
|
|
86
|
+
unavailable in this pilot; it does not prove that all stores are logged out.
|
|
87
|
+
After a crash, confirm no Campaigns OS process is running before removing the
|
|
88
|
+
stale binding's `.lock` directory under `~/.campaigns-os/credentials`. Never
|
|
89
|
+
remove another live process's lock. If selection metadata is damaged, preserve
|
|
90
|
+
it privately and repair or move aside only that binding's broken selection file
|
|
91
|
+
before logging in again; this does not remotely revoke an old grant. Do not
|
|
92
|
+
bypass ownership or symlink checks by making the directory world writable.
|
|
93
|
+
|
|
94
|
+
## Sign out
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
campaigns-os logout --store example
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Logout uses the same optional interactive store prompt. It attempts gateway
|
|
101
|
+
revocation and clears the local selected login. Its message distinguishes
|
|
102
|
+
confirmed remote revocation from an unrecognized grant, failed request or
|
|
103
|
+
unreadable local record. Local cleanup alone is not proof of remote revocation;
|
|
104
|
+
a failed keychain-item cleanup is reported separately. A pending/uncertain
|
|
105
|
+
refresh is never replayed during logout. If remote revocation is unconfirmed,
|
|
106
|
+
use the pilot operator's grant-revocation procedure; do not assume uninstall or
|
|
107
|
+
local file deletion revoked it.
|
|
108
|
+
|
|
109
|
+
Login, logout and the offline demo bypass lifecycle capture; login/logout do not
|
|
110
|
+
accept general lifecycle flags. `tooling diagnose` also bypasses lifecycle
|
|
111
|
+
capture. Gateway login does not grant telemetry administration:
|
|
112
|
+
`CAMPAIGN_OPS_ADMIN_KEY` remains a separate cross-tenant `/api/runs` credential,
|
|
113
|
+
with its existing trusted-origin safeguards and explicit warning.
|
|
@@ -25,7 +25,7 @@ Ledger schema id: `campaigns-os-release-ledger/v1`
|
|
|
25
25
|
Change policy version: `1.0.0`
|
|
26
26
|
Reason-code vocabulary version: `1.0.0`
|
|
27
27
|
Limits version: `1.0.0`
|
|
28
|
-
Supported surface at generation time: `1.
|
|
28
|
+
Supported surface at generation time: `1.41.2`
|
|
29
29
|
|
|
30
30
|
## Forward compatibility
|
|
31
31
|
|
|
@@ -244,6 +244,9 @@ so a renamed command fails here as well as at the supported-surface gate.
|
|
|
244
244
|
- `campaigns-os run-record`
|
|
245
245
|
- `campaigns-os run`
|
|
246
246
|
- `campaigns-os demo`
|
|
247
|
+
- `campaigns-os login`
|
|
248
|
+
- `campaigns-os logout`
|
|
249
|
+
- `campaigns-os readback`
|
|
247
250
|
|
|
248
251
|
## Terminal outcome examples
|
|
249
252
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Minimal progress observations
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
export and `schemas/campaigns-os-progress-snapshot.v0.schema.json
|
|
5
|
-
|
|
3
|
+
Release **1.36.0** adds the portable `@nextcommerce/campaigns-os/progress`
|
|
4
|
+
export and `schemas/campaigns-os-progress-snapshot.v0.schema.json`; it first
|
|
5
|
+
shipped in 1.37.1 and is in every later release. Progress is a compact
|
|
6
6
|
observation of the existing lifecycle, not a second workflow or proof of readiness.
|
|
7
7
|
|
|
8
8
|
`next --packet <packet>` records the canonical picker result after the same doctor
|
|
@@ -1692,9 +1692,9 @@ a trusted submission attests the runner, not execution or resource identity.
|
|
|
1692
1692
|
|
|
1693
1693
|
### Playwright updates and consumer installs
|
|
1694
1694
|
|
|
1695
|
-
After installing or updating Campaigns OS, run
|
|
1696
|
-
|
|
1697
|
-
installation). This resolves the same Playwright package as QA and polish capture.
|
|
1695
|
+
After installing or updating Campaigns OS, run
|
|
1696
|
+
`npx --no-install campaigns-os qa install-browser` from the campaign project
|
|
1697
|
+
(or `campaigns-os qa install-browser` for a global installation). This resolves the same Playwright package as QA and polish capture.
|
|
1698
1698
|
A project's own `npx playwright install` can resolve a different version and install
|
|
1699
1699
|
a different Chromium build. Campaigns OS is an optional-dependency owner, not a
|
|
1700
1700
|
Playwright peer dependency: npm may share a compatible copy or install a nested one.
|