@nextcommerce/campaigns-os 1.43.2 → 1.46.0

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 (72) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +648 -5103
  3. package/README.md +32 -11
  4. package/agents/claude/CLAUDE.md +1 -1
  5. package/agents/codex/AGENTS.md +1 -1
  6. package/agents/copilot/copilot-instructions.md +1 -1
  7. package/agents/cursor/campaigns-os.mdc +1 -1
  8. package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +2 -2
  9. package/campaign-spec/dist/rules/analytics-contract-shape.js +2 -2
  10. package/campaign-spec/dist/rules/store-profile-shape.d.ts +5 -1
  11. package/campaign-spec/dist/rules/store-profile-shape.js +8 -10
  12. package/campaign-spec/dist/types.d.ts +2 -2
  13. package/contracts/archive/CHANGELOG.2026-09-30.md +5111 -0
  14. package/contracts/archive/release-ledger.2026-09-30.json +5068 -0
  15. package/contracts/effects.v1.json +1176 -113
  16. package/contracts/orientation-reason-codes.v1.json +7 -0
  17. package/contracts/release-ledger.json +2345 -6087
  18. package/contracts/supported-surface.json +7 -4
  19. package/contracts/template-slot-manifest.shared-content-core.v0.json +403 -0
  20. package/docs/brand-theme-bridge.md +81 -0
  21. package/docs/build-packet.md +158 -21
  22. package/docs/campaigns-os-build-flow.md +3 -3
  23. package/docs/design-source-package.md +73 -0
  24. package/docs/effects.md +50 -8
  25. package/docs/gateway-login.md +3 -0
  26. package/docs/local-setup.md +1 -1
  27. package/docs/orientation-contract-reference.md +42 -2
  28. package/docs/polish-evidence.md +74 -0
  29. package/docs/qa-and-test-orders.md +99 -13
  30. package/docs/release-ledger-authoring-guide.md +64 -4
  31. package/docs/runtime-readiness.md +1 -1
  32. package/docs/sdk-storage-compatibility.md +1 -1
  33. package/docs/skills-revision.md +10 -10
  34. package/docs/supported-surface.md +2 -2
  35. package/docs/versioning.md +4 -1
  36. package/package.json +1 -1
  37. package/schemas/campaigns-os-release-ledger.v1.schema.json +32 -2
  38. package/schemas/campaigns-os-tooling-orientation.v1.schema.json +1 -0
  39. package/skills/campaign-lifecycle-orientation/SKILL.md +16 -5
  40. package/skills/campaign-readback-classification/SKILL.md +3 -3
  41. package/skills/campaign-run-evidence/SKILL.md +7 -6
  42. package/skills/contribution-intake/SKILL.md +3 -3
  43. package/skills/next-campaigns-build/SKILL.md +7 -6
  44. package/skills/next-campaigns-os/SKILL.md +7 -7
  45. package/skills/next-campaigns-os/references/session-intake.md +9 -3
  46. package/skills/next-campaigns-os-setup/SKILL.md +5 -5
  47. package/skills/next-campaigns-polish/SKILL.md +28 -9
  48. package/skills/next-campaigns-qa/SKILL.md +7 -4
  49. package/skills.json +10 -10
  50. package/src/brand-theme.mjs +320 -20
  51. package/src/built-site-scope.mjs +16 -4
  52. package/src/cli.mjs +280 -46
  53. package/src/commercial-parity.mjs +48 -2
  54. package/src/deviation.mjs +13 -1
  55. package/src/diagnostic.mjs +5 -2
  56. package/src/doctor/checks.mjs +320 -81
  57. package/src/doctor/inspect.mjs +55 -13
  58. package/src/doctor/source-provenance.mjs +184 -0
  59. package/src/invocation.mjs +4 -0
  60. package/src/live-campaign-refs.mjs +466 -0
  61. package/src/login.mjs +2 -2
  62. package/src/page-kit-store-profile.mjs +69 -12
  63. package/src/page-kit-sync.mjs +31 -12
  64. package/src/progress-node.mjs +3 -1
  65. package/src/qa-browser.mjs +538 -28
  66. package/src/qa-commercial-parity.mjs +48 -5
  67. package/src/qa-node.mjs +122 -7
  68. package/src/qa-test-order-topology.mjs +148 -0
  69. package/src/sdk-markup.mjs +72 -8
  70. package/src/source-html-intake.mjs +116 -0
  71. package/src/stage-record.mjs +551 -0
  72. package/src/upsell-selector-scope.mjs +112 -2
@@ -49,6 +49,7 @@
49
49
  "--force",
50
50
  "--from-store",
51
51
  "--list",
52
+ "--no-live-refs",
52
53
  "--no-post-verdict",
53
54
  "--no-probe",
54
55
  "--no-remit",
@@ -137,17 +138,31 @@
137
138
  "subcommand": null,
138
139
  "flags": [],
139
140
  "annotations": {
140
- "readOnlyHint": true,
141
+ "readOnlyHint": false,
141
142
  "destructiveHint": false,
142
- "openWorldHint": false,
143
+ "openWorldHint": true,
143
144
  "idempotentHint": true
144
145
  },
145
- "tier": "none",
146
+ "tier": "A",
146
147
  "writes": [],
147
- "sends": [],
148
+ "sends": [
149
+ {
150
+ "destination": "{proxy-base}/api/campaign",
151
+ "what": "one GET of the live campaign (its packages and shipping methods) under X-Campaign-Key, the public Campaigns API key; no request body and no Admin or store credential, and the read writes nothing",
152
+ "when": "packet mode, the packet's built _site/<route>/ exists, and a public Campaigns API key resolves from the packet, its local CampaignSpec or the declared campaign-key env var",
153
+ "requires_consent": false,
154
+ "observed_in": [
155
+ "no_session",
156
+ "ambient_session",
157
+ "stale_session",
158
+ "lifecycle_log",
159
+ "persisted_consent"
160
+ ]
161
+ }
162
+ ],
148
163
  "effect_test": "effects: doctor",
149
164
  "test_scope": "full",
150
- "notes": "Inspection is the default. With --packet and without --write, doctor writes no sidecar AND appends no lifecycle entry, so an inspection of a delivered campaign leaves the target byte-identical."
165
+ "notes": "Inspection is the default. With --packet and without --write, doctor writes no sidecar AND appends no lifecycle entry, so an inspection of a delivered campaign leaves the target byte-identical; it is not readOnlyHint because of the one read it may send. When the packet's built _site/<route>/ exists and a public Campaigns API key resolves, doctor makes one read-only GET of {proxy-base}/api/campaign under X-Campaign-Key to check each built page's shipping and package refs against the live campaign (#533); --proxy-base names the proxy, else the canonical one. No key, no built page, or a failed read records derived.live_campaign_refs as not_run with its reason and sends nothing more. --no-live-refs skips that read (its own row)."
151
166
  },
