@nextcommerce/campaigns-os 1.43.1 → 1.43.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/AGENTS.md +4 -2
  2. package/CHANGELOG.md +451 -0
  3. package/README.md +2 -2
  4. package/contracts/agent-relevant-change-policy.v1.json +5 -0
  5. package/contracts/effects.v1.json +8 -8
  6. package/contracts/release-ledger.json +672 -0
  7. package/contracts/supported-surface.json +2 -2
  8. package/docs/build-packet.md +64 -2
  9. package/docs/campaigns-os-build-flow.md +1 -0
  10. package/docs/design-source-package.md +89 -15
  11. package/docs/effects.md +16 -4
  12. package/docs/local-setup.md +1 -1
  13. package/docs/orientation-contract-reference.md +1 -1
  14. package/docs/progress-snapshots.md +10 -6
  15. package/docs/qa-and-test-orders.md +131 -7
  16. package/docs/release-ledger-authoring-guide.md +6 -4
  17. package/docs/runtime-readiness.md +1 -1
  18. package/docs/skills-revision.md +10 -10
  19. package/package.json +1 -1
  20. package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
  21. package/skills/campaign-readback-classification/SKILL.md +3 -3
  22. package/skills/campaign-run-evidence/SKILL.md +3 -3
  23. package/skills/contribution-intake/SKILL.md +3 -3
  24. package/skills/next-campaigns-build/SKILL.md +4 -4
  25. package/skills/next-campaigns-os/SKILL.md +3 -3
  26. package/skills/next-campaigns-os-setup/SKILL.md +3 -3
  27. package/skills/next-campaigns-polish/SKILL.md +3 -3
  28. package/skills/next-campaigns-qa/SKILL.md +3 -3
  29. package/skills.json +10 -10
  30. package/src/build-brief.mjs +6 -4
  31. package/src/built-script-syntax.mjs +480 -0
  32. package/src/campaigns-api-key.mjs +99 -0
  33. package/src/cli-helpers.mjs +118 -0
  34. package/src/cli.mjs +1211 -7495
  35. package/src/design-source-package.mjs +1 -1
  36. package/src/design-source-publication.mjs +898 -0
  37. package/src/diagnostic.mjs +2 -1
  38. package/src/directory-lock.mjs +270 -0
  39. package/src/doctor/checks.mjs +4415 -0
  40. package/src/doctor/inspect.mjs +636 -0
  41. package/src/doctor/next-step.mjs +731 -0
  42. package/src/install-invocation.mjs +29 -0
  43. package/src/invocation.mjs +179 -0
  44. package/src/private-template-source.mjs +1 -1
  45. package/src/progress-node.mjs +6 -35
  46. package/src/proof-policy.mjs +1 -1
  47. package/src/qa-analytics-correctness.mjs +3 -0
  48. package/src/qa-binding-evidence.mjs +76 -11
  49. package/src/qa-browser.mjs +778 -77
  50. package/src/qa-build-scope.mjs +47 -0
  51. package/src/qa-node.mjs +218 -13
  52. package/src/source-html-intake.mjs +1 -1
  53. package/src/source-html-manifest.mjs +9 -2
  54. package/src/stage-ledger.mjs +28 -0
  55. package/src/target-lock.mjs +54 -0
  56. package/src/template-brand-contract.mjs +17 -1
package/AGENTS.md CHANGED
@@ -117,8 +117,10 @@ appends no lifecycle entry and creates no file of its own. One effect does
117
117
  precede argument refusal: `start`, `prepare-build`, `build`, `run start` and
118
118
  `run end` close out a stale run session at the root they are about to act on
119
119
  before argv is refused, which is a declared effect of those commands and is
120
- suppressed by `--no-write`. Every remitting command's effect declaration, when
121
- published, names its destination as open-world, and the agent onboarding skill
120
+ suppressed by `--no-write`. Commands implementing `--dry-run` also suppress
121
+ that closeout whenever the flag is present, including a value that will be
122
+ refused (such as `run end --dry-run yes`). Every remitting command's effect
123
+ declaration, when published, names its destination as open-world, and the agent onboarding skill
122
124
  records an explicit telemetry choice before the first remitting command.
123
125
 
124
126
  1.37.0 adds `demo --target <new-directory>`, an offline visual sample
package/CHANGELOG.md CHANGED
@@ -2,6 +2,457 @@
2
2
 
3
3
  Notable supported-surface changes are recorded here.
4
4
 
