@nextcommerce/campaigns-os 1.41.2 → 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 (56) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +6 -4
  3. package/agents/claude/CLAUDE.md +5 -1
  4. package/campaign-spec/dist/types.d.ts +2 -0
  5. package/contracts/effects.v1.json +111 -18
  6. package/contracts/migration-sidecar-bundle.v0.json +9 -0
  7. package/contracts/release-ledger.json +752 -0
  8. package/contracts/supported-surface.json +12 -11
  9. package/docs/build-packet.md +56 -6
  10. package/docs/campaigns-os-build-flow.md +2 -2
  11. package/docs/effects.md +70 -1
  12. package/docs/local-setup.md +51 -0
  13. package/docs/migration-sidecar-bundle.md +6 -1
  14. package/docs/orientation-contract-reference.md +1 -1
  15. package/docs/progress-snapshots.md +6 -0
  16. package/docs/qa-and-test-orders.md +26 -10
  17. package/docs/runtime-readiness.md +1 -1
  18. package/docs/skills-revision.md +10 -10
  19. package/package.json +3 -2
  20. package/schemas/campaign-runtime-assembly-report.v0.schema.json +6 -1
  21. package/schemas/campaign-runtime-build-packet.v0.schema.json +6 -1
  22. package/schemas/campaign-spec.v4.schema.json +4 -0
  23. package/schemas/campaigns-os-progress-snapshot.v0.schema.json +1 -0
  24. package/schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json +1 -0
  25. package/schemas/campaigns-os-qa-verdict.v0.schema.json +1 -0
  26. package/schemas/campaigns-os-run-record.v0.schema.json +1 -0
  27. package/skills/campaign-lifecycle-orientation/SKILL.md +13 -8
  28. package/skills/campaign-readback-classification/SKILL.md +3 -3
  29. package/skills/campaign-run-evidence/SKILL.md +8 -6
  30. package/skills/contribution-intake/SKILL.md +3 -3
  31. package/skills/next-campaigns-build/SKILL.md +3 -3
  32. package/skills/next-campaigns-os/SKILL.md +17 -4
  33. package/skills/next-campaigns-os/references/session-intake.md +4 -4
  34. package/skills/next-campaigns-os-setup/SKILL.md +3 -3
  35. package/skills/next-campaigns-polish/SKILL.md +3 -3
  36. package/skills/next-campaigns-qa/SKILL.md +10 -9
  37. package/skills.json +11 -11
  38. package/src/cli.mjs +187 -70
  39. package/src/finding-cause.mjs +14 -10
  40. package/src/lifecycle.mjs +5 -4
  41. package/src/polish-node.mjs +5 -2
  42. package/src/progress-node.mjs +3 -2
  43. package/src/progress.mjs +5 -3
  44. package/src/qa-node.mjs +58 -26
  45. package/src/qa-publish.mjs +4 -0
  46. package/src/qa-sidecar.mjs +2 -0
  47. package/src/qa-verdict-discovery.mjs +11 -0
  48. package/src/qa-verdict-publish.mjs +1 -0
  49. package/src/qa-verdict.mjs +8 -1
  50. package/src/readback.mjs +2 -1
  51. package/src/run-record-closeout.mjs +3 -4
  52. package/src/run-record.mjs +4 -0
  53. package/src/sidecar-bundle.mjs +21 -0
  54. package/src/spec-source-identity.mjs +44 -0
  55. package/src/stage-ledger.mjs +4 -1
  56. package/src/tooling-setup.mjs +160 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,184 @@
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
+
5
183
  ## [1.41.2] - 2026-09-23
6
184
 
7
185
  ### Fixed