152
167
  {
153
168
  "command": "doctor",
@@ -155,6 +170,39 @@
155
170
  "flags": [
156
171
  "--no-write"
157
172
  ],
173
+ "annotations": {
174
+ "readOnlyHint": false,
175
+ "destructiveHint": false,
176
+ "openWorldHint": true,
177
+ "idempotentHint": true
178
+ },
179
+ "tier": "A",
180
+ "writes": [],
181
+ "sends": [
182
+ {
183
+ "destination": "{proxy-base}/api/campaign",
184
+ "what": "one GET of the live campaign (its packages and shipping methods) under X-Campaign-Key, the public Campaigns API key; no request body and no Admin or store credential, and the read writes nothing",
185
+ "when": "packet mode, the packet's built _site/<route>/ exists, and a public Campaigns API key resolves from the packet, its local CampaignSpec or the declared campaign-key env var",
186
+ "requires_consent": false,
187
+ "observed_in": [
188
+ "no_session",
189
+ "ambient_session",
190
+ "stale_session",
191
+ "lifecycle_log",
192
+ "persisted_consent"
193
+ ]
194
+ }
195
+ ],
196
+ "effect_test": "effects: doctor --no-write",
197
+ "test_scope": "full",
198
+ "notes": "--no-write wins over --write: no doctor sidecar, no assembly-report update, no journal entry. When the packet's built _site/<route>/ exists and a public Campaigns API key resolves, doctor makes one read-only GET of {proxy-base}/api/campaign under X-Campaign-Key to check each built page's shipping and package refs against the live campaign (#533); --proxy-base names the proxy, else the canonical one. No key, no built page, or a failed read records derived.live_campaign_refs as not_run with its reason and sends nothing more. --no-live-refs skips that read (its own row)."
199
+ },
200
+ {
201
+ "command": "doctor",
202
+ "subcommand": null,
203
+ "flags": [
204
+ "--no-live-refs"
205
+ ],
158
206
  "annotations": {
159
207
  "readOnlyHint": true,
160
208
  "destructiveHint": false,
@@ -164,9 +212,9 @@
164
212
  "tier": "none",
165
213
  "writes": [],
166
214
  "sends": [],
167
- "effect_test": "effects: doctor --no-write",
215
+ "effect_test": "effects: doctor --no-live-refs",
168
216
  "test_scope": "full",
169
- "notes": "--no-write wins over --write: no doctor sidecar, no assembly-report update, no journal entry."
217
+ "notes": "Inspection with the live campaign read switched off: with --packet and without --write, doctor writes no sidecar, appends no lifecycle entry and sends nothing, even when the packet's built _site/<route>/ exists and a public Campaigns API key resolves. derived.live_campaign_refs records the check as not_run with reason disabled, never as a pass; the CampaignSpec ref check still runs."
170
218
  },
171
219
  {
172
220
  "command": "sdk",
@@ -880,6 +928,71 @@
880
928
  "flags": [
881
929
  "--write"
882
930
  ],
931
+ "annotations": {
932
+ "readOnlyHint": false,
933
+ "destructiveHint": false,
934
+ "openWorldHint": true,
935
+ "idempotentHint": true
936
+ },
937
+ "tier": "A",
938
+ "writes": [
939
+ {
940
+ "path": "{target}/.campaign-runtime/doctor-output.json",
941
+ "when": "always under --write — the doctor sidecar is (re)written",
942
+ "observed_in": [
943
+ "no_session",
944
+ "ambient_session",
945
+ "stale_session",
946
+ "lifecycle_log",
947
+ "persisted_consent"
948
+ ]
949
+ },
950
+ {
951
+ "path": "{target}/.campaign-runtime/assembly-report.json",
952
+ "when": "the run's doctor evidence on the assembly report changes",
953
+ "observed_in": [
954
+ "no_session",
955
+ "ambient_session",
956
+ "stale_session",
957
+ "lifecycle_log",
958
+ "persisted_consent"
959
+ ]
960
+ },
961
+ {
962
+ "path": "{lifecycle-journal}",
963
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
964
+ "observed_in": [
965
+ "ambient_session",
966
+ "lifecycle_log"
967
+ ]
968
+ }
969
+ ],
970
+ "sends": [
971
+ {
972
+ "destination": "{proxy-base}/api/campaign",
973
+ "what": "one GET of the live campaign (its packages and shipping methods) under X-Campaign-Key, the public Campaigns API key; no request body and no Admin or store credential, and the read writes nothing",
974
+ "when": "packet mode, the packet's built _site/<route>/ exists, and a public Campaigns API key resolves from the packet, its local CampaignSpec or the declared campaign-key env var",
975
+ "requires_consent": false,
976
+ "observed_in": [
977
+ "no_session",
978
+ "ambient_session",
979
+ "stale_session",
980
+ "lifecycle_log",
981
+ "persisted_consent"
982
+ ]
983
+ }
984
+ ],
985
+ "effect_test": "effects: doctor --write",
986
+ "test_scope": "full",
987
+ "notes": "--write turns the inspection into a recording run: the doctor sidecar and the report's doctor evidence are rewritten, and the lifecycle exemption for the read-only form no longer applies. When the packet's built _site/<route>/ exists and a public Campaigns API key resolves, doctor makes one read-only GET of {proxy-base}/api/campaign under X-Campaign-Key to check each built page's shipping and package refs against the live campaign (#533); --proxy-base names the proxy, else the canonical one. No key, no built page, or a failed read records derived.live_campaign_refs as not_run with its reason and sends nothing more. --no-live-refs skips that read (its own row)."
988
+ },
989
+ {
990
+ "command": "doctor",
991
+ "subcommand": null,
992
+ "flags": [
993
+ "--write",
994
+ "--no-live-refs"
995
+ ],
883
996
  "annotations": {
884
997
  "readOnlyHint": false,
885
998
  "destructiveHint": false,
@@ -920,9 +1033,9 @@
920
1033
  }
921
1034
  ],
922
1035
  "sends": [],
923
- "effect_test": "effects: doctor --write",
1036
+ "effect_test": "effects: doctor --write --no-live-refs",
924
1037
  "test_scope": "full",
925
- "notes": "--write turns the inspection into a recording run: the doctor sidecar and the report's doctor evidence are rewritten, and the lifecycle exemption for the read-only form no longer applies."
1038
+ "notes": "The doctor --write row's writes without the live campaign read: no request to {proxy-base}/api/campaign even when the built _site/<route>/ exists and a public Campaigns API key resolves, and derived.live_campaign_refs records not_run with reason disabled, never a pass."
926
1039
  },
927
1040
  {
928
1041
  "command": "theme",
@@ -2096,11 +2209,19 @@
2096
2209
  "observed_in": [
2097
2210
  "persisted_consent"
2098
2211
  ]
2212
+ },
2213
+ {
2214
+ "destination": "{proxy-base}/api/campaign",
2215
+ "what": "one GET of the live campaign (its packages and shipping methods) under X-Campaign-Key, the public Campaigns API key; no request body and no Admin or store credential, and the read writes nothing",
2216
+ "when": "at least one served page was read and a public Campaigns API key resolves from the packet, the CampaignSpec or the declared campaign-key env var",
2217
+ "requires_consent": false,
2218
+ "observed_in": [],
2219
+ "not_observed_reason": "the offline fixture is blocked at the theme/proof gate before a served page is read, so there is no page to compare and the live campaign is not read"
2099
2220
  }
2100
2221
  ],
2101
2222
  "effect_test": "effects: qa run",
2102
2223
  "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. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict."
2224
+ "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. When a served page was read and a public Campaigns API key resolves, it also makes one read-only GET of {proxy-base}/api/campaign under X-Campaign-Key to check the served pages' shipping and package refs against the live campaign (#533); without a key or a page, or on a failed read, the verdict records the check as not_run with its reason. --no-live-refs skips that read (its own row)."
2104
2225
  },
2105
2226
  {
2106
2227
  "command": "qa",
@@ -2212,11 +2333,19 @@
2212
2333
  "requires_consent": true,
2213
2334
  "observed_in": [],
2214
2335
  "not_observed_reason": "a blocked attempt keeps the session open and assembles no Run Record, so nothing is remitted"
2336
+ },
2337
+ {
2338
+ "destination": "{proxy-base}/api/campaign",
2339
+ "what": "one GET of the live campaign (its packages and shipping methods) under X-Campaign-Key, the public Campaigns API key; no request body and no Admin or store credential, and the read writes nothing",
2340
+ "when": "at least one served page was read and a public Campaigns API key resolves from the packet, the CampaignSpec or the declared campaign-key env var",
2341
+ "requires_consent": false,
2342
+ "observed_in": [],
2343
+ "not_observed_reason": "the offline fixture is blocked at the theme/proof gate before a served page is read, so there is no page to compare and the live campaign is not read"
2215
2344
  }
2216
2345
  ],
2217
2346
  "effect_test": "effects: qa run --no-remit",
2218
2347
  "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. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict."
2348
+ "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. When a served page was read and a public Campaigns API key resolves, it also makes one read-only GET of {proxy-base}/api/campaign under X-Campaign-Key to check the served pages' shipping and package refs against the live campaign (#533); without a key or a page, or on a failed read, the verdict records the check as not_run with its reason. --no-live-refs skips that read (its own row)."
2220
2349
  },
2221
2350
  {
2222
2351
  "command": "qa",
@@ -2328,11 +2457,19 @@
2328
2457
  "observed_in": [
2329
2458
  "persisted_consent"
2330
2459
  ]
2460
+ },
2461
+ {
2462
+ "destination": "{proxy-base}/api/campaign",
2463
+ "what": "one GET of the live campaign (its packages and shipping methods) under X-Campaign-Key, the public Campaigns API key; no request body and no Admin or store credential, and the read writes nothing",
2464
+ "when": "at least one served page was read and a public Campaigns API key resolves from the packet, the CampaignSpec or the declared campaign-key env var",
2465
+ "requires_consent": false,
2466
+ "observed_in": [],
2467
+ "not_observed_reason": "the offline fixture is blocked at the theme/proof gate before a served page is read, so there is no page to compare and the live campaign is not read"
2331
2468
  }
2332
2469
  ],
2333
2470
  "effect_test": "effects: qa run --no-post-verdict",
2334
2471
  "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. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict."
2472
+ "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. When a served page was read and a public Campaigns API key resolves, it also makes one read-only GET of {proxy-base}/api/campaign under X-Campaign-Key to check the served pages' shipping and package refs against the live campaign (#533); without a key or a page, or on a failed read, the verdict records the check as not_run with its reason. --no-live-refs skips that read (its own row)."
2336
2473
  },
2337
2474
  {
2338
2475
  "command": "qa",
@@ -2453,11 +2590,19 @@
2453
2590
  "observed_in": [
2454
2591
  "persisted_consent"
2455
2592
  ]
2593
+ },
2594
+ {
2595
+ "destination": "{proxy-base}/api/campaign",
2596
+ "what": "one GET of the live campaign (its packages and shipping methods) under X-Campaign-Key, the public Campaigns API key; no request body and no Admin or store credential, and the read writes nothing",
2597
+ "when": "at least one served page was read and a public Campaigns API key resolves from the packet, the CampaignSpec or the declared campaign-key env var",
2598
+ "requires_consent": false,
2599
+ "observed_in": [],
2600
+ "not_observed_reason": "the offline fixture is blocked at the theme/proof gate before a served page is read, so there is no page to compare and the live campaign is not read"
2456
2601
  }
2457
2602
  ],
2458
2603
  "effect_test": "effects: qa run --test-order",
2459
2604
  "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. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict."
2605
+ "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. When a served page was read and a public Campaigns API key resolves, it also makes one read-only GET of {proxy-base}/api/campaign under X-Campaign-Key to check the served pages' shipping and package refs against the live campaign (#533); without a key or a page, or on a failed read, the verdict records the check as not_run with its reason. --no-live-refs skips that read (its own row)."
2461
2606
  },