5
+ ## [1.43.2] - 2026-09-28
6
+
7
+ ### Changed
8
+
9
+ - Stabilization release. Package and supported-surface version advance to
10
+ 1.43.2 and ship every same-surface change recorded since 1.43.1
11
+ (`1.43.1+agent.1` through `1.43.1+agent.23`). No command, message, or exit
12
+ code changes in this release itself.
13
+ - `contracts/effects.v1.json` notes for the eight `--dry-run` invocations now
14
+ say the command is declared `dryRun` in `src/invocation.mjs` instead of
15
+ naming the removed `DRY_RUN_COMMANDS` set. The declared effects are
16
+ unchanged.
17
+ - The local setup install command pins the 1.43.2 package. Bundled skills
18
+ carry revision `1.43.2+skills.1`, with each skill version advanced one patch.
19
+
20
+ ## [1.43.1+agent.23] - 2026-09-28
21
+
22
+ ### Changed
23
+
24
+ - No command behaves differently. The comment in the doctor's `route_root`
25
+ declaration check now uses a neutral placeholder for its near-miss route
26
+ examples and states the canonical form (`"/"` or `"/<public_route_slug>/"`)
27
+ outright. Comment-only; every message and every exit code is unchanged.
28
+
29
+ ## [1.43.1+agent.22] - 2026-09-28
30
+
31
+ ### Changed
32
+
33
+ - No command behaves differently. Follow-ups the 1.43.1+agent.21 review
34
+ recorded: the filesystem path-identity helper the doctor's next-step picker
35
+ and the Design Source Package publication share moved from `src/doctor/`
36
+ to the shared helper module (one definition; the doctor module now imports
37
+ it), stale comments in the publication module that still named
38
+ `prepare-build` as the target-lock holder now name the publication entry
39
+ that holds it, the Campaign Build Brief's private JSON clone (which maps a
40
+ nullish brief to an empty object, unlike the shared clone) is renamed so it
41
+ no longer shadows the shared helper, and the CLI drops three imports
42
+ nothing used. Every message and every exit code is unchanged.
43
+
44
+ ## [1.43.1+agent.21] - 2026-09-27
45
+
46
+ ### Changed
47
+
48
+ - No command behaves differently. Design Source Package publication for
49
+ `prepare-build`, `start` and `build` now has one owner instead of being
50
+ restated inside `prepare-build`: the pending provenance record, the target
51
+ lock's critical section, the output collision checks, the stage-evidence
52
+ re-checks and the staged publication order. The on-disk names, the
53
+ publication order, every message and every exit code are unchanged.
54
+
55
+ ## [1.43.1+agent.20] - 2026-09-27
56
+
57
+ ### Changed
58
+
59
+ - No command behaves differently. The doctor checks, the Build Packet and
60
+ built-output inspection, and the next-step picker that `doctor` and `next`
61
+ share now live under `src/doctor/` instead of inside the CLI module. Every
62
+ check, its order, its messages and its exit codes are unchanged.
63
+ - `contracts/agent-relevant-change-policy.v1.json` classifies a change under
64
+ `src/doctor/` as a CLI-surface change, so a later change there owes a
65
+ release-ledger entry. The shared helper modules `src/install-invocation.mjs`,
66
+ `src/cli-helpers.mjs` and `src/campaigns-api-key.mjs` are classified as
67
+ implementation, so a change confined to them owes a CHANGELOG section but no
68
+ release-ledger entry, even where a doctor message reads through them.
69
+ - The general helpers the CLI and the doctor share (the install-aware command
70
+ spelling, small value and JSON-file helpers, and Campaigns API key
71
+ resolution) moved to their own modules under `src/`. They are internal
72
+ implementation, not package exports.
73
+ - The repository's tests and performance worker import the moved functions from
74
+ their new modules.
75
+
76
+ ## [1.43.1+agent.19] - 2026-09-27
77
+
78
+ ### Changed
79
+
80
+ - No command behaves differently. The CLI's invocation policy now has one
81
+ owner instead of being restated at each step: which commands run outside
82
+ session recovery and lifecycle capture, where the stale run-session
83
+ closeout runs before `start`, `prepare-build`, `build`, `run start` and
84
+ `run end` and what suppresses it (`--no-write`, `--no-run-session`, and
85
+ `--dry-run` on a command that implements it), which invocations append no
86
+ lifecycle entry, which commands implement `--dry-run`, and when `qa run`
87
+ ends its run session. Every declared effect, every argument refusal and its
88
+ order, and the valued `--dry-run` and bare `run` behaviours are unchanged.
89
+ - The command list behind the did-you-mean suggestion for an unknown command
90
+ now comes from that same declaration instead of being read out of the
91
+ dispatch code's text. The list, its order and the suggestions are
92
+ unchanged.
93
+
94
+ ## [1.43.1+agent.18] - 2026-09-27
95
+
96
+ ### Fixed
97
+
98
+ - `prepare-build` no longer loses track of a Design Source Package it
99
+ synthesized when the run fails or is killed after publishing the package
100
+ but before writing the Assembly Report that records it. Before the package
101
+ goes out, the run writes a pending provenance record beside it
102
+ (`.campaign-runtime/input/.design-source-package.json.pending-provenance.json`)
103
+ with the sha256 of the bytes it is publishing, and removes it once the
104
+ report is written. A retry that finds the record treats a package that
105
+ still hashes to it as `origin: "synthesized"`, so a later `--force` after a
106
+ manifest edit regenerates the package instead of refusing it as someone
107
+ else's. A `--force` regeneration keeps the replaced package's hash in the
108
+ record until the replacement is recorded, so a regeneration that fails or
109
+ dies part way leaves the package on disk provable, and a run that adopts
110
+ another writer's package instead of publishing drops its own candidate from
111
+ the record. Just before the report is published the record is narrowed to
112
+ the package the report records. Bytes changed since the failure, and a
113
+ malformed record, are not vouched for. The record's path is reserved: no
114
+ configurable output may point at it.
115
+ - The `manifest_sha256` the Design Source Package records is now the hash of
116
+ the exact source-html manifest bytes source intake parsed. The file was
117
+ read twice, once to parse and once to hash, so an edit between the two
118
+ recorded a hash that did not describe the parsed manifest.
119
+ - With `--map-id`, `start`, `prepare-build` and `build` still fetch the Map
120
+ first, but write the fetched spec to the shared
121
+ `.campaign-runtime/fetched-specs/<map-id>.json` cache file only once they
122
+ hold the per-target prepare-build lock. A run waiting for the lock could
123
+ previously overwrite the cache with a newer revision while the lock holder
124
+ was still recording it, so the holder recorded mappings from one revision
125
+ beside hashes of the other. Argv-only intake refusals still happen before
126
+ any spec read, fetch or cache write, and a failed fetch still writes
127
+ nothing.
128
+ ## [1.43.1+agent.17] - 2026-09-27
129
+
130
+ ### Fixed
131
+
132
+ - The per-target lock that `prepare-build` holds, and the progress allocation
133
+ lock, can no longer be held by two processes at once. A process suspended
134
+ after creating the lock directory but before recording its owner used to
135
+ lose the lock to a waiter after ten seconds, and then both ran. The lock
136
+ directory and its owner record are now published together by one rename,
137
+ each holder confirms its own token before entering, and release only ever
138
+ removes a lock that still carries the holder's token. A lock directory with
139
+ no owner record, which only an older release leaves, is never taken over:
140
+ the command refuses it after about a second with a message naming the lock
141
+ directory (for progress capture, the warning now names the affected
142
+ `.allocation-lock`); remove it by hand once no campaigns-os process is
143
+ working on the target. A waiter that loses the publishing rename to a
144
+ holder that has already released now retries instead of failing. Do not run
145
+ an older release against the same target at the same time.
146
+ - Commands that edit the Assembly Report (`doctor`, `qa run`, waivers, the
147
+ polish merge and the other stage producers) now take the same per-target
148
+ lock as `prepare-build` for their read-modify-write, so stage evidence can
149
+ no longer land between `prepare-build`'s final stage-evidence check and its
150
+ publication. A producer reached from inside `prepare-build`'s own run enters
151
+ without waiting on itself. A waiver `--dry-run` preview writes nothing and
152
+ takes no lock.
153
+ - `prepare-build` refuses a `--out`, `--context-out`, `--report-out`,
154
+ `--doctor-out` or `--brief-out` path inside the lock directory
155
+ (`.campaign-runtime/input/.design-source-package.json.lock`) or the
156
+ staging and tomb directories the lock creates beside it (`.lock.staging-*`,
157
+ `.lock.recovery-staging-*`, `.lock.released-*`, `.lock.abandoned-*`),
158
+ including through a symlinked directory or a case-only alias. Such an output was written and then deleted with the lock, leaving
159
+ the packet and context pointing at a missing file.
160
+ ## [1.43.1+agent.16] - 2026-09-27
161
+
162
+ ### Fixed
163
+
164
+ - QA's optional analytics comparison against a legacy funnel
165
+ (`--analytics-baseline`) now measures a partial build's first built page
166
+ when the campaign root is not part of the build or does not answer, the same
167
+ way the analytics correctness check has since #493. It used to measure the
168
+ campaign root only, which a partial build does not have. The comparison
169
+ records which page it measured. When no built page answers, it is skipped
170
+ if there was nothing to try, and blocks if every page it tried failed; in
171
+ both cases the legacy funnel is not loaded. An explicit
172
+ `--analytics-candidate` URL is still measured as given.
173
+ - A funnel entry whose URL differs from the campaign root only by its query
174
+ string (for example `/campaign/?step=checkout`) is no longer treated as the
175
+ root. On a partial build, the root counted as built and the entry was
176
+ dropped as a duplicate, so the root's generic page was measured instead of
177
+ that entry. The entry is now measured itself and marked `query_routed`.
178
+ Step routing stays path-based in every certified family.
179
+ - `docs/qa-and-test-orders.md` now describes which page the analytics checks
180
+ measure on a partial build, the `no_in_scope_page_captured` and
181
+ `no_capture_page_answered` outcomes, how query strings affect page identity,
182
+ and when a local-serve run turns a silent pixel into `manual_review`: a
183
+ recorded development render and a page measured on localhost, with
184
+ `data-layer-purchase` still blocking.
185
+ ## [1.43.1+agent.15] - 2026-09-27
186
+
187
+ ### Fixed
188
+
189
+ - The doctor `built_output.script_syntax` gate and the QA `script-parse`
190
+ check now resolve each `<script src>` against the base in effect when the
191
+ parser prepares that script at its end tag: the first HTML `<base href>` in
192
+ tree order among those already parsed, or else the page. A `<base>` parsed
193
+ after a script no longer moves it, whether it is async, deferred or a
194
+ module, and parse order decides even when table foster parenting reorders
195
+ the tree. An SVG `base` no longer counts, and the href is no longer trimmed
196
+ of non-ASCII whitespace the URL parser keeps. Only HTML-namespace
197
+ `<script>` elements are page scripts: an SVG `<script src>` is no longer
198
+ read or parsed by doctor, and QA leaves a page with an SVG script dynamic
199
+ instead of fetching it. Only an empty `src` is skipped, as the browser
200
+ skips it; a `src` of other whitespace is resolved and read. QA recognises
201
+ the Campaign Cart SDK by its URL as the parser reads it, so a tab or
202
+ newline inside the attribute no longer makes the SDK look like an
203
+ unavailable config script. A base
204
+ the browser refuses (a `data:` or `javascript:` URL, or one that does not
205
+ parse) now falls back to the page, as the HTML "set the frozen base URL"
206
+ steps require, so the local script is read and a parse failure in it blocks
207
+ instead of the script being listed as unresolved (#502).
208
+
209
+ ### Changed
210
+
211
+ - A local script a built page loads that is not in the built output is now a
212
+ doctor warning, `built_output.script_syntax.missing_script`, one per src
213
+ naming the pages that load it. It was information on the gate only. It does
214
+ not block. While a parse failure blocks the gate, the missing scripts stay
215
+ on the gate's `warned[]` (#502).
216
+ - `fixtures/certified-families/` now carries every local script the rendered
217
+ pages load (each family's `js/*.js` beside `config.js`), refreshed from the
218
+ same templates commit. `scripts/refresh-certified-family-fixtures.mjs`
219
+ copies them, resolved the way the gate resolves them, and fails when a
220
+ referenced script is not in the render, or a copied file is a symlink or
221
+ resolves outside the family's render. The reachability test reads the
222
+ expected scripts from the HTML independently of the gate and requires the
223
+ gate to read exactly that set on every certified family (#502).
224
+ ## [1.43.1+agent.14] - 2026-09-27
225
+
226
+ ### Fixed
227
+
228
+ - An accepted upsell whose mutation body loads late is now matched to the
229
+ request its own click made, not to any response on the order-upsells URL.
230
+ Every upsell step in a path posts to the same `/orders/<ref>/upsells/` URL,
231
+ whether the steps share a page or sit on separate pages, so an earlier
232
+ step's slow body could land while a later step waited for its own and be
233
+ judged as the later step's evidence: failing it when that body lacked its
234
+ line, or passing it on the earlier step's line. The runner keeps the
235
+ Playwright request of each captured response and of each step's mutation
236
+ and accepts a late body only when the two are the same request. A step
237
+ whose own body never arrived and that no later read-back settled is
238
+ unverified even when the stale lines on hand would have matched. The
239
+ step's mutation watch also ignores any order-upsells response whose
240
+ request started before the watch was armed at the click, so an earlier
241
+ step's response that arrives late, after its own watch expired, is no
242
+ longer taken as this step's.
243
+ - A test-order path whose only open question is an unverified accepted upsell
244
+ is no longer `ok` on its result. The result carries `upsell_unverified`
245
+ instead, the order's `verification.verified` is `false` (so the purchase
246
+ proof summary no longer counts it in `orders_verified`; it still counts in
247
+ `orders_created`), and the path is neither re-run (a second order) nor
248
+ passed through read-only recovery (which cannot re-check an upsell). The
249
+ `browser-test-order` assertion still reports it as `manual_review`. A path
250
+ with an unverified upsell and another failure is recovered as before, but
251
+ a recovery that clears the other failure leaves the upsell unverified: the
252
+ result goes to `manual_review`, not `pass`, and the order stays unverified.
253
+ ## [1.43.1+agent.13] - 2026-09-27
254
+
255
+ ### Changed
256
+
257
+ - The lifecycle effects tests now prove that `start`, `prepare-build` and
258
+ `build` refuse a bare, empty or whitespace `--template-family`,
259
+ `--allow-uncertified-template`, `--theme-policy` or `--brief`, and an
260
+ unsupported `--theme-policy`, before the CampaignSpec is looked at. The
261
+ earlier refusal tests seeded a valid spec, so a check that ran after the
262
+ spec was read would still have passed them. The new cases make the local
263
+ `--spec` file and the `--cached-spec` cache file missing, a directory, or
264
+ malformed JSON, and require the flag refusal with no journal entry, no fetch
265
+ and an unchanged tree. The tree snapshot these tests compare now lists
266
+ directories as well as files, so a refusal that only creates an empty
267
+ directory is caught too. Tests only; CLI behavior is unchanged (#504).
268
+
269
+ ## [1.43.1+agent.12] - 2026-09-26
270
+
271
+ ### Fixed
272
+
273
+ - QA no longer blocks every local proof run on analytics. Under
274
+ `deploy.target: local-serve` the build renders the development environment,
275
+ which leaves out the vendor loaders on purpose, so a declared pixel could
276
+ never fire on localhost. When the run is served from localhost and the build
277
+ recorded `stages.assembly.evidence.build_environment: development`, the tag,
278
+ out-of-band vendor and receipt Purchase (`purchase-fires`) checks that did
279
+ not fire on a page measured on localhost are now `manual_review` with the
280
+ reason `local_serve_development_render` instead of blockers. Each one says
281
+ to re-run QA against the PR preview with `--base-url <preview-url>`, which
282
+ is a production render and still gates them, and cites the recorded
283
+ `page-kit parity` result when there is one. A production build, or a build
284
+ with no recorded environment, keeps its blockers on localhost. The exception
285
+ covers only pages measured on localhost: the tracking capture now records
286
+ the page URL it settled on after redirects (`final_url`), and a capture that
287
+ landed on another host, such as a built entry page whose URL is a production
288
+ preview or a localhost root that redirects to production, keeps its
289
+ blockers. So does a receipt Purchase check whose receipt page is not on
290
+ localhost, judged both by the order's final URL and by the page URL read
291
+ after the receipt's analytics settled (`receipt_document_url`), so a
292
+ localhost receipt that redirects to a hosted page while analytics settle
293
+ keeps its blocker, as does any check whose measured page was not recorded.
294
+ So does a capture that failed: a `purchase-fires` failure that lists
295
+ unmeasured receipts in `capture_error_plan_ids` still blocks, and a tracking
296
+ capture whose page could not be read (for example, a page that reloaded
297
+ while the capture read it) now fails as the analytics runner blocker instead
298
+ of reading as a page where nothing fired. The data-layer Purchase
299
+ check (`data-layer-purchase`) still blocks too: the SDK pushes `dl_purchase`
300
+ in the development render as well.
301
+
302
+ ## [1.43.1+agent.11] - 2026-09-26
303
+
304
+ ### Fixed
305
+
306
+ - `start`, `prepare-build` and `build` now refuse a bare, empty or
307
+ whitespace-only `--template-family`, `--allow-uncertified-template`,
308
+ `--theme-policy` or `--brief`, and a `--theme-policy` other than
309
+ `inspect_only`, `auto` or `off`, before reading the spec, fetching the Map or
310
+ writing the spec cache. Before, these four were read only after the spec was
311
+ resolved: a blank value was quietly ignored (or, for `--theme-policy`, fell
312
+ back to `inspect_only`), and an unknown theme policy failed partway through
313
+ intake and was journaled as a handler failure. A refused invocation writes
314
+ no journal entry. Whether a named family is certified, and whether a named
315
+ brief can be read, still depend on file content, so those failures are still
316
+ journaled.
317
+ ## [1.43.1+agent.10] - 2026-09-26
318
+
319
+ ### Fixed
320
+
321
+ - Browser QA no longer hangs on an upsell accept when the page moves on before
322
+ the upsell response body has loaded. The runner read that body with no time
323
+ limit, and a page that redirected as soon as the response headers arrived
324
+ could leave the read waiting forever. The read now gives up after a few
325
+ seconds: the step still reports the response and its status, with no order
326
+ body, and records that the read timed out. Checkout event capture keeps its
327
+ unbounded read, since nothing waits on it: an order body that loads late
328
+ still counts as order evidence.
329
+ - A slow but successful upsell accept is no longer failed as "no new upsell
330
+ line". When the upsell body read times out on a successful response, the
331
+ step waits up to 15 seconds, inside its own time budget, for the late body
332
+ or an order read-back that shows the accepted line. If neither arrives, the
333
+ upsell is reported as unverified and the test order goes to manual review,
334
+ not to a blocker. A late body, or an order read-back captured after the
335
+ click, that lacks the line still fails.
336
+ ## [1.43.1+agent.9] - 2026-09-26
337
+
338
+ ### Fixed
339
+
340
+ - Doctor no longer reports a build ready when a campaign script has a syntax
341
+ error. Every doctor run that sees built output, `doctor --built` and the
342
+ packet path alike, now parses each campaign-owned `.js` file a built page
343
+ loads by a local `<script src>`, and blocks under
344
+ `built_output.script_syntax.parse_failure` when one does not parse. The error
345
+ names the file, line and column, for example a hand-edited checkout script
346
+ left with one closing `});` too many. Remote scripts such as CDN URLs are not
347
+ read, `type="module"` scripts are parsed as modules, and classic `nomodule`
348
+ scripts are skipped. Script types are read as the browser reads them, trimmed
349
+ of surrounding whitespace and case-insensitive, and a module script is parsed
350
+ even when it carries `nomodule`, since the browser still runs it. Script paths resolve against the page's `<base href>` and are
351
+ percent-decoded, as the browser loads them. The gate is not waivable and
352
+ passes on every certified starter family.
353
+ - QA no longer reads a page script that does not parse as "dynamic". The
354
+ credential binding treats its declarations as unavailable, and QA adds a
355
+ `script-parse:<page_id>` blocker naming the script, line and column. Both
356
+ report a fixed diagnostic category, never text from the script. QA
357
+ classifies script types the same way doctor does.
358
+ ## [1.43.1+agent.8] - 2026-09-26
359
+
360
+ ### Fixed
361
+
362
+ - `prepare-build --force` (and `start --force` and `build --force`) now
363
+ regenerates a stale Design Source Package that an earlier `prepare-build`
364
+ synthesized, instead of refusing it. Previously, editing the source manifest
365
+ after a first run left `.campaign-runtime/input/design-source-package.json`
366
+ stale, and every rerun failed until the file was deleted by hand, with nothing
367
+ in the output saying so. The Assembly Report now records the package's
368
+ `origin` (`synthesized` or `adopted`), and a package counts as the producer's
369
+ own only when that report says `synthesized` and the bytes on disk still match
370
+ its hash. A package placed by an operator, edited by hand, or recorded by an
371
+ older report is still refused, `--force` or not. Every refusal now names the
372
+ file and the recovery: rerun with `--force` for the producer's own package,
373
+ otherwise reconcile it or delete it and rerun.
374
+ - The manifest docs now say up front that a source-html manifest `pages[]`
375
+ entry with both `path` and `skip_reason` is invalid.
376
+ ## [1.43.1+agent.7] - 2026-09-26
377
+
378
+ ### Fixed
379
+
380
+ - QA's analytics tracking check works on partial builds. It used to capture the
381
+ campaign root (`/<slug>/`) even when the build starts deeper, such as at
382
+ `checkout/`, so it read an empty page and failed every declared pixel as
383
+ absent. On a partial build the root now counts as in scope only when a
384
+ built, in-scope page is served there, so a host's directory index or
385
+ generic fallback at the root is never measured. When the root is out of
386
+ scope, answers with a non-2xx status, or fails to load (a navigation timeout
387
+ or network error), the check captures the first built in-scope page, the same entry partial-scope QA starts from, and records the
388
+ page it used and why on the `analytics-correctness:capture` evidence. If the
389
+ build has no capturable page at all, the check is skipped with the reason
390
+ `no_in_scope_page_captured`. If pages existed but none answered 2xx or
391
+ loaded at all, the check fails as a blocker with the reason `no_capture_page_answered` and
392
+ lists each attempt, because the declared vendors went unmeasured.
393
+
394
+ ## [1.43.1+agent.6] - 2026-09-26
395
+
396
+ ### Fixed
397
+
398
+ - Browser QA no longer misses the upsell accept on a control that pulses
399
+ forever, such as a stock `pb-animate="pulse-upsell"` button. The runner used
400
+ to wait about 30 seconds for the control to settle before scrolling to it and
401
+ another 10 before forcing the click, so the upsell POST landed after its
402
+ 20-second watch had expired. That reported `api_response_seen: false` for an
403
+ accept that had worked, and could time out deep accept paths. Controls are now
404
+ scrolled into view without a settle wait, a control that animates forever is
405
+ clicked straight away, and the watch starts at the click. Cart-entry,
406
+ package-card, checkout-submit and text-matched clicks use the same bounded
407
+ scroll.
408
+
409
+ ## [1.43.1+agent.5] - 2026-09-26
410
+
411
+ ### Fixed
412
+
413
+ - Payment-logo residue checks ignore starter `payment-logos.html` logos that
414
+ are still `hidden`. The template hides each method's logo until the campaign
415
+ offers it, so doctor and browser QA no longer flag PayPal or Klarna on pages
416
+ built from the new templates. A logo left visible is still checked; when a
417
+ page forces one on, doctor's warning points at the `payment_flags.show_<method>`
418
+ frontmatter flag.
419
+
420
+ ## [1.43.1+agent.4] - 2026-09-26
421
+
422
+ ### Fixed
423
+
424
+ - Partial-build QA skips recorded, unbuilt out-of-scope pages with explicit
425
+ `out_of_build_scope` evidence and starts at the first in-scope page. Built
426
+ stock pages rejoin QA; missing in-scope pages still fail. Commercial checks
427
+ share the same scope as HTTP and browser checks.
428
+ - Build handoffs keep skipped routes unbuilt by default. Materializing a stock
429
+ stand-in requires explicit per-page operator opt-in, so upstream pages on
430
+ another host are not replaced with placeholder copy.
431
+ ## [1.43.1+agent.3] - 2026-09-26
432
+
433
+ ### Fixed
434
+
435
+ - Legacy direct-API QA rejects missing or unusable carts and unknown test-order
436
+ modes before resolving campaign inputs, without appending a lifecycle entry.
437
+ Browser QA precedence and legacy API credential checks are unchanged.
438
+
439
+ ## [1.43.1+agent.2] - 2026-09-26
440
+
441
+ ### Fixed
442
+
443
+ - `run end --dry-run yes` now refuses the valued flag without closing a stale
444
+ session first. No Run Record, lifecycle entry, remit, or session deletion
445
+ occurs. Bare `--dry-run` and ordinary run closeout keep their existing behavior.
446
+
447
+ ## [1.43.1+agent.1] - 2026-09-25
448
+
449
+ ### Fixed
450
+
451
+ - Doctor recognizes numeric package, shipping and offer references exported by
452
+ the Map, so declared commerce IDs no longer trigger false undeclared-package
453
+ blockers or starter-demo warnings. Reference fallback fields now use the same
454
+ string-or-finite-number rule; other types are ignored instead of stringified.
455
+
5
456
  ## [1.43.1] - 2026-09-24
6
457
 
7
458
  ### Fixed
package/README.md CHANGED
@@ -198,8 +198,8 @@ are template stock instead — no bespoke design, the starter family *is* the
198
198
  design — there is no screenshot to honestly supply. Declare those pages out of
199
199
  source scope (a manifest `skip_reason` entry, or CampaignSpec
200
200
  `build_scope.mode: "partial"`): intake records them as template stock, demands
201
- no design source for them, and the build stage materialises each from the
202
- locked family's own page. A family that publishes Template Reference proof
201
+ no design source for them, and leaves them unbuilt unless the operator opts in
202
+ per page to materializing the locked family's stock. A family that publishes Template Reference proof
203
203
  (today `apollo`) covers them with `template_baseline`; every other family
204
204
  records an accepted Source Gap and intake lands at `ready_with_gaps`. See
205
205
  [Template-stock pages: the family decides](docs/design-source-package.md#template-stock-pages-the-family-decides).
@@ -67,6 +67,11 @@
67
67
  "class": "cli_surface",
68
68
  "_note": "The agent-facing entry points (skill install, agent context, the tooling-status revision check) are reachable from the supported argv surface, so a change under src/agent/ is a CLI-surface change even though the rest of src/ is unsupported implementation. This rule sits ahead of the broad src/ ignore on purpose: the ignore's reason is 'implementation reachable only through declared package exports', which is exactly what this subtree is not."
69
69
  },
70
+ {
71
+ "match": { "kind": "prefix", "value": "src/doctor/" },
72
+ "class": "cli_surface",
73
+ "_note": "Doctor checks are kernel-declared behavior reachable from the supported argv surface (`doctor`, `next`, `start`, `prepare-build` and `qa run` read them), so a change under src/doctor/ is a CLI-surface change even though the rest of src/ is unsupported implementation. This rule sits ahead of the broad src/ ignore on purpose, as the src/agent/ rule does. The shared helper modules beside it under src/ (src/install-invocation.mjs, src/cli-helpers.mjs, src/campaigns-api-key.mjs) remain implementation under the broad src/ ignore."
74
+ },
70
75
  {
71
76
  "match": { "kind": "prefix", "value": "agents/" },
72
77
  "class": "documentation",
@@ -236,7 +236,7 @@
236
236
  "sends": [],
237
237
  "effect_test": "effects: page-kit sync --dry-run",
238
238
  "test_scope": "full",
239
- "notes": "page-kit sync --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."
239
+ "notes": "page-kit sync --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 (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
240
240
  },
241
241
  {
242
242
  "command": "spec",
@@ -255,7 +255,7 @@
255
255
  "sends": [],
256
256
  "effect_test": "effects: spec derive --dry-run",
257
257
  "test_scope": "full",
258
- "notes": "spec derive --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."
258
+ "notes": "spec derive --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 (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
259
259
  },
260
260
  {
261
261
  "command": "install-skills",
@@ -274,7 +274,7 @@
274
274
  "sends": [],
275
275
  "effect_test": "effects: install-skills --dry-run",
276
276
  "test_scope": "full",
277
- "notes": "install-skills --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."
277
+ "notes": "install-skills --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 (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
278
278
  },
279
279
  {
280
280
  "command": "install-agent-context",
@@ -293,7 +293,7 @@
293
293
  "sends": [],
294
294
  "effect_test": "effects: install-agent-context --dry-run",
295
295
  "test_scope": "full",
296
- "notes": "install-agent-context --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."
296
+ "notes": "install-agent-context --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 (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
297
297
  },
298
298
  {
299
299
  "command": "qa",
@@ -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. Local-spec packets are refused before publication because they have no saved Map destination."
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 (the command is declared `dryRun` in src/invocation.mjs) 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",
@@ -331,7 +331,7 @@
331
331
  "sends": [],
332
332
  "effect_test": "effects: checkpoint waive --dry-run",
333
333
  "test_scope": "full",
334
- "notes": "checkpoint waive --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."
334
+ "notes": "checkpoint waive --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 (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
335
335
  },
336
336
  {
337
337
  "command": "theme",
@@ -350,7 +350,7 @@
350
350
  "sends": [],
351
351
  "effect_test": "effects: theme waive --dry-run",
352
352
  "test_scope": "full",
353
- "notes": "theme waive --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."
353
+ "notes": "theme waive --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 (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
354
354
  },
355
355
  {
356
356
  "command": "run-record",
@@ -369,7 +369,7 @@
369
369
  "sends": [],
370
370
  "effect_test": "effects: run-record --dry-run",
371
371
  "test_scope": "full",
372
- "notes": "run-record --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."
372
+ "notes": "run-record --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 (the command is declared `dryRun` in src/invocation.mjs) and runs no stale-session closeout."
373
373
  },
374
374
  {
375
375
  "command": "run-record",