package/README.md CHANGED
@@ -5,8 +5,8 @@ Campaigns OS is the developer toolkit for agent-assisted campaign builds on [Nex
5
5
  This toolkit gives campaign developers and AI coding tools a clear path for assembling one from prepared page files:
6
6
 
7
7
  1. Configure the campaign in the Next Commerce dashboard (Campaigns App).
8
- 2. Create or review the Campaign Map in [Campaign Map Builder](https://campaign-map.nextcommerce.com).
9
- 3. Export a local CampaignSpec JSON.
8
+ 2. Use the current saved Map in [Campaign Map Builder](https://campaign-map.nextcommerce.com), or have the coding agent author a [local CampaignSpec](docs/build-packet.md#local-spec-entry) from the brief and verified campaign values.
9
+ 3. Keep the CampaignSpec JSON with its saved Map ID or stable `local_spec_id`, plus its public route slug.
10
10
  4. Bring prepared HTML/CSS/assets for the campaign pages.
11
11
  5. Provide or generate a [Campaign Build Brief](./docs/campaign-build-brief.md) for merchandising/design presentation decisions.
12
12
  6. Create and doctor a Build Packet.
@@ -119,7 +119,9 @@ npx --no-install campaigns-os next --packet ./campaign-runtime.build.json --json
119
119
 
120
120
  `--map-id <id>` starts from a map saved in Campaign Map Builder (add
121
121
  `--proxy-base <origin>` when the map was saved on a non-production map store);
122
- `--spec <campaignspec.json>` starts from a local export instead. `--source` is
122
+ `--spec <campaignspec.json>` starts from a local export or an agent-authored
123
+ [local spec](docs/build-packet.md#local-spec-entry) instead. Local-spec identity
124
+ requires a reviewed 1.43.0-or-later release. `--source` is
123
125
  always required: the folder of prepared HTML/CSS/assets for the pages you are
124
126
  building, with a source manifest that carries desktop and mobile screenshot
125
127
  proof for each designed page
@@ -228,7 +230,7 @@ Then ask your AI tool to continue from the emitted handoff. Fresh target repos u
228
230
 
229
231
  ## Source Files
230
232
 
231
- The current source adapter is `html_funnel`: bring prepared HTML/CSS/assets for the campaign pages, plus a local exported CampaignSpec from Campaign Map Builder.
233
+ The current source adapter is `html_funnel`: bring prepared HTML/CSS/assets for the campaign pages, plus a CampaignSpec exported from Campaign Map Builder or authored by the coding agent through the [local-spec entry](docs/build-packet.md#local-spec-entry).
232
234
 
233
235
  For raw AI-generated or exported static HTML, "prepared" means page-kit-ready
234
236
  source, not a browser document dropped in unchanged and not a wholesale Liquid
@@ -1,6 +1,10 @@
1
1
  # Campaigns OS Agent Context
2
2
 
3
- You are helping assemble a NEXT campaign through Campaigns OS. Start from the Build Packet, not from private runtime source.
3
+ You are helping assemble a NEXT campaign through Campaigns OS. Use the
4
+ `next-campaigns-os` skill from this project's pinned toolkit. When a Build
5
+ Packet exists, read it and follow `next`. Check the loaded skill's bundle
6
+ revision and restart the session if it differs from the
7
+ project copy. Do not use private runtime source as the campaign's starting point.
4
8
 
5
9
  Core rules:
6
10
 
@@ -593,6 +593,8 @@ export interface AnalyticsContract {
593
593
  * local-experimental.
594
594
  */
595
595
  export interface SpecIdentity {
596
+ /** Stable agent-authored identity when there is no saved Map. */
597
+ local_spec_id?: string;
596
598
  map_id?: string;
597
599
  source?: string;
598
600
  id?: string;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": "campaigns-os-effects/v1",
3
3
  "_note": "The declared effect of every supported invocation: what it writes, what it sends, and the test case that proves it. One row per command, per subcommand and per effect-changing flag (vocabulary.effect_changing_flags). A row is not publishable without its effect test — scripts/check-effects.mjs refuses one, and refuses a flag from that list that a help usage line carries and no row declares. Read this with docs/effects.md.",
4
- "contract_version": "1.1.0",
4
+ "contract_version": "1.2.0",
5
5
  "vocabulary": {
6
6
  "annotations": {
7
7
  "readOnlyHint": "The invocation changes nothing: no file under the target, the working directory or the user's machine, and no request off the machine. The command-lifecycle journal counts — a journal append is a write like any other, so every readOnlyHint: true row is an invocation the CLI exempts from lifecycle capture (the converse does not hold: some journal-exempt invocations, such as demo and the --no-write forms, still write other declared files).",
@@ -26,7 +26,7 @@
26
26
  "{proxy-base}": "The endpoint --proxy-base names, or the canonical NEXT endpoint when it does not.",
27
27
  "{base-url}": "The campaign under test, as --base-url names it or as the packet derives it.",
28
28
  "{map-id}": "The saved Map the CampaignSpec identifies.",
29
- "{playwright-download-host}": "Where Playwright fetches browser builds from: PLAYWRIGHT_DOWNLOAD_HOST when set, else the Playwright CDN (cdn.playwright.dev). The one destination in this file that is not a Campaigns OS endpoint — `qa install-browser` is the one supported invocation that downloads from a third party."
29
+ "{playwright-download-host}": "Where Playwright fetches browser builds from: PLAYWRIGHT_DOWNLOAD_HOST when set, else the Playwright CDN (cdn.playwright.dev). The third-party browser download used by `qa install-browser` and `tooling setup`."
30
30
  },
31
31
  "conditions": {
32
32
  "no_session": "No run session at the target or the working directory.",
@@ -312,7 +312,7 @@
312
312
  "sends": [],
313
313
  "effect_test": "effects: qa publish --dry-run",
314
314
  "test_scope": "full",
315
- "notes": "qa publish --dry-run runs every check the real invocation runs and prints what it would do; it writes nothing under the target, appends no lifecycle entry (DRY_RUN_COMMANDS) and runs no stale-session closeout."
315
+ "notes": "qa publish --dry-run runs every check the real invocation runs and prints what it would do; it writes nothing under the target, appends no lifecycle entry (DRY_RUN_COMMANDS) and runs no stale-session closeout. Local-spec packets are refused before publication because they have no saved Map destination."
316
316
  },
317
317
  {
318
318
  "command": "checkpoint",
@@ -2066,7 +2066,7 @@
2066
2066
  {
2067
2067
  "destination": "{proxy-base}/api/qa/verdicts",
2068
2068
  "what": "the QA verdict for the attempt, published to the QA portal",
2069
- "when": "publish is not opted out (--no-post-verdict) and the consent seam allows it — Run Telemetry consent on for the endpoint, or a portal-managed campaign",
2069
+ "when": "publish is not opted out (--no-post-verdict) and the consent seam allows it — Run Telemetry consent on for the endpoint, or a portal-managed campaign; never for a local-spec packet",
2070
2070
  "requires_consent": true,
2071
2071
  "observed_in": [
2072
2072
  "persisted_consent"
@@ -2091,7 +2091,7 @@
2091
2091
  {
2092
2092
  "destination": "{proxy-base}/api/progress",
2093
2093
  "what": "the stage progress observation — the snapshot just captured locally, POSTed under X-Campaign-Key",
2094
- "when": "Run Telemetry consent is on for the endpoint, --no-remit was not given, and the packet's campaign key resolves",
2094
+ "when": "Run Telemetry consent is on for the endpoint, --no-remit was not given, and the packet's campaign key resolves; never for a local-spec packet",
2095
2095
  "requires_consent": true,
2096
2096
  "observed_in": [
2097
2097
  "persisted_consent"
@@ -2100,7 +2100,7 @@
2100
2100
  ],
2101
2101
  "effect_test": "effects: qa run",
2102
2102
  "test_scope": "full",
2103
- "notes": "Destructive and not idempotent: each attempt overwrites the committed verdict sidecar and the report's QA stage, and a ready verdict clears the ambient run session. It SENDS on a blocked attempt too: the verdict is published to {proxy-base}/api/qa/verdicts and the QA stage progress observation to {proxy-base}/api/progress, both under persisted consent for the endpoint. Publishing rides the telemetry consent seam rather than a flag of its own, so the four consent-off conditions saw neither request."
2103
+ "notes": "Destructive and not idempotent: each attempt overwrites the committed verdict sidecar and the report's QA stage, and a ready verdict clears the ambient run session. It SENDS on a blocked attempt too: the verdict is published to {proxy-base}/api/qa/verdicts and the QA stage progress observation to {proxy-base}/api/progress, both under persisted consent for the endpoint. Publishing rides the telemetry consent seam rather than a flag of its own, so the four consent-off conditions saw neither request. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict."
2104
2104
  },
2105
2105
  {
2106
2106
  "command": "qa",
@@ -2191,7 +2191,7 @@
2191
2191
  {
2192
2192
  "destination": "{proxy-base}/api/qa/verdicts",
2193
2193
  "what": "the QA verdict for the attempt, published to the QA portal",
2194
- "when": "publish is not opted out (--no-post-verdict) and the consent seam allows it — Run Telemetry consent on for the endpoint, or a portal-managed campaign",
2194
+ "when": "publish is not opted out (--no-post-verdict) and the consent seam allows it — Run Telemetry consent on for the endpoint, or a portal-managed campaign; never for a local-spec packet",
2195
2195
  "requires_consent": true,
2196
2196
  "observed_in": [
2197
2197
  "persisted_consent"
@@ -2216,7 +2216,7 @@
2216
2216
  ],
2217
2217
  "effect_test": "effects: qa run --no-remit",
2218
2218
  "test_scope": "full",
2219
- "notes": "The base `qa run` row's writes, without any remit: --no-remit stops the progress observation and the Run Record remit. The verdict publish is NOT a remit and is not gated by the flag — it still posts to the QA portal under the consent seam, which --no-post-verdict is the opt-out from."
2219
+ "notes": "The base `qa run` row's writes, without any remit: --no-remit stops the progress observation and the Run Record remit. The verdict publish is NOT a remit and is not gated by the flag — it still posts to the QA portal under the consent seam, which --no-post-verdict is the opt-out from. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict."
2220
2220
  },
2221
2221
  {
2222
2222
  "command": "qa",
@@ -2323,7 +2323,7 @@
2323
2323
  {
2324
2324
  "destination": "{proxy-base}/api/progress",
2325
2325
  "what": "the stage progress observation — the snapshot just captured locally, POSTed under X-Campaign-Key",
2326
- "when": "Run Telemetry consent is on for the endpoint, --no-remit was not given, and the packet's campaign key resolves",
2326
+ "when": "Run Telemetry consent is on for the endpoint, --no-remit was not given, and the packet's campaign key resolves; never for a local-spec packet",
2327
2327
  "requires_consent": true,
2328
2328
  "observed_in": [
2329
2329
  "persisted_consent"
@@ -2332,7 +2332,7 @@
2332
2332
  ],
2333
2333
  "effect_test": "effects: qa run --no-post-verdict",
2334
2334
  "test_scope": "full",
2335
- "notes": "The base `qa run` row's writes, without the verdict publish: --no-post-verdict is the opt-out from posting the verdict to the QA portal. The progress observation is a separate delivery and still leaves under persisted consent; --no-remit is the flag that stops that one."
2335
+ "notes": "The base `qa run` row's writes, without the verdict publish: --no-post-verdict is the opt-out from posting the verdict to the QA portal. The progress observation is a separate delivery and still leaves under persisted consent; --no-remit is the flag that stops that one. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict."
2336
2336
  },
2337
2337
  {
2338
2338
  "command": "qa",
@@ -2423,7 +2423,7 @@
2423
2423
  {
2424
2424
  "destination": "{proxy-base}/api/qa/verdicts",
2425
2425
  "what": "the QA verdict for the attempt, published to the QA portal",
2426
- "when": "publish is not opted out (--no-post-verdict) and the consent seam allows it — Run Telemetry consent on for the endpoint, or a portal-managed campaign",
2426
+ "when": "publish is not opted out (--no-post-verdict) and the consent seam allows it — Run Telemetry consent on for the endpoint, or a portal-managed campaign; never for a local-spec packet",
2427
2427
  "requires_consent": true,
2428
2428
  "observed_in": [
2429
2429
  "persisted_consent"
@@ -2448,7 +2448,7 @@
2448
2448
  {
2449
2449
  "destination": "{proxy-base}/api/progress",
2450
2450
  "what": "the stage progress observation — the snapshot just captured locally, POSTed under X-Campaign-Key",
2451
- "when": "Run Telemetry consent is on for the endpoint, --no-remit was not given, and the packet's campaign key resolves",
2451
+ "when": "Run Telemetry consent is on for the endpoint, --no-remit was not given, and the packet's campaign key resolves; never for a local-spec packet",
2452
2452
  "requires_consent": true,
2453
2453
  "observed_in": [
2454
2454
  "persisted_consent"
@@ -2457,7 +2457,7 @@
2457
2457
  ],
2458
2458
  "effect_test": "effects: qa run --test-order",
2459
2459
  "test_scope": "full",
2460
- "notes": "The most consequential row in the file: --test-order places REAL orders against the store under test. Destructive, open-world, not idempotent. It SENDS on a blocked attempt too: the verdict is published to {proxy-base}/api/qa/verdicts and the QA stage progress observation to {proxy-base}/api/progress, both under persisted consent for the endpoint. Publishing rides the telemetry consent seam rather than a flag of its own, so the four consent-off conditions saw neither request."
2460
+ "notes": "The most consequential row in the file: --test-order places REAL orders against the store under test. Destructive, open-world, not idempotent. It SENDS on a blocked attempt too: the verdict is published to {proxy-base}/api/qa/verdicts and the QA stage progress observation to {proxy-base}/api/progress, both under persisted consent for the endpoint. Publishing rides the telemetry consent seam rather than a flag of its own, so the four consent-off conditions saw neither request. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict."
2461
2461
  },
2462
2462
  {
2463
2463
  "command": "qa",
@@ -2548,7 +2548,7 @@
2548
2548
  {
2549
2549
  "destination": "{proxy-base}/api/qa/verdicts",
2550
2550
  "what": "the QA verdict for the attempt, published to the QA portal",
2551
- "when": "publish is not opted out (--no-post-verdict) and the consent seam allows it — Run Telemetry consent on for the endpoint, or a portal-managed campaign",
2551
+ "when": "publish is not opted out (--no-post-verdict) and the consent seam allows it — Run Telemetry consent on for the endpoint, or a portal-managed campaign; never for a local-spec packet",
2552
2552
  "requires_consent": true,
2553
2553
  "observed_in": [
2554
2554
  "persisted_consent"
@@ -2573,7 +2573,7 @@
2573
2573
  {
2574
2574
  "destination": "{proxy-base}/api/progress",
2575
2575
  "what": "the stage progress observation — the snapshot just captured locally, POSTed under X-Campaign-Key",
2576
- "when": "Run Telemetry consent is on for the endpoint, --no-remit was not given, and the packet's campaign key resolves",
2576
+ "when": "Run Telemetry consent is on for the endpoint, --no-remit was not given, and the packet's campaign key resolves; never for a local-spec packet",
2577
2577
  "requires_consent": true,
2578
2578
  "observed_in": [
2579
2579
  "persisted_consent"
@@ -2582,7 +2582,7 @@
2582
2582
  ],
2583
2583
  "effect_test": "effects: qa run --browser (preflight)",
2584
2584
  "test_scope": "preflight",
2585
- "notes": "PREFLIGHT-PROVED: --browser needs an installed Chromium and a reachable campaign, neither of which exists offline. The case proves the attempt is blocked at the same gate as the node run and writes exactly the blocked-attempt evidence. It SENDS on a blocked attempt too: the verdict is published to {proxy-base}/api/qa/verdicts and the QA stage progress observation to {proxy-base}/api/progress, both under persisted consent for the endpoint. Publishing rides the telemetry consent seam rather than a flag of its own, so the four consent-off conditions saw neither request.",
2585
+ "notes": "PREFLIGHT-PROVED: --browser needs an installed Chromium and a reachable campaign, neither of which exists offline. The case proves the attempt is blocked at the same gate as the node run and writes exactly the blocked-attempt evidence. It SENDS on a blocked attempt too: the verdict is published to {proxy-base}/api/qa/verdicts and the QA stage progress observation to {proxy-base}/api/progress, both under persisted consent for the endpoint. Publishing rides the telemetry consent seam rather than a flag of its own, so the four consent-off conditions saw neither request. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict.",
2586
2586
  "preflight": {
2587
2587
  "may_write": [
2588
2588
  "{target}/qa-output/**",
@@ -2637,7 +2637,7 @@
2637
2637
  ],
2638
2638
  "effect_test": "effects: qa publish",
2639
2639
  "test_scope": "full",
2640
- "notes": "Posts an already-stored verdict without a re-run and without an order. It is NOT gated by Run Telemetry consent — the publish is the operator's explicit act."
2640
+ "notes": "Posts an already-stored verdict without a re-run and without an order. It is NOT gated by Run Telemetry consent — the publish is the operator's explicit act. Local-spec packets are refused before publication because they have no saved Map destination."
2641
2641
  },
2642
2642
  {
2643
2643
  "command": "qa",
@@ -2679,7 +2679,7 @@
2679
2679
  ],
2680
2680
  "effect_test": "effects: qa publish --republish",
2681
2681
  "test_scope": "full",
2682
- "notes": "--republish lifts the already-published refusal: the same POST to the QA portal for a verdict that was already delivered. Not destructive locally — nothing under the target is overwritten — and not gated by Run Telemetry consent."
2682
+ "notes": "--republish lifts the already-published refusal: the same POST to the QA portal for a verdict that was already delivered. Not destructive locally — nothing under the target is overwritten — and not gated by Run Telemetry consent. Local-spec packets are refused before publication because they have no saved Map destination."
2683
2683
  },
2684
2684
  {
2685
2685
  "command": "qa",
@@ -4789,6 +4789,99 @@
4789
4789
  "may_write": [],
4790
4790
  "may_contact": []
4791
4791
  }
4792
+ },
4793
+ {
4794
+ "command": "tooling",
4795
+ "subcommand": "setup",
4796
+ "flags": [],
4797
+ "annotations": {
4798
+ "readOnlyHint": false,
4799
+ "destructiveHint": false,
4800
+ "openWorldHint": true,
4801
+ "idempotentHint": true
4802
+ },
4803
+ "tier": "A",
4804
+ "writes": [
4805
+ {
4806
+ "path": "{target}/.campaign-runtime/agent-context/*",
4807
+ "when": "the four bundled context files are installed after preservation preflight",
4808
+ "observed_in": [],
4809
+ "not_observed_reason": "The offline preflight fails the browser download before shared skills or project files are changed. Existing installer effects are proved in their own rows; focused setup tests cover composition and preservation."
4810
+ },
4811
+ {
4812
+ "path": "{target}/.gitignore",
4813
+ "when": "the managed runtime ignore block is absent",
4814
+ "observed_in": [],
4815
+ "not_observed_reason": "The offline preflight fails the browser download before shared skills or project files are changed. Existing installer effects are proved in their own rows; focused setup tests cover composition and preservation."
4816
+ },
4817
+ {
4818
+ "path": "{target}/CLAUDE.md",
4819
+ "when": "the context import is absent; preserve all existing text and append the import once",
4820
+ "observed_in": [],
4821
+ "not_observed_reason": "The offline preflight fails the browser download before shared skills or project files are changed. Existing installer effects are proved in their own rows; focused setup tests cover composition and preservation."
4822
+ },
4823
+ {
4824
+ "path": "{home}/.claude/skills/**",
4825
+ "when": "the selected Claude skill bundle is installed with the existing installer",
4826
+ "observed_in": [],
4827
+ "not_observed_reason": "The offline preflight fails the browser download before shared skills or project files are changed. Existing installer effects are proved in their own rows; focused setup tests cover composition and preservation."
4828
+ },
4829
+ {
4830
+ "path": "{home}/ms-playwright/.links/*",
4831
+ "when": "always — Playwright records this package's installation in the browser registry as one link entry, named for the installing package's path, before it looks at what is already downloaded",
4832
+ "observed_in": [
4833
+ "no_session",
4834
+ "ambient_session",
4835
+ "stale_session",
4836
+ "lifecycle_log",
4837
+ "persisted_consent"
4838
+ ]
4839
+ },
4840
+ {
4841
+ "path": "{home}/ms-playwright/**",
4842
+ "when": "the download completes — the Chromium build (a ~150 MB archive) is unpacked into a revision directory of the same registry",
4843
+ "observed_in": [],
4844
+ "not_observed_reason": "the effect fixture pins PLAYWRIGHT_DOWNLOAD_HOST at a closed loopback port, so the archive is never fetched and no browser directory is created"
4845
+ }
4846
+ ],
4847
+ "sends": [
4848
+ {
4849
+ "destination": "{playwright-download-host}",
4850
+ "what": "a GET of the Chromium build archive (~150 MB) and of the headless shell beside it",
4851
+ "when": "the registry does not already hold the revision this package's Playwright pins",
4852
+ "requires_consent": false,
4853
+ "observed_in": [],
4854
+ "not_observed_reason": "the effect fixture pins PLAYWRIGHT_DOWNLOAD_HOST at a closed loopback port, so the fetch fails at connect; the NODE_DEBUG=net trace the case asserts on names no host but 127.0.0.1"
4855
+ }
4856
+ ],
4857
+ "effect_test": "effects: tooling setup (preflight)",
4858
+ "test_scope": "preflight",
4859
+ "notes": "PREFLIGHT-PROVED: checks the selected project copy, exact pin, installed page-kit and context preservation before attempting the package-owned browser download. The closed-loopback fixture fails that download before global skills, context or project instructions change. After a successful download, setup composes the existing installers and appends the context import once. Does not read gateway credentials, resolve or close campaign run sessions, append journals, remit telemetry, scaffold pages or create campaign evidence. Focused tests prove rerun preservation, refusal-before-write and browser-failure recovery.",
4860
+ "preflight": {
4861
+ "may_write": [
4862
+ "{home}/ms-playwright/.links/*"
4863
+ ],
4864
+ "may_contact": []
4865
+ }
4866
+ },
4867
+ {
4868
+ "command": "tooling",
4869
+ "subcommand": "setup",
4870
+ "flags": [
4871
+ "--dry-run"
4872
+ ],
4873
+ "annotations": {
4874
+ "readOnlyHint": true,
4875
+ "destructiveHint": false,
4876
+ "openWorldHint": false,
4877
+ "idempotentHint": true
4878
+ },
4879
+ "tier": "none",
4880
+ "writes": [],
4881
+ "sends": [],
4882
+ "effect_test": "effects: tooling setup --dry-run",
4883
+ "test_scope": "full",
4884
+ "notes": "Checks the selected project and previews the existing skill/context installers and instruction import without downloading a browser, touching credentials or recovering/remitting a run session."
4792
4885
  }
4793
4886
  ]
4794
4887
  }
@@ -59,6 +59,15 @@
59
59
  "identity_fields": [
60
60
  {
61
61
  "name": "map_id",
62
+ "local_spec_alternative": {
63
+ "name": "local_spec_id",
64
+ "artifact_paths": {
65
+ "build_packet": "spec.local_spec_id",
66
+ "assembly_report": "identity.local_spec_id",
67
+ "doctor_output": "derived.local_spec_id",
68
+ "qa_verdict": "local_spec_id"
69
+ }
70
+ },
62
71
  "artifact_paths": {
63
72
  "build_packet": "spec.map_id",
64
73
  "assembly_report": "identity.map_id",