2462
2607
  {
2463
2608
  "command": "qa",
@@ -2578,11 +2723,19 @@
2578
2723
  "observed_in": [
2579
2724
  "persisted_consent"
2580
2725
  ]
2726
+ },
2727
+ {
2728
+ "destination": "{proxy-base}/api/campaign",
2729
+ "what": "one GET of the live campaign (its packages and shipping methods) under X-Campaign-Key, the public Campaigns API key; no request body and no Admin or store credential, and the read writes nothing",
2730
+ "when": "at least one served page was read and a public Campaigns API key resolves from the packet, the CampaignSpec or the declared campaign-key env var",
2731
+ "requires_consent": false,
2732
+ "observed_in": [],
2733
+ "not_observed_reason": "the offline fixture is blocked at the theme/proof gate before a served page is read, so there is no page to compare and the live campaign is not read"
2581
2734
  }
2582
2735
  ],
2583
2736
  "effect_test": "effects: qa run --browser (preflight)",
2584
2737
  "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. Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict.",
2738
+ "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. When a served page was read and a public Campaigns API key resolves, it also makes one read-only GET of {proxy-base}/api/campaign under X-Campaign-Key to check the served pages' shipping and package refs against the live campaign (#533); without a key or a page, or on a failed read, the verdict records the check as not_run with its reason. --no-live-refs skips that read (its own row).",
2586
2739
  "preflight": {
2587
2740
  "may_write": [
2588
2741
  "{target}/qa-output/**",
@@ -2601,31 +2754,32 @@
2601
2754
  },
2602
2755
  {
2603
2756
  "command": "qa",
2604
- "subcommand": "publish",
2605
- "flags": [],
2757
+ "subcommand": "run",
2758
+ "flags": [
2759
+ "--no-live-refs"
2760
+ ],
2606
2761
  "annotations": {
2607
2762
  "readOnlyHint": false,
2608
- "destructiveHint": false,
2763
+ "destructiveHint": true,
2609
2764
  "openWorldHint": true,
2610
- "idempotentHint": true
2765
+ "idempotentHint": false
2611
2766
  },
2612
- "tier": "A",
2767
+ "tier": "C",
2613
2768
  "writes": [
2614
2769
  {
2615
- "path": "{lifecycle-journal}",
2616
- "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
2770
+ "path": "{target}/qa-output/**",
2771
+ "when": "always — the full verdict for the attempt is written to the qa-output tree",
2617
2772
  "observed_in": [
2773
+ "no_session",
2618
2774
  "ambient_session",
2619
- "lifecycle_log"
2775
+ "stale_session",
2776
+ "lifecycle_log",
2777
+ "persisted_consent"
2620
2778
  ]
2621
- }
2622
- ],
2623
- "sends": [
2779
+ },
2624
2780
  {
2625
- "destination": "{proxy-base}/api/qa/verdicts",
2626
- "what": "the already-stored verdict",
2627
- "when": "the verdict's spec_hash is current and it is not already published",
2628
- "requires_consent": false,
2781
+ "path": "{cwd}/.campaign-runtime/qa-verdict.json",
2782
+ "when": "always — the committed verdict sidecar is projected from the attempt",
2629
2783
  "observed_in": [
2630
2784
  "no_session",
2631
2785
  "ambient_session",
@@ -2633,41 +2787,21 @@
2633
2787
  "lifecycle_log",
2634
2788
  "persisted_consent"
2635
2789
  ]
2636
- }
2637
- ],
2638
- "effect_test": "effects: qa publish",
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. Local-spec packets are refused before publication because they have no saved Map destination."
2641
- },
2642
- {
2643
- "command": "qa",
2644
- "subcommand": "publish",
2645
- "flags": [
2646
- "--republish"
2647
- ],
2648
- "annotations": {
2649
- "readOnlyHint": false,
2650
- "destructiveHint": false,
2651
- "openWorldHint": true,
2652
- "idempotentHint": true
2653
- },
2654
- "tier": "A",
2655
- "writes": [
2790
+ },
2656
2791
  {
2657
- "path": "{lifecycle-journal}",
2658
- "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
2792
+ "path": "{target}/.campaign-runtime/assembly-report.json",
2793
+ "when": "always — the QA stage outcome is recorded on the report",
2659
2794
  "observed_in": [
2795
+ "no_session",
2660
2796
  "ambient_session",
2661
- "lifecycle_log"
2797
+ "stale_session",
2798
+ "lifecycle_log",
2799
+ "persisted_consent"
2662
2800
  ]
2663
- }
2664
- ],
2665
- "sends": [
2801
+ },
2666
2802
  {
2667
- "destination": "{proxy-base}/api/qa/verdicts",
2668
- "what": "the already-stored verdict",
2669
- "when": "the verdict's spec_hash is current and it is not already published",
2670
- "requires_consent": false,
2803
+ "path": "{target}/.campaign-runtime/progress/**",
2804
+ "when": "always — the QA stage progress snapshot is captured locally",
2671
2805
  "observed_in": [
2672
2806
  "no_session",
2673
2807
  "ambient_session",
@@ -2675,49 +2809,694 @@
2675
2809
  "lifecycle_log",
2676
2810
  "persisted_consent"
2677
2811
  ]
2678
- }
2679
- ],
2680
- "effect_test": "effects: qa publish --republish",
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. Local-spec packets are refused before publication because they have no saved Map destination."
2683
- },
2684
- {
2685
- "command": "qa",
2686
- "subcommand": "parity",
2687
- "flags": [],
2688
- "annotations": {
2689
- "readOnlyHint": false,
2690
- "destructiveHint": false,
2691
- "openWorldHint": true,
2692
- "idempotentHint": false
2693
- },
2694
- "tier": "A",
2695
- "writes": [
2812
+ },
2696
2813
  {
2697
- "path": "{cwd}/qa-output/**",
2698
- "when": "the capture completes — the evidence bundle (<run-id>.parity-bundle.json: the assessed order, the candidate capture and any baseline capture) and the full verdict, both under the fixture's campaign slug. `qa parity` takes no packet, so the directory is qa-output under the working directory unless --output-dir names another",
2699
- "observed_in": [],
2700
- "not_observed_reason": "the parity capture drives the candidate funnel through Playwright, and the refusal (\"Playwright Chromium is not installed\") lands before a page is opened, so the run never reaches the bundle write or the verdict"
2814
+ "path": "{target}/.campaign-runtime/doctor-output.json",
2815
+ "when": "the QA outcome invalidates the retained doctor sidecar",
2816
+ "observed_in": [
2817
+ "no_session",
2818
+ "ambient_session",
2819
+ "stale_session",
2820
+ "lifecycle_log",
2821
+ "persisted_consent"
2822
+ ]
2823
+ },
2824
+ {
2825
+ "path": "{target}/.campaign-runtime/run-session.json",
2826
+ "when": "an ambient run session is resolved; a blocked attempt keeps it open, a ready verdict clears it",
2827
+ "observed_in": [
2828
+ "ambient_session"
2829
+ ]
2701
2830
  },
2702
2831
  {
2703
2832
  "path": "{lifecycle-journal}",
2704
- "when": "a lifecycle journal is selected for the invocation — --lifecycle-journal or CAMPAIGNS_OS_LIFECYCLE_LOG. This invocation takes no --packet, so an ambient run session opened at a target repo is not its session and its journal is not selected",
2833
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
2705
2834
  "observed_in": [
2835
+ "ambient_session",
2706
2836
  "lifecycle_log"
2707
2837
  ]
2708
2838
  }
2709
2839
  ],
2710
2840
  "sends": [
2841
+ {
2842
+ "destination": "{proxy-base}/api/qa/verdicts",
2843
+ "what": "the QA verdict for the attempt, published to the QA portal",
2844
+ "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",
2845
+ "requires_consent": true,
2846
+ "observed_in": [
2847
+ "persisted_consent"
2848
+ ]
2849
+ },
2711
2850
  {
2712
2851
  "destination": "{base-url}",
2713
- "what": "browser navigation and typed-card order traversal of the scenario's checkout and upsell routes against the candidate campaign — real orders through the deployed checkout",
2714
- "when": "a Chromium is installed and the scenario's routes resolve; the base URL is --base-url, else the fixture's candidate_base_url",
2852
+ "what": "HTTP requests against the campaign under test",
2853
+ "when": "always",
2715
2854
  "requires_consent": false,
2716
2855
  "observed_in": [],
2717
- "not_observed_reason": "no browser is installed in the fixture's pinned Playwright registry, so the capture is refused before the first navigation and the loopback stand-in for --base-url is never contacted"
2856
+ "not_observed_reason": "the offline fixture is blocked at the theme/proof gate before the campaign is reached, so no request leaves for the base URL"
2718
2857
  },
2719
2858
  {
2720
- "destination": "{proxy-base}/api/qa/verdicts",
2859
+ "destination": "{proxy-base}/api/runs",
2860
+ "what": "the Run Record / progress remit envelope for this run",
2861
+ "when": "Run Telemetry consent is on for the endpoint and --no-remit was not given",
2862
+ "requires_consent": true,
2863
+ "observed_in": [],
2864
+ "not_observed_reason": "a blocked attempt keeps the session open and assembles no Run Record, so nothing is remitted"
2865
+ },
2866
+ {
2867
+ "destination": "{proxy-base}/api/progress",
2868
+ "what": "the stage progress observation — the snapshot just captured locally, POSTed under X-Campaign-Key",
2869
+ "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",
2870
+ "requires_consent": true,
2871
+ "observed_in": [
2872
+ "persisted_consent"
2873
+ ]
2874
+ }
2875
+ ],
2876
+ "effect_test": "effects: qa run --no-live-refs",
2877
+ "test_scope": "full",
2878
+ "notes": "The base `qa run` row's writes and sends, without the live campaign read: --no-live-refs makes no request to {proxy-base}/api/campaign even when a served page was read and a public Campaigns API key resolves, and the verdict records the check as a skipped live-campaign-refs assertion with reason disabled, never a pass. The verdict publish, the progress observation and the Run Record remit are unchanged and keep their own opt-outs (--no-post-verdict, --no-remit). Local-spec packets retain verdicts in the repository and never send a QA verdict or progress to the Map portal, even with --post-verdict."
2879
+ },
2880
+ {
2881
+ "command": "qa",
2882
+ "subcommand": "run",
2883
+ "flags": [
2884
+ "--no-remit",
2885
+ "--no-live-refs"
2886
+ ],
2887
+ "annotations": {
2888
+ "readOnlyHint": false,
2889
+ "destructiveHint": true,
2890
+ "openWorldHint": true,
2891
+ "idempotentHint": false
2892
+ },
2893
+ "tier": "C",
2894
+ "writes": [
2895
+ {
2896
+ "path": "{target}/qa-output/**",
2897
+ "when": "always — the full verdict for the attempt is written to the qa-output tree",
2898
+ "observed_in": [
2899
+ "no_session",
2900
+ "ambient_session",
2901
+ "stale_session",
2902
+ "lifecycle_log",
2903
+ "persisted_consent"
2904
+ ]
2905
+ },
2906
+ {
2907
+ "path": "{cwd}/.campaign-runtime/qa-verdict.json",
2908
+ "when": "always — the committed verdict sidecar is projected from the attempt",
2909
+ "observed_in": [
2910
+ "no_session",
2911
+ "ambient_session",
2912
+ "stale_session",
2913
+ "lifecycle_log",
2914
+ "persisted_consent"
2915
+ ]
2916
+ },
2917
+ {
2918
+ "path": "{target}/.campaign-runtime/assembly-report.json",
2919
+ "when": "always — the QA stage outcome is recorded on the report",
2920
+ "observed_in": [
2921
+ "no_session",
2922
+ "ambient_session",
2923
+ "stale_session",
2924
+ "lifecycle_log",
2925
+ "persisted_consent"
2926
+ ]
2927
+ },
2928
+ {
2929
+ "path": "{target}/.campaign-runtime/progress/**",
2930
+ "when": "always — the QA stage progress snapshot is captured locally",
2931
+ "observed_in": [
2932
+ "no_session",
2933
+ "ambient_session",
2934
+ "stale_session",
2935
+ "lifecycle_log",
2936
+ "persisted_consent"
2937
+ ]
2938
+ },
2939
+ {
2940
+ "path": "{target}/.campaign-runtime/doctor-output.json",
2941
+ "when": "the QA outcome invalidates the retained doctor sidecar",
2942
+ "observed_in": [
2943
+ "no_session",
2944
+ "ambient_session",
2945
+ "stale_session",
2946
+ "lifecycle_log",
2947
+ "persisted_consent"
2948
+ ]
2949
+ },
2950
+ {
2951
+ "path": "{target}/.campaign-runtime/run-session.json",
2952
+ "when": "an ambient run session is resolved; a blocked attempt keeps it open, a ready verdict clears it",
2953
+ "observed_in": [
2954
+ "ambient_session"
2955
+ ]
2956
+ },
2957
+ {
2958
+ "path": "{lifecycle-journal}",
2959
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
2960
+ "observed_in": [
2961
+ "ambient_session",
2962
+ "lifecycle_log"
2963
+ ]
2964
+ }
2965
+ ],
2966
+ "sends": [
2967
+ {
2968
+ "destination": "{proxy-base}/api/qa/verdicts",
2969
+ "what": "the QA verdict for the attempt, published to the QA portal",
2970
+ "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",
2971
+ "requires_consent": true,
2972
+ "observed_in": [
2973
+ "persisted_consent"
2974
+ ]
2975
+ },
2976
+ {
2977
+ "destination": "{base-url}",
2978
+ "what": "HTTP requests against the campaign under test",
2979
+ "when": "always",
2980
+ "requires_consent": false,
2981
+ "observed_in": [],
2982
+ "not_observed_reason": "the offline fixture is blocked at the theme/proof gate before the campaign is reached, so no request leaves for the base URL"
2983
+ },
2984
+ {
2985
+ "destination": "{proxy-base}/api/runs",
2986
+ "what": "the Run Record / progress remit envelope for this run",
2987
+ "when": "Run Telemetry consent is on for the endpoint and --no-remit was not given",
2988
+ "requires_consent": true,
2989
+ "observed_in": [],
2990
+ "not_observed_reason": "a blocked attempt keeps the session open and assembles no Run Record, so nothing is remitted"
2991
+ }
2992
+ ],
2993
+ "effect_test": "effects: qa run --no-remit --no-live-refs",
2994
+ "test_scope": "full",
2995
+ "notes": "The `qa run --no-remit` row's writes and sends, without the {proxy-base}/api/campaign read: the verdict records the live-campaign-refs check skipped with reason disabled, never a pass. The other sends keep their own conditions and opt-outs."
2996
+ },
2997
+ {
2998
+ "command": "qa",
2999
+ "subcommand": "run",
3000
+ "flags": [
3001
+ "--no-post-verdict",
3002
+ "--no-live-refs"
3003
+ ],
3004
+ "annotations": {
3005
+ "readOnlyHint": false,
3006
+ "destructiveHint": true,
3007
+ "openWorldHint": true,
3008
+ "idempotentHint": false
3009
+ },
3010
+ "tier": "C",
3011
+ "writes": [
3012
+ {
3013
+ "path": "{target}/qa-output/**",
3014
+ "when": "always — the full verdict for the attempt is written to the qa-output tree",
3015
+ "observed_in": [
3016
+ "no_session",
3017
+ "ambient_session",
3018
+ "stale_session",
3019
+ "lifecycle_log",
3020
+ "persisted_consent"
3021
+ ]
3022
+ },
3023
+ {
3024
+ "path": "{cwd}/.campaign-runtime/qa-verdict.json",
3025
+ "when": "always — the committed verdict sidecar is projected from the attempt",
3026
+ "observed_in": [
3027
+ "no_session",
3028
+ "ambient_session",
3029
+ "stale_session",
3030
+ "lifecycle_log",
3031
+ "persisted_consent"
3032
+ ]
3033
+ },
3034
+ {
3035
+ "path": "{target}/.campaign-runtime/assembly-report.json",
3036
+ "when": "always — the QA stage outcome is recorded on the report",
3037
+ "observed_in": [
3038
+ "no_session",
3039
+ "ambient_session",
3040
+ "stale_session",
3041
+ "lifecycle_log",
3042
+ "persisted_consent"
3043
+ ]
3044
+ },
3045
+ {
3046
+ "path": "{target}/.campaign-runtime/progress/**",
3047
+ "when": "always — the QA stage progress snapshot is captured locally",
3048
+ "observed_in": [
3049
+ "no_session",
3050
+ "ambient_session",
3051
+ "stale_session",
3052
+ "lifecycle_log",
3053
+ "persisted_consent"
3054
+ ]
3055
+ },
3056
+ {
3057
+ "path": "{target}/.campaign-runtime/doctor-output.json",
3058
+ "when": "the QA outcome invalidates the retained doctor sidecar",
3059
+ "observed_in": [
3060
+ "no_session",
3061
+ "ambient_session",
3062
+ "stale_session",
3063
+ "lifecycle_log",
3064
+ "persisted_consent"
3065
+ ]
3066
+ },
3067
+ {
3068
+ "path": "{target}/.campaign-runtime/run-session.json",
3069
+ "when": "an ambient run session is resolved; a blocked attempt keeps it open, a ready verdict clears it",
3070
+ "observed_in": [
3071
+ "ambient_session"
3072
+ ]
3073
+ },
3074
+ {
3075
+ "path": "{lifecycle-journal}",
3076
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
3077
+ "observed_in": [
3078
+ "ambient_session",
3079
+ "lifecycle_log"
3080
+ ]
3081
+ }
3082
+ ],
3083
+ "sends": [
3084
+ {
3085
+ "destination": "{base-url}",
3086
+ "what": "HTTP requests against the campaign under test",
3087
+ "when": "always",
3088
+ "requires_consent": false,
3089
+ "observed_in": [],
3090
+ "not_observed_reason": "the offline fixture is blocked at the theme/proof gate before the campaign is reached, so no request leaves for the base URL"
3091
+ },
3092
+ {
3093
+ "destination": "{proxy-base}/api/runs",
3094
+ "what": "the Run Record / progress remit envelope for this run",
3095
+ "when": "Run Telemetry consent is on for the endpoint and --no-remit was not given",
3096
+ "requires_consent": true,
3097
+ "observed_in": [],
3098
+ "not_observed_reason": "a blocked attempt keeps the session open and assembles no Run Record, so nothing is remitted"
3099
+ },
3100
+ {
3101
+ "destination": "{proxy-base}/api/progress",
3102
+ "what": "the stage progress observation — the snapshot just captured locally, POSTed under X-Campaign-Key",
3103
+ "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",
3104
+ "requires_consent": true,
3105
+ "observed_in": [
3106
+ "persisted_consent"
3107
+ ]
3108
+ }
3109
+ ],
3110
+ "effect_test": "effects: qa run --no-post-verdict --no-live-refs",
3111
+ "test_scope": "full",
3112
+ "notes": "The `qa run --no-post-verdict` row's writes and sends, without the {proxy-base}/api/campaign read: the verdict records the live-campaign-refs check skipped with reason disabled, never a pass. The other sends keep their own conditions and opt-outs."
3113
+ },
3114
+ {
3115
+ "command": "qa",
3116
+ "subcommand": "run",
3117
+ "flags": [
3118
+ "--test-order",
3119
+ "--no-live-refs"
3120
+ ],
3121
+ "annotations": {
3122
+ "readOnlyHint": false,
3123
+ "destructiveHint": true,
3124
+ "openWorldHint": true,
3125
+ "idempotentHint": false
3126
+ },
3127
+ "tier": "C",
3128
+ "writes": [
3129
+ {
3130
+ "path": "{target}/qa-output/**",
3131
+ "when": "always — the full verdict for the attempt is written to the qa-output tree",
3132
+ "observed_in": [
3133
+ "no_session",
3134
+ "ambient_session",
3135
+ "stale_session",
3136
+ "lifecycle_log",
3137
+ "persisted_consent"
3138
+ ]
3139
+ },
3140
+ {
3141
+ "path": "{cwd}/.campaign-runtime/qa-verdict.json",
3142
+ "when": "always — the committed verdict sidecar is projected from the attempt",
3143
+ "observed_in": [
3144
+ "no_session",
3145
+ "ambient_session",
3146
+ "stale_session",
3147
+ "lifecycle_log",
3148
+ "persisted_consent"
3149
+ ]
3150
+ },
3151
+ {
3152
+ "path": "{target}/.campaign-runtime/assembly-report.json",
3153
+ "when": "always — the QA stage outcome is recorded on the report",
3154
+ "observed_in": [
3155
+ "no_session",
3156
+ "ambient_session",
3157
+ "stale_session",
3158
+ "lifecycle_log",
3159
+ "persisted_consent"
3160
+ ]
3161
+ },
3162
+ {
3163
+ "path": "{target}/.campaign-runtime/progress/**",
3164
+ "when": "always — the QA stage progress snapshot is captured locally",
3165
+ "observed_in": [
3166
+ "no_session",
3167
+ "ambient_session",
3168
+ "stale_session",
3169
+ "lifecycle_log",
3170
+ "persisted_consent"
3171
+ ]
3172
+ },
3173
+ {
3174
+ "path": "{target}/.campaign-runtime/doctor-output.json",
3175
+ "when": "the QA outcome invalidates the retained doctor sidecar",
3176
+ "observed_in": [
3177
+ "no_session",
3178
+ "ambient_session",
3179
+ "stale_session",
3180
+ "lifecycle_log",
3181
+ "persisted_consent"
3182
+ ]
3183
+ },
3184
+ {
3185
+ "path": "{target}/.campaign-runtime/run-session.json",
3186
+ "when": "an ambient run session is resolved; a blocked attempt keeps it open, a ready verdict clears it",
3187
+ "observed_in": [
3188
+ "ambient_session"
3189
+ ]
3190
+ },
3191
+ {
3192
+ "path": "{lifecycle-journal}",
3193
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
3194
+ "observed_in": [
3195
+ "ambient_session",
3196
+ "lifecycle_log"
3197
+ ]
3198
+ }
3199
+ ],
3200
+ "sends": [
3201
+ {
3202
+ "destination": "{proxy-base}/api/qa/verdicts",
3203
+ "what": "the QA verdict for the attempt, published to the QA portal",
3204
+ "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",
3205
+ "requires_consent": true,
3206
+ "observed_in": [
3207
+ "persisted_consent"
3208
+ ]
3209
+ },
3210
+ {
3211
+ "destination": "{base-url}",
3212
+ "what": "HTTP requests against the campaign under test, INCLUDING order creation against the live store",
3213
+ "when": "always",
3214
+ "requires_consent": false,
3215
+ "observed_in": [],
3216
+ "not_observed_reason": "the offline fixture is blocked before the order ladder is reached; the mode is proved by the gate refusing without placing an order"
3217
+ },
3218
+ {
3219
+ "destination": "{proxy-base}/api/runs",
3220
+ "what": "the Run Record / progress remit envelope for this run",
3221
+ "when": "Run Telemetry consent is on for the endpoint and --no-remit was not given",
3222
+ "requires_consent": true,
3223
+ "observed_in": [],
3224
+ "not_observed_reason": "a blocked attempt assembles no Run Record, so nothing is remitted"
3225
+ },
3226
+ {
3227
+ "destination": "{proxy-base}/api/progress",
3228
+ "what": "the stage progress observation — the snapshot just captured locally, POSTed under X-Campaign-Key",
3229
+ "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",
3230
+ "requires_consent": true,
3231
+ "observed_in": [
3232
+ "persisted_consent"
3233
+ ]
3234
+ }
3235
+ ],
3236
+ "effect_test": "effects: qa run --test-order --no-live-refs",
3237
+ "test_scope": "full",
3238
+ "notes": "The `qa run --test-order` row's writes and sends, without the {proxy-base}/api/campaign read: the verdict records the live-campaign-refs check skipped with reason disabled, never a pass. The other sends keep their own conditions and opt-outs."
3239
+ },
3240
+ {
3241
+ "command": "qa",
3242
+ "subcommand": "run",
3243
+ "flags": [
3244
+ "--browser",
3245
+ "--no-live-refs"
3246
+ ],
3247
+ "annotations": {
3248
+ "readOnlyHint": false,
3249
+ "destructiveHint": true,
3250
+ "openWorldHint": true,
3251
+ "idempotentHint": false
3252
+ },
3253
+ "tier": "C",
3254
+ "writes": [
3255
+ {
3256
+ "path": "{target}/qa-output/**",
3257
+ "when": "always — the full verdict for the attempt is written to the qa-output tree",
3258
+ "observed_in": [
3259
+ "no_session",
3260
+ "ambient_session",
3261
+ "stale_session",
3262
+ "lifecycle_log",
3263
+ "persisted_consent"
3264
+ ]
3265
+ },
3266
+ {
3267
+ "path": "{cwd}/.campaign-runtime/qa-verdict.json",
3268
+ "when": "always — the committed verdict sidecar is projected from the attempt",
3269
+ "observed_in": [
3270
+ "no_session",
3271
+ "ambient_session",
3272
+ "stale_session",
3273
+ "lifecycle_log",
3274
+ "persisted_consent"
3275
+ ]
3276
+ },
3277
+ {
3278
+ "path": "{target}/.campaign-runtime/assembly-report.json",
3279
+ "when": "always — the QA stage outcome is recorded on the report",
3280
+ "observed_in": [
3281
+ "no_session",
3282
+ "ambient_session",
3283
+ "stale_session",
3284
+ "lifecycle_log",
3285
+ "persisted_consent"
3286
+ ]
3287
+ },
3288
+ {
3289
+ "path": "{target}/.campaign-runtime/progress/**",
3290
+ "when": "always — the QA stage progress snapshot is captured locally",
3291
+ "observed_in": [
3292
+ "no_session",
3293
+ "ambient_session",
3294
+ "stale_session",
3295
+ "lifecycle_log",
3296
+ "persisted_consent"
3297
+ ]
3298
+ },
3299
+ {
3300
+ "path": "{target}/.campaign-runtime/doctor-output.json",
3301
+ "when": "the QA outcome invalidates the retained doctor sidecar",
3302
+ "observed_in": [
3303
+ "no_session",
3304
+ "ambient_session",
3305
+ "stale_session",
3306
+ "lifecycle_log",
3307
+ "persisted_consent"
3308
+ ]
3309
+ },
3310
+ {
3311
+ "path": "{target}/.campaign-runtime/run-session.json",
3312
+ "when": "an ambient run session is resolved; a blocked attempt keeps it open, a ready verdict clears it",
3313
+ "observed_in": [
3314
+ "ambient_session"
3315
+ ]
3316
+ },
3317
+ {
3318
+ "path": "{lifecycle-journal}",
3319
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
3320
+ "observed_in": [
3321
+ "ambient_session",
3322
+ "lifecycle_log"
3323
+ ]
3324
+ }
3325
+ ],
3326
+ "sends": [
3327
+ {
3328
+ "destination": "{proxy-base}/api/qa/verdicts",
3329
+ "what": "the QA verdict for the attempt, published to the QA portal",
3330
+ "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",
3331
+ "requires_consent": true,
3332
+ "observed_in": [
3333
+ "persisted_consent"
3334
+ ]
3335
+ },
3336
+ {
3337
+ "destination": "{base-url}",
3338
+ "what": "browser navigation and HTTP requests against the campaign under test",
3339
+ "when": "a browser is installed and the gates pass",
3340
+ "requires_consent": false,
3341
+ "observed_in": [],
3342
+ "not_observed_reason": "the browser run cannot execute offline; the case proves the gate refusal"
3343
+ },
3344
+ {
3345
+ "destination": "{proxy-base}/api/runs",
3346
+ "what": "the Run Record / progress remit envelope for this run",
3347
+ "when": "Run Telemetry consent is on for the endpoint and --no-remit was not given",
3348
+ "requires_consent": true,
3349
+ "observed_in": [],
3350
+ "not_observed_reason": "a blocked attempt assembles no Run Record"
3351
+ },
3352
+ {
3353
+ "destination": "{proxy-base}/api/progress",
3354
+ "what": "the stage progress observation — the snapshot just captured locally, POSTed under X-Campaign-Key",
3355
+ "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",
3356
+ "requires_consent": true,
3357
+ "observed_in": [
3358
+ "persisted_consent"
3359
+ ]
3360
+ }
3361
+ ],
3362
+ "effect_test": "effects: qa run --browser --no-live-refs (preflight)",
3363
+ "test_scope": "preflight",
3364
+ "notes": "PREFLIGHT-PROVED like `qa run --browser`: no installed Chromium or reachable campaign offline. The `qa run --browser` row's writes and sends, without the {proxy-base}/api/campaign read: the verdict records the live-campaign-refs check skipped with reason disabled, never a pass. The other sends keep their own conditions and opt-outs.",
3365
+ "preflight": {
3366
+ "may_write": [
3367
+ "{target}/qa-output/**",
3368
+ "{cwd}/.campaign-runtime/qa-verdict.json",
3369
+ "{target}/.campaign-runtime/assembly-report.json",
3370
+ "{target}/.campaign-runtime/progress/**",
3371
+ "{target}/.campaign-runtime/doctor-output.json",
3372
+ "{target}/.campaign-runtime/run-session.json",
3373
+ "{lifecycle-journal}"
3374
+ ],
3375
+ "may_contact": [
3376
+ "/api/qa/verdicts",
3377
+ "/api/progress"
3378
+ ]
3379
+ }
3380
+ },
3381
+ {
3382
+ "command": "qa",
3383
+ "subcommand": "publish",
3384
+ "flags": [],
3385
+ "annotations": {
3386
+ "readOnlyHint": false,
3387
+ "destructiveHint": false,
3388
+ "openWorldHint": true,
3389
+ "idempotentHint": true
3390
+ },
3391
+ "tier": "A",
3392
+ "writes": [
3393
+ {
3394
+ "path": "{lifecycle-journal}",
3395
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
3396
+ "observed_in": [
3397
+ "ambient_session",
3398
+ "lifecycle_log"
3399
+ ]
3400
+ }
3401
+ ],
3402
+ "sends": [
3403
+ {
3404
+ "destination": "{proxy-base}/api/qa/verdicts",
3405
+ "what": "the already-stored verdict",
3406
+ "when": "the verdict's spec_hash is current and it is not already published",
3407
+ "requires_consent": false,
3408
+ "observed_in": [
3409
+ "no_session",
3410
+ "ambient_session",
3411
+ "stale_session",
3412
+ "lifecycle_log",
3413
+ "persisted_consent"
3414
+ ]
3415
+ }
3416
+ ],
3417
+ "effect_test": "effects: qa publish",
3418
+ "test_scope": "full",
3419
+ "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."
3420
+ },
3421
+ {
3422
+ "command": "qa",
3423
+ "subcommand": "publish",
3424
+ "flags": [
3425
+ "--republish"
3426
+ ],
3427
+ "annotations": {
3428
+ "readOnlyHint": false,
3429
+ "destructiveHint": false,
3430
+ "openWorldHint": true,
3431
+ "idempotentHint": true
3432
+ },
3433
+ "tier": "A",
3434
+ "writes": [
3435
+ {
3436
+ "path": "{lifecycle-journal}",
3437
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
3438
+ "observed_in": [
3439
+ "ambient_session",
3440
+ "lifecycle_log"
3441
+ ]
3442
+ }
3443
+ ],
3444
+ "sends": [
3445
+ {
3446
+ "destination": "{proxy-base}/api/qa/verdicts",
3447
+ "what": "the already-stored verdict",
3448
+ "when": "the verdict's spec_hash is current and it is not already published",
3449
+ "requires_consent": false,
3450
+ "observed_in": [
3451
+ "no_session",
3452
+ "ambient_session",
3453
+ "stale_session",
3454
+ "lifecycle_log",
3455
+ "persisted_consent"
3456
+ ]
3457
+ }
3458
+ ],
3459
+ "effect_test": "effects: qa publish --republish",
3460
+ "test_scope": "full",
3461
+ "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."
3462
+ },
3463
+ {
3464
+ "command": "qa",
3465
+ "subcommand": "parity",
3466
+ "flags": [],
3467
+ "annotations": {
3468
+ "readOnlyHint": false,
3469
+ "destructiveHint": false,
3470
+ "openWorldHint": true,
3471
+ "idempotentHint": false
3472
+ },
3473
+ "tier": "A",
3474
+ "writes": [
3475
+ {
3476
+ "path": "{cwd}/qa-output/**",
3477
+ "when": "the capture completes — the evidence bundle (<run-id>.parity-bundle.json: the assessed order, the candidate capture and any baseline capture) and the full verdict, both under the fixture's campaign slug. `qa parity` takes no packet, so the directory is qa-output under the working directory unless --output-dir names another",
3478
+ "observed_in": [],
3479
+ "not_observed_reason": "the parity capture drives the candidate funnel through Playwright, and the refusal (\"Playwright Chromium is not installed\") lands before a page is opened, so the run never reaches the bundle write or the verdict"
3480
+ },
3481
+ {
3482
+ "path": "{lifecycle-journal}",
3483
+ "when": "a lifecycle journal is selected for the invocation — --lifecycle-journal or CAMPAIGNS_OS_LIFECYCLE_LOG. This invocation takes no --packet, so an ambient run session opened at a target repo is not its session and its journal is not selected",
3484
+ "observed_in": [
3485
+ "lifecycle_log"
3486
+ ]
3487
+ }
3488
+ ],
3489
+ "sends": [
3490
+ {
3491
+ "destination": "{base-url}",
3492
+ "what": "browser navigation and typed-card order traversal of the scenario's checkout and upsell routes against the candidate campaign — real orders through the deployed checkout",
3493
+ "when": "a Chromium is installed and the scenario's routes resolve; the base URL is --base-url, else the fixture's candidate_base_url",
3494
+ "requires_consent": false,
3495
+ "observed_in": [],
3496
+ "not_observed_reason": "no browser is installed in the fixture's pinned Playwright registry, so the capture is refused before the first navigation and the loopback stand-in for --base-url is never contacted"
3497
+ },
3498
+ {
3499
+ "destination": "{proxy-base}/api/qa/verdicts",
2721
3500
  "what": "the parity verdict for the attempt, published to the QA portal",
2722
3501
  "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",
2723
3502
  "requires_consent": true,
@@ -3449,6 +4228,13 @@
3449
4228
  "persisted_consent"
3450
4229
  ]
3451
4230
  },
4231
+ {
4232
+ "path": "{target}/.campaign-runtime/fetched-specs/*.json",
4233
+ "when": "--map-id is given without --cached-spec — the fetched Map copy, named for the sanitized Map ID and replacing any earlier copy of that Map; the intake reads the spec from this path",
4234
+ "observed_in": [
4235
+ "persisted_consent"
4236
+ ]
4237
+ },
3452
4238
  {
3453
4239
  "path": "{target}/.campaign-runtime/theme/theme-report.json",
3454
4240
  "when": "the theme policy is not off",
@@ -3524,10 +4310,11 @@
3524
4310
  {
3525
4311
  "destination": "{proxy-base}/api/spec/{map-id}",
3526
4312
  "what": "the CampaignSpec fetch",
3527
- "when": "--map-id is given instead of --spec",
4313
+ "when": "--map-id is given instead of --spec, without --cached-spec",
3528
4314
  "requires_consent": false,
3529
- "observed_in": [],
3530
- "not_observed_reason": "the effect test seeds a local --spec, so the Map Builder fetch is declared and not exercised"
4315
+ "observed_in": [
4316
+ "persisted_consent"
4317
+ ]
3531
4318
  },
3532
4319
  {
3533
4320
  "destination": "{proxy-base}/api/runs",
@@ -3600,6 +4387,13 @@
3600
4387
  "persisted_consent"
3601
4388
  ]
3602
4389
  },
4390
+ {
4391
+ "path": "{target}/.campaign-runtime/fetched-specs/*.json",
4392
+ "when": "--map-id is given without --cached-spec — the fetched Map copy, named for the sanitized Map ID and replacing any earlier copy of that Map; the intake reads the spec from this path",
4393
+ "observed_in": [
4394
+ "persisted_consent"
4395
+ ]
4396
+ },
3603
4397
  {
3604
4398
  "path": "{target}/.campaign-runtime/theme/theme-report.json",
3605
4399
  "when": "the theme policy is not off",
@@ -3675,10 +4469,11 @@
3675
4469
  {
3676
4470
  "destination": "{proxy-base}/api/spec/{map-id}",
3677
4471
  "what": "the CampaignSpec fetch",
3678
- "when": "--map-id is given instead of --spec",
4472
+ "when": "--map-id is given instead of --spec, without --cached-spec",
3679
4473
  "requires_consent": false,
3680
- "observed_in": [],
3681
- "not_observed_reason": "the effect test seeds a local --spec"
4474
+ "observed_in": [
4475
+ "persisted_consent"
4476
+ ]
3682
4477
  },
3683
4478
  {
3684
4479
  "destination": "{proxy-base}/api/runs",
@@ -3751,6 +4546,13 @@
3751
4546
  "persisted_consent"
3752
4547
  ]
3753
4548
  },
4549
+ {
4550
+ "path": "{target}/.campaign-runtime/fetched-specs/*.json",
4551
+ "when": "--map-id is given without --cached-spec — the fetched Map copy, named for the sanitized Map ID and replacing any earlier copy of that Map; the intake reads the spec from this path",
4552
+ "observed_in": [
4553
+ "persisted_consent"
4554
+ ]
4555
+ },
3754
4556
  {
3755
4557
  "path": "{target}/.campaign-runtime/theme/theme-report.json",
3756
4558
  "when": "the theme policy is not off",
@@ -3806,10 +4608,11 @@
3806
4608
  {
3807
4609
  "destination": "{proxy-base}/api/spec/{map-id}",
3808
4610
  "what": "the CampaignSpec fetch",
3809
- "when": "--map-id is given instead of --spec",
4611
+ "when": "--map-id is given instead of --spec, without --cached-spec",
3810
4612
  "requires_consent": false,
3811
- "observed_in": [],
3812
- "not_observed_reason": "the effect test seeds a local --spec"
4613
+ "observed_in": [
4614
+ "persisted_consent"
4615
+ ]
3813
4616
  }
3814
4617
  ],
3815
4618
  "effect_test": "effects: start --no-run-session",
@@ -3872,6 +4675,13 @@
3872
4675
  "persisted_consent"
3873
4676
  ]
3874
4677
  },
4678
+ {
4679
+ "path": "{target}/.campaign-runtime/fetched-specs/*.json",
4680
+ "when": "--map-id is given without --cached-spec — the fetched Map copy, named for the sanitized Map ID and replacing any earlier copy of that Map; the intake reads the spec from this path",
4681
+ "observed_in": [
4682
+ "persisted_consent"
4683
+ ]
4684
+ },
3875
4685
  {
3876
4686
  "path": "{target}/.campaign-runtime/theme/theme-report.json",
3877
4687
  "when": "the theme policy is not off",
@@ -3925,10 +4735,11 @@
3925
4735
  {
3926
4736
  "destination": "{proxy-base}/api/spec/{map-id}",
3927
4737
  "what": "the CampaignSpec fetch",
3928
- "when": "--map-id is given instead of --spec",
4738
+ "when": "--map-id is given instead of --spec, without --cached-spec",
3929
4739
  "requires_consent": false,
3930
- "observed_in": [],
3931
- "not_observed_reason": "the effect test seeds a local --spec"
4740
+ "observed_in": [
4741
+ "persisted_consent"
4742
+ ]
3932
4743
  },
3933
4744
  {
3934
4745
  "destination": "{proxy-base}/api/runs",
@@ -4001,6 +4812,13 @@
4001
4812
  "persisted_consent"
4002
4813
  ]
4003
4814
  },
4815
+ {
4816
+ "path": "{target}/.campaign-runtime/fetched-specs/*.json",
4817
+ "when": "--map-id is given without --cached-spec — the fetched Map copy, named for the sanitized Map ID and replacing any earlier copy of that Map; the intake reads the spec from this path",
4818
+ "observed_in": [
4819
+ "persisted_consent"
4820
+ ]
4821
+ },
4004
4822
  {
4005
4823
  "path": "{target}/.campaign-runtime/theme/theme-report.json",
4006
4824
  "when": "the theme policy is not off",
@@ -4034,10 +4852,11 @@
4034
4852
  {
4035
4853
  "destination": "{proxy-base}/api/spec/{map-id}",
4036
4854
  "what": "the CampaignSpec fetch",
4037
- "when": "--map-id is given instead of --spec",
4855
+ "when": "--map-id is given instead of --spec, without --cached-spec",
4038
4856
  "requires_consent": false,
4039
- "observed_in": [],
4040
- "not_observed_reason": "the effect test seeds a local --spec"
4857
+ "observed_in": [
4858
+ "persisted_consent"
4859
+ ]
4041
4860
  }
4042
4861
  ],
4043
4862
  "effect_test": "effects: prepare-build --no-run-session",
@@ -4102,6 +4921,13 @@
4102
4921
  "persisted_consent"
4103
4922
  ]
4104
4923
  },
4924
+ {
4925
+ "path": "{target}/.campaign-runtime/fetched-specs/*.json",
4926
+ "when": "--map-id is given without --cached-spec — the fetched Map copy, named for the sanitized Map ID and replacing any earlier copy of that Map; the intake reads the spec from this path",
4927
+ "observed_in": [
4928
+ "persisted_consent"
4929
+ ]
4930
+ },
4105
4931
  {
4106
4932
  "path": "{target}/.campaign-runtime/theme/theme-report.json",
4107
4933
  "when": "the theme policy is not off",
@@ -4155,10 +4981,11 @@
4155
4981
  {
4156
4982
  "destination": "{proxy-base}/api/spec/{map-id}",
4157
4983
  "what": "the CampaignSpec fetch",
4158
- "when": "--map-id is given instead of --spec",
4984
+ "when": "--map-id is given instead of --spec, without --cached-spec",
4159
4985
  "requires_consent": false,
4160
- "observed_in": [],
4161
- "not_observed_reason": "the effect test seeds a local --spec"
4986
+ "observed_in": [
4987
+ "persisted_consent"
4988
+ ]
4162
4989
  },
4163
4990
  {
4164
4991
  "destination": "{proxy-base}/api/runs",
@@ -4229,6 +5056,13 @@
4229
5056
  "persisted_consent"
4230
5057
  ]
4231
5058
  },
5059
+ {
5060
+ "path": "{target}/.campaign-runtime/fetched-specs/*.json",
5061
+ "when": "--map-id is given without --cached-spec — the fetched Map copy, named for the sanitized Map ID and replacing any earlier copy of that Map; the intake reads the spec from this path",
5062
+ "observed_in": [
5063
+ "persisted_consent"
5064
+ ]
5065
+ },
4232
5066
  {
4233
5067
  "path": "{target}/.campaign-runtime/theme/theme-report.json",
4234
5068
  "when": "the theme policy is not off",
@@ -4293,10 +5127,11 @@
4293
5127
  {
4294
5128
  "destination": "{proxy-base}/api/spec/{map-id}",
4295
5129
  "what": "the CampaignSpec fetch",
4296
- "when": "--map-id is given instead of --spec",
5130
+ "when": "--map-id is given instead of --spec, without --cached-spec",
4297
5131
  "requires_consent": false,
4298
- "observed_in": [],
4299
- "not_observed_reason": "the effect test seeds a local --spec"
5132
+ "observed_in": [
5133
+ "persisted_consent"
5134
+ ]
4300
5135
  },
4301
5136
  {
4302
5137
  "destination": "{proxy-base}/api/runs",
@@ -4369,6 +5204,13 @@
4369
5204
  "persisted_consent"
4370
5205
  ]
4371
5206
  },
5207
+ {
5208
+ "path": "{target}/.campaign-runtime/fetched-specs/*.json",
5209
+ "when": "--map-id is given without --cached-spec — the fetched Map copy, named for the sanitized Map ID and replacing any earlier copy of that Map; the intake reads the spec from this path",
5210
+ "observed_in": [
5211
+ "persisted_consent"
5212
+ ]
5213
+ },
4372
5214
  {
4373
5215
  "path": "{target}/.campaign-runtime/theme/theme-report.json",
4374
5216
  "when": "the theme policy is not off",
@@ -4413,10 +5255,11 @@
4413
5255
  {
4414
5256
  "destination": "{proxy-base}/api/spec/{map-id}",
4415
5257
  "what": "the CampaignSpec fetch",
4416
- "when": "--map-id is given instead of --spec",
5258
+ "when": "--map-id is given instead of --spec, without --cached-spec",
4417
5259
  "requires_consent": false,
4418
- "observed_in": [],
4419
- "not_observed_reason": "the effect test seeds a local --spec"
5260
+ "observed_in": [
5261
+ "persisted_consent"
5262
+ ]
4420
5263
  }
4421
5264
  ],
4422
5265
  "effect_test": "effects: build --no-run-session",
@@ -4481,6 +5324,13 @@
4481
5324
  "persisted_consent"
4482
5325
  ]
4483
5326
  },
5327
+ {
5328
+ "path": "{target}/.campaign-runtime/fetched-specs/*.json",
5329
+ "when": "--map-id is given without --cached-spec — the fetched Map copy, named for the sanitized Map ID and replacing any earlier copy of that Map; the intake reads the spec from this path",
5330
+ "observed_in": [
5331
+ "persisted_consent"
5332
+ ]
5333
+ },
4484
5334
  {
4485
5335
  "path": "{target}/.campaign-runtime/theme/theme-report.json",
4486
5336
  "when": "the theme policy is not off",
@@ -4545,10 +5395,11 @@
4545
5395
  {
4546
5396
  "destination": "{proxy-base}/api/spec/{map-id}",
4547
5397
  "what": "the CampaignSpec fetch",
4548
- "when": "--map-id is given instead of --spec",
5398
+ "when": "--map-id is given instead of --spec, without --cached-spec",
4549
5399
  "requires_consent": false,
4550
- "observed_in": [],
4551
- "not_observed_reason": "the effect test seeds a local --spec"
5400
+ "observed_in": [
5401
+ "persisted_consent"
5402
+ ]
4552
5403
  },
4553
5404
  {
4554
5405
  "destination": "{proxy-base}/api/runs",
@@ -4753,6 +5604,218 @@
4753
5604
  "may_contact": []
4754
5605
  }
4755
5606
  },
5607
+ {
5608
+ "command": "record",
5609
+ "subcommand": "setup",
5610
+ "flags": [],
5611
+ "annotations": {
5612
+ "readOnlyHint": false,
5613
+ "destructiveHint": true,
5614
+ "openWorldHint": false,
5615
+ "idempotentHint": false
5616
+ },
5617
+ "tier": "C",
5618
+ "writes": [
5619
+ {
5620
+ "path": "{target}/.campaign-runtime/build-context.json",
5621
+ "when": "the record passes every check — scaffold.required becomes false (handoff to build)",
5622
+ "observed_in": [
5623
+ "no_session",
5624
+ "ambient_session",
5625
+ "stale_session",
5626
+ "lifecycle_log",
5627
+ "persisted_consent"
5628
+ ]
5629
+ },
5630
+ {
5631
+ "path": "{target}/.campaign-runtime/assembly-report.json",
5632
+ "when": "the record passes every check — stages.setup becomes completed",
5633
+ "observed_in": [
5634
+ "no_session",
5635
+ "ambient_session",
5636
+ "stale_session",
5637
+ "lifecycle_log",
5638
+ "persisted_consent"
5639
+ ]
5640
+ },
5641
+ {
5642
+ "path": "{target}/.campaign-runtime/doctor-output.json",
5643
+ "when": "a retained doctor sidecar exists — the record postdates the doctor snapshot, so it is stamped stale",
5644
+ "observed_in": [
5645
+ "no_session",
5646
+ "ambient_session",
5647
+ "stale_session",
5648
+ "lifecycle_log",
5649
+ "persisted_consent"
5650
+ ]
5651
+ },
5652
+ {
5653
+ "path": "{lifecycle-journal}",
5654
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
5655
+ "observed_in": [
5656
+ "ambient_session",
5657
+ "lifecycle_log"
5658
+ ]
5659
+ }
5660
+ ],
5661
+ "sends": [],
5662
+ "effect_test": "effects: record setup",
5663
+ "test_scope": "full",
5664
+ "notes": "Destructive: it replaces stages.setup and the Build Context scaffold block, which changes the stage next recommends. Refused, writing nothing, when next answers prepare-build, when the campaign output directory does not exist, when there is no Build Context or Assembly Report, or when either file would fail its schema. Both files are written together, the Build Context first."
5665
+ },
5666
+ {
5667
+ "command": "record",
5668
+ "subcommand": "setup",
5669
+ "flags": [
5670
+ "--dry-run"
5671
+ ],
5672
+ "annotations": {
5673
+ "readOnlyHint": true,
5674
+ "destructiveHint": false,
5675
+ "openWorldHint": false,
5676
+ "idempotentHint": true
5677
+ },
5678
+ "tier": "none",
5679
+ "writes": [],
5680
+ "sends": [],
5681
+ "effect_test": "effects: record setup --dry-run",
5682
+ "test_scope": "full",
5683
+ "notes": "record setup --dry-run runs every check the real invocation runs and prints what it would write; it writes nothing under the target, appends no lifecycle entry (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
5684
+ },
5685
+ {
5686
+ "command": "record",
5687
+ "subcommand": "build",
5688
+ "flags": [],
5689
+ "annotations": {
5690
+ "readOnlyHint": false,
5691
+ "destructiveHint": true,
5692
+ "openWorldHint": false,
5693
+ "idempotentHint": false
5694
+ },
5695
+ "tier": "C",
5696
+ "writes": [
5697
+ {
5698
+ "path": "{target}/.campaign-runtime/assembly-report.json",
5699
+ "when": "the record passes every check — stages.assembly becomes completed with build_fingerprint set to doctor's derived.build_output_fingerprint.value, and stages.polish becomes required unless its evidence is bound to that exact output",
5700
+ "observed_in": [
5701
+ "no_session",
5702
+ "ambient_session",
5703
+ "stale_session",
5704
+ "lifecycle_log",
5705
+ "persisted_consent"
5706
+ ]
5707
+ },
5708
+ {
5709
+ "path": "{target}/.campaign-runtime/doctor-output.json",
5710
+ "when": "a retained doctor sidecar exists — the record postdates the doctor snapshot, so it is stamped stale",
5711
+ "observed_in": [
5712
+ "no_session",
5713
+ "ambient_session",
5714
+ "stale_session",
5715
+ "lifecycle_log",
5716
+ "persisted_consent"
5717
+ ]
5718
+ },
5719
+ {
5720
+ "path": "{lifecycle-journal}",
5721
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
5722
+ "observed_in": [
5723
+ "ambient_session",
5724
+ "lifecycle_log"
5725
+ ]
5726
+ }
5727
+ ],
5728
+ "sends": [],
5729
+ "effect_test": "effects: record build",
5730
+ "test_scope": "full",
5731
+ "notes": "Destructive: it replaces stages.assembly and, when the output changed, resets stages.polish to required. The fingerprint is doctor's, never typed. Refused, writing nothing, when next answers prepare-build, when doctor cannot compute the fingerprint (no built output), when setup is still required or stages.setup is not terminal, or when the report would fail its schema. The Build Context is read, not written."
5732
+ },
5733
+ {
5734
+ "command": "record",
5735
+ "subcommand": "build",
5736
+ "flags": [
5737
+ "--dry-run"
5738
+ ],
5739
+ "annotations": {
5740
+ "readOnlyHint": true,
5741
+ "destructiveHint": false,
5742
+ "openWorldHint": false,
5743
+ "idempotentHint": true
5744
+ },
5745
+ "tier": "none",
5746
+ "writes": [],
5747
+ "sends": [],
5748
+ "effect_test": "effects: record build --dry-run",
5749
+ "test_scope": "full",
5750
+ "notes": "record build --dry-run runs every check the real invocation runs and prints what it would write; it writes nothing under the target, appends no lifecycle entry (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
5751
+ },
5752
+ {
5753
+ "command": "record",
5754
+ "subcommand": "polish",
5755
+ "flags": [],
5756
+ "annotations": {
5757
+ "readOnlyHint": false,
5758
+ "destructiveHint": true,
5759
+ "openWorldHint": false,
5760
+ "idempotentHint": false
5761
+ },
5762
+ "tier": "C",
5763
+ "writes": [
5764
+ {
5765
+ "path": "{target}/.campaign-runtime/assembly-report.json",
5766
+ "when": "the record passes every check — stages.polish becomes the file's status (completed, blocked with blockers, or skipped with skip_reason) and evidence, bound to doctor's current fingerprint; theme.repair_loop_defect when the file sets it",
5767
+ "observed_in": [
5768
+ "no_session",
5769
+ "ambient_session",
5770
+ "stale_session",
5771
+ "lifecycle_log",
5772
+ "persisted_consent"
5773
+ ]
5774
+ },
5775
+ {
5776
+ "path": "{target}/.campaign-runtime/doctor-output.json",
5777
+ "when": "a retained doctor sidecar exists — the record postdates the doctor snapshot, so it is stamped stale",
5778
+ "observed_in": [
5779
+ "no_session",
5780
+ "ambient_session",
5781
+ "stale_session",
5782
+ "lifecycle_log",
5783
+ "persisted_consent"
5784
+ ]
5785
+ },
5786
+ {
5787
+ "path": "{lifecycle-journal}",
5788
+ "when": "a lifecycle journal is selected for the invocation — an active run session, --lifecycle-journal, or CAMPAIGNS_OS_LIFECYCLE_LOG",
5789
+ "observed_in": [
5790
+ "ambient_session",
5791
+ "lifecycle_log"
5792
+ ]
5793
+ }
5794
+ ],
5795
+ "sends": [],
5796
+ "effect_test": "effects: record polish",
5797
+ "test_scope": "full",
5798
+ "notes": "Destructive: it replaces stages.polish (the captured page_load is kept). Refused, writing nothing, when build is not recorded for the current output, when the --evidence file has a shape error (named by field), or, for a completed status, when the polish gate doctor evaluates would not pass on the report it would write. The Build Context is read, not written."
5799
+ },
5800
+ {
5801
+ "command": "record",
5802
+ "subcommand": "polish",
5803
+ "flags": [
5804
+ "--dry-run"
5805
+ ],
5806
+ "annotations": {
5807
+ "readOnlyHint": true,
5808
+ "destructiveHint": false,
5809
+ "openWorldHint": false,
5810
+ "idempotentHint": true
5811
+ },
5812
+ "tier": "none",
5813
+ "writes": [],
5814
+ "sends": [],
5815
+ "effect_test": "effects: record polish --dry-run",
5816
+ "test_scope": "full",
5817
+ "notes": "record polish --dry-run runs every check the real invocation runs and prints what it would write; it writes nothing under the target, appends no lifecycle entry (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
5818
+ },
4756
5819
  {
4757
5820
  "command": "login",
4758
5821
  "subcommand": null,