@nextcommerce/campaigns-os 1.54.0 → 1.57.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.
- package/CHANGELOG.md +148 -0
- package/CONTEXT.md +19 -5
- package/README.md +26 -12
- package/campaign-spec/README.md +15 -1
- package/campaign-spec/dist/checkout-flow.d.ts +90 -0
- package/campaign-spec/dist/checkout-flow.js +229 -0
- package/campaign-spec/dist/index.d.ts +1 -0
- package/campaign-spec/dist/index.js +6 -1
- package/campaign-spec/dist/normalize.d.ts +3 -2
- package/campaign-spec/dist/normalize.js +7 -5
- package/campaign-spec/dist/routing.d.ts +6 -3
- package/campaign-spec/dist/routing.js +6 -3
- package/campaign-spec/dist/rules/checkout-forward-target.d.ts +17 -0
- package/campaign-spec/dist/rules/checkout-forward-target.js +48 -0
- package/campaign-spec/dist/rules/checkout-step-reaches-checkout.d.ts +12 -0
- package/campaign-spec/dist/rules/checkout-step-reaches-checkout.js +37 -0
- package/campaign-spec/dist/rules/exit-intent-validation.js +2 -1
- package/campaign-spec/dist/rules/index.d.ts +4 -0
- package/campaign-spec/dist/rules/index.js +12 -0
- package/campaign-spec/dist/rules/one-checkout-per-path.d.ts +25 -0
- package/campaign-spec/dist/rules/one-checkout-per-path.js +84 -0
- package/campaign-spec/dist/rules/pre-payment-forward-target.d.ts +12 -0
- package/campaign-spec/dist/rules/pre-payment-forward-target.js +42 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.js +2 -1
- package/campaign-spec/dist/rules/schema-version.d.ts +9 -4
- package/campaign-spec/dist/rules/schema-version.js +10 -5
- package/campaign-spec/dist/types.d.ts +20 -3
- package/campaign-spec/dist/types.js +3 -2
- package/compatibility.json +2 -2
- package/contracts/fixtures/runtime-recipe/accept/current.json +6 -1
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +6 -1
- package/contracts/fixtures/runtime-recipe/dist-states.json +1 -1
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +1 -1
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +5 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +6 -1
- package/contracts/release-ledger.json +1146 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +6 -1
- package/contracts/supported-surface.json +9 -5
- package/docs/brand-theme-bridge.md +10 -0
- package/docs/build-packet.md +86 -26
- package/docs/campaigns-os-build-flow.md +1 -1
- package/docs/effects.md +2 -5
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/qa-and-test-orders.md +133 -29
- package/docs/runtime-readiness.md +8 -3
- package/docs/skills-revision.md +10 -10
- package/docs/supported-surface.md +1 -1
- package/docs/versioning.md +4 -1
- package/docs/workflow-findings-sidecar.md +57 -2
- package/package.json +4 -1
- package/schemas/campaign-runtime-build-context.v0.schema.json +3 -1
- package/schemas/campaign-runtime-build-packet.v0.schema.json +3 -1
- package/schemas/campaign-spec.v5.schema.json +586 -0
- package/schemas/campaigns-os-run-record.v0.schema.json +20 -15
- package/skills/campaign-lifecycle-orientation/SKILL.md +10 -8
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +3 -3
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +6 -4
- package/skills/next-campaigns-os/SKILL.md +3 -3
- package/skills/next-campaigns-os-setup/SKILL.md +3 -3
- package/skills/next-campaigns-polish/SKILL.md +21 -6
- package/skills/next-campaigns-qa/SKILL.md +5 -4
- package/skills.json +10 -10
- package/src/adapter-decision-contract.mjs +27 -13
- package/src/brand-theme.mjs +44 -8
- package/src/build-brief.mjs +14 -5
- package/src/campaign-identity.mjs +33 -4
- package/src/cli-helpers.mjs +129 -0
- package/src/cli.mjs +156 -111
- package/src/commercial-journey.mjs +25 -2
- package/src/consent.mjs +3 -2
- package/src/design-source-package.mjs +1 -1
- package/src/design-source-publication.mjs +8 -8
- package/src/deviation.mjs +2 -1
- package/src/doctor/checks.mjs +212 -19
- package/src/doctor/inspect.mjs +4 -2
- package/src/doctor/next-step.mjs +46 -16
- package/src/doctor-sidecar.mjs +5 -3
- package/src/gate-actions.mjs +21 -1
- package/src/input-refresh.mjs +6 -6
- package/src/invocation.mjs +4 -3
- package/src/lifecycle.mjs +88 -6
- package/src/local-preview-policy.mjs +116 -12
- package/src/login.mjs +2 -1
- package/src/polish-browser.mjs +8 -2
- package/src/polish-node.mjs +9 -4
- package/src/polish-readability.mjs +2 -2
- package/src/qa-browser.mjs +741 -120
- package/src/qa-cart-entry.mjs +107 -20
- package/src/qa-node.mjs +179 -72
- package/src/qa-test-order-topology.mjs +118 -1
- package/src/qa-verdict-discovery.mjs +21 -4
- package/src/qa-verdict-publish.mjs +2 -1
- package/src/run-record.mjs +46 -34
- package/src/runtime-state-ignore.mjs +7 -5
- package/src/sdk-markup.mjs +41 -1
- package/src/source-html-intake.mjs +46 -4
- package/src/stage-ledger.mjs +18 -4
- package/src/stage-record.mjs +53 -16
|
@@ -101,7 +101,7 @@ recapture. A hosted target (`netlify`, `cloudflare-pages`, …) is unaffected:
|
|
|
101
101
|
its build stage renders production as before and `page-kit parity` refuses the
|
|
102
102
|
packet (`local_proof.parity.not_local_serve`).
|
|
103
103
|
|
|
104
|
-
### Missing evidence carried forward on
|
|
104
|
+
### Missing evidence carried forward on previews
|
|
105
105
|
|
|
106
106
|
On a `local-serve` packet served from a loopback host (`localhost`,
|
|
107
107
|
`127.0.0.1`, `[::1]`, for both `deploy.preview_url` and `--base-url`), some
|
|
@@ -110,20 +110,28 @@ loop. The campaign must still prove its commerce there: store and campaign
|
|
|
110
110
|
binding, routes, SDK loading, prices and a typed-card order. Only these
|
|
111
111
|
checks are carried forward:
|
|
112
112
|
|
|
113
|
-
| Check |
|
|
114
|
-
| --- | --- |
|
|
115
|
-
| `polish.evidence_missing`, `polish.report_missing` | Polish was never recorded
|
|
116
|
-
| `polish.hidden_eager_media.no_capturable_routes` |
|
|
117
|
-
| `polish.hidden_eager_media.capture_malformed` |
|
|
118
|
-
| Template-residue severity | With `theme_gate.nothing_generatable`,
|
|
113
|
+
| Check | Local loopback preview | Hosted preview |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `polish.evidence_missing`, `polish.report_missing` | Carried when Polish was never recorded. | Carried only with the all-template `no_capturable_routes` checkpoint. |
|
|
116
|
+
| `polish.hidden_eager_media.no_capturable_routes` | Carried when every mapped page is template stock (`skip_reason`). | Carried when every mapped page is template stock and QA uses the packet's non-loopback `deploy.preview_url`, distinct from `deploy.production_url` by origin or QA-resolved campaign route path. |
|
|
117
|
+
| `polish.hidden_eager_media.capture_malformed` | Carried only when no page-load capture was recorded at all. | Blocked. |
|
|
118
|
+
| Template-residue severity | With `theme_gate.nothing_generatable`, residue findings warn. | Unchanged. |
|
|
119
119
|
|
|
120
120
|
A carried-forward gate has status `carried_forward`. Doctor reports it as a
|
|
121
|
-
warning starting "Carried forward on the local preview"; `next` moves past
|
|
122
|
-
polish to deploy and QA; QA records it as a `warn` row, so the verdict is at
|
|
121
|
+
warning starting "Carried forward on the local preview" or "Carried forward on the hosted preview"; `next` moves past
|
|
122
|
+
polish to deploy and QA, and the recorded all-template hosted preview satisfies deploy without a deploy record; QA records it as a `warn` row, so the verdict is at
|
|
123
123
|
best `ready_with_exceptions`. The evidence is reported as missing, never as
|
|
124
|
-
passed.
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
passed. On a hosted all-template preview, first record an unset preview URL
|
|
125
|
+
with `campaigns-os qa policy set --packet <packet> --preview-url <url>`, then
|
|
126
|
+
run `campaigns-os next --packet <packet>`. Run `campaigns-os next qa --packet <packet>`
|
|
127
|
+
then `campaigns-os qa run --packet <packet> --base-url <preview-url> --browser --test-order common` directly. After QA records the current-build verdict, run `campaigns-os next --packet <packet>` for closeout;
|
|
128
|
+
`record deploy` only accepts loopback local-serve URLs; a pending hosted deploy
|
|
129
|
+
record does not suppress these QA actions when the all-template checkpoint is
|
|
130
|
+
carried forward. The hosted exception
|
|
131
|
+
requires a distinct packet `deploy.preview_url`; a query or fragment on the production page does not count as distinct. QA with `--base-url` equal to
|
|
132
|
+
the packet's `deploy.production_url` stays blocked. A `local-serve` packet
|
|
133
|
+
served from any other host also gets strict gates. `record polish` and the
|
|
134
|
+
waiver commands keep them strict. Progress
|
|
127
135
|
snapshots record a carried-forward gate as `not_applicable`, because their
|
|
128
136
|
schema has no carried-forward state.
|
|
129
137
|
|
|
@@ -179,8 +187,9 @@ line and the printed per-URL rows say which URLs those were rather than leaving
|
|
|
179
187
|
an operator to infer it from `counts.unreachable`. It is `null` only when every
|
|
180
188
|
probed URL resolved, or when nothing was probed at all.
|
|
181
189
|
|
|
182
|
-
Resolve appends `campaign.public_route_slug`
|
|
183
|
-
|
|
190
|
+
Resolve appends `campaign.public_route_slug` to the preview base for `base_url`
|
|
191
|
+
and page URLs, including when `campaign.route_root` is `/`. A subpath on
|
|
192
|
+
`--base-url` stays ahead of the slug. When
|
|
184
193
|
every derived route is dead, one extra probe of the host without that slug
|
|
185
194
|
separates the two causes and says which it found:
|
|
186
195
|
`route_probe.route_root_mismatch` (the host serves the campaign under a
|
|
@@ -308,6 +317,21 @@ for that entry URL. If Entry URLs are empty, still point at a deleted preview, o
|
|
|
308
317
|
fail their own HTTP assertion, fix `--base-url` or the packet deploy URL before
|
|
309
318
|
continuing.
|
|
310
319
|
|
|
320
|
+
For an `is_entry` page with `page_url` empty, `/`, `/<slug>/`, or `<slug>`, `qa resolve` uses that page's
|
|
321
|
+
explicit Page Kit permalink first, then the campaign root when
|
|
322
|
+
`_site/<slug>/index.html` exists. If neither applies and Page Kit emitted the
|
|
323
|
+
entry at its recorded page-file route, the printed Entry URL uses that locally
|
|
324
|
+
emitted route. Doctor warns that the Map still points at the unserved root;
|
|
325
|
+
add a redirect from the root to the emitted route or set `permalink: /<slug>/`
|
|
326
|
+
on the entry page. A non-root permalink also gets the warning when root output
|
|
327
|
+
is absent; QA keeps that permalink as its Entry URL. A present root output
|
|
328
|
+
suppresses the warning. The default
|
|
329
|
+
probe checks the printed URL on the host. For either `campaign.route_root: "/"`
|
|
330
|
+
or the default route root, the file route is joined under `base_url` as
|
|
331
|
+
`/<slug>/<file-route>/` (after any preview subpath). The root output, when
|
|
332
|
+
present, uses `base_url` itself. The route-root declaration still determines
|
|
333
|
+
the public route named in doctor's unserved-root warning.
|
|
334
|
+
|
|
311
335
|
`--base-url` can be either the deploy host or the campaign root. If the Build Packet says `campaign.public_route_slug = "roadside-ready"`, both of these resolve pages under `/roadside-ready/`:
|
|
312
336
|
|
|
313
337
|
```bash
|
|
@@ -341,6 +365,24 @@ npm run campaigns-os -- qa run \
|
|
|
341
365
|
|
|
342
366
|
The runner fetches deployed pages, checks route availability, verifies CampaignSpec `sdk_hints.meta_tags` (a key the Campaign Cart SDK does not read, `next-currency` or `next-predictive-address` from `src/sdk-meta-tags.mjs`, is a `warn` row at severity `warn` carrying the shared note, never `manual_review` and never a blocker, whether or not the tag rendered; doctor reports the same key as `sdk_hints.meta_tags.ignored_by_sdk`), writes a local verdict JSON under `<target-repo>/qa-output/<qa-storage-key>/<run-id>.json` (the packet's `assembly.target_repo`, else the packet's directory; `--output-dir` overrides it, and a packet-less run uses `qa-output/` under the current directory), and returns exit code `4` when the verdict is blocked. The storage key is the Map ID for saved-Map QA and `local-spec-<local_spec_id>` for local-spec packet QA. The target's managed ignore block lists `qa-output/`, because full verdicts carry live storefront URLs; the committed form is the `.campaign-runtime/qa-verdict.json` projection.
|
|
343
367
|
|
|
368
|
+
`--currency <code>` proves one currency per run: the runner adds
|
|
369
|
+
`?currency=<CODE>` to each funnel's entry URL (the HTTP fetch, the browser load
|
|
370
|
+
and `entry_urls`) and to the first page load of each test-order path (the
|
|
371
|
+
checkout probe, or the entry page or checkout when that load comes first),
|
|
372
|
+
keeping any query or tracking params already there. Campaign Cart keeps the URL
|
|
373
|
+
currency for the rest of that tab's session, so the runner adds it to no other
|
|
374
|
+
load (upsell pages and the receipt reload in recovery go without it); a later
|
|
375
|
+
load of the entry URL still carries it, because that URL is tagged. The flag takes one code per run (the
|
|
376
|
+
shared parser keeps the last one if it is given twice). The code must be three
|
|
377
|
+
letters (upper-cased: `gbp` runs as `GBP`); anything else is refused before any
|
|
378
|
+
request. A campaign set up for three currencies takes three runs. `qa parity`
|
|
379
|
+
refuses `--currency`, and so does a run whose orders come from
|
|
380
|
+
`--legacy-api-test-order`, since a direct API order would be placed in the
|
|
381
|
+
default currency. The persisted verdict redacts every URL query, so the
|
|
382
|
+
currency is recorded as `evidence.currency` on the entry page's
|
|
383
|
+
`http:<page_id>` assertion; an entry URL that does not parse gets no currency,
|
|
384
|
+
and its row records `evidence.currency_not_applied` with the reason instead.
|
|
385
|
+
|
|
344
386
|
### Automatic commercial parity
|
|
345
387
|
|
|
346
388
|
When the CampaignSpec has enabled pages with package rows, the same `qa run`
|
|
@@ -804,7 +846,7 @@ prints the portal link.
|
|
|
804
846
|
The outcome lands on the run's Run Record as the `qa_verdict_publish` block —
|
|
805
847
|
`verdict_run_id`, `publisher` (`qa run` or `qa publish`), `state`
|
|
806
848
|
(`skipped` / `ok` / `failed`), `result` in the remit vocabulary, `base_kind`,
|
|
807
|
-
`published_at` — on the record whose `qa_verdict` artifact references the
|
|
849
|
+
`published_at`, and the nullable publish decision `reason` — on the record whose `qa_verdict` artifact references the
|
|
808
850
|
verdict under the packet's campaign. `qa run` records its own publish (or
|
|
809
851
|
its `--no-post-verdict` skip) the same way when the run session closes, which
|
|
810
852
|
is what `already_published` reads. A stored `ok` is never downgraded: a
|
|
@@ -1183,8 +1225,8 @@ Fixture essentials:
|
|
|
1183
1225
|
|
|
1184
1226
|
Test Orders use **global test cards** that work on any live store and integration.
|
|
1185
1227
|
They **bypass the payment gateway and create no transactions** (and no fulfillment),
|
|
1186
|
-
|
|
1187
|
-
|
|
1228
|
+
but each creates a real order record on the store. Unless the operator has already said test orders are fine for this campaign, ask once, up front, before placing test orders; then proceed. No permission flag,
|
|
1229
|
+
packet policy, or merchant sandbox routing is required. They leave a small,
|
|
1188
1230
|
easy-to-clean footprint: Test orders are deletable in bulk, and the resulting
|
|
1189
1231
|
Customer record is reused (see the test email note below) rather than multiplied.
|
|
1190
1232
|
|
|
@@ -1428,7 +1470,21 @@ timeout still leaves the ladder up to the point of failure. Ladder entries carry
|
|
|
1428
1470
|
object. Evidence is resolved even when the step fails or times out, because the
|
|
1429
1471
|
failing path is the one worth reading.
|
|
1430
1472
|
|
|
1431
|
-
|
|
1473
|
+
Five steps write structured evidence today.
|
|
1474
|
+
|
|
1475
|
+
**Multi-step checkout paths (campaigns-os#641).** The ladder drives the
|
|
1476
|
+
Checkout — the page that takes payment (`type: "checkout"`) — on the tested
|
|
1477
|
+
path, never the first page typed checkout. When `checkout_step` pages lead
|
|
1478
|
+
into it, the runner lands on the first step: through a `select` page ahead of
|
|
1479
|
+
it when there is one (`entered_via_landing` with `control_kind:
|
|
1480
|
+
checkout_button`), otherwise the step fills the cart itself.
|
|
1481
|
+
`customer_fields_filled` fills only the fields that step renders, one
|
|
1482
|
+
`checkout_step_submitted` rung per step follows, and the order is submitted
|
|
1483
|
+
only on the Checkout. With
|
|
1484
|
+
split-test paths in one campaign, each Checkout reached from an entry page is
|
|
1485
|
+
its own path; `tiers` plans read declared tiers and bumps from every page of
|
|
1486
|
+
that path (the select page or first step declares the cart), and coupons from
|
|
1487
|
+
its steps and Checkout.
|
|
1432
1488
|
|
|
1433
1489
|
**`entered_via_landing` — cart entry.** The first rung of the ladder, before
|
|
1434
1490
|
`opened_checkout`. A checkout renders its customer form whether or not the SDK
|
|
@@ -1472,7 +1528,13 @@ checkout — never a receipt or an offer page), navigates there, waits for the
|
|
|
1472
1528
|
SDK, and clicks the cart-entry control: an SDK add-to-cart control
|
|
1473
1529
|
(`[data-next-action="add-to-cart"]`, the only attribute the SDK activates the
|
|
1474
1530
|
feature on), or a link into the checkout URL carrying `?forcePackageId=`,
|
|
1475
|
-
which is what the certified `shop-single-step` landing renders
|
|
1531
|
+
which is what the certified `shop-single-step` landing renders, or, on a
|
|
1532
|
+
page with bundle cards (`[data-next-bundle-card]`, a `select` page), a checkout
|
|
1533
|
+
button: the SDK's `[data-next-action="checkout"]` or a plain link into the
|
|
1534
|
+
checkout URL. There the swap-mode selector already wrote the cart and the SDK
|
|
1535
|
+
persists it, so the runner keeps the pre-selected card, or with
|
|
1536
|
+
`--select-package <ref>` clicks the bundle card carrying that ref first,
|
|
1537
|
+
strictly, before the button. A control
|
|
1476
1538
|
spelled any other way is not a cart entry, so a page that offers nothing else
|
|
1477
1539
|
fails the step by name (`cart_entry_control_missing`) rather than clicking a
|
|
1478
1540
|
control the SDK never wired and waiting out the navigation budget. A visible
|
|
@@ -1490,8 +1552,9 @@ cart away. `opened_checkout` then records the arrival instead of re-opening.
|
|
|
1490
1552
|
|
|
1491
1553
|
Evidence: `landing_url`, `landing_page_id`, `landing_page_type`,
|
|
1492
1554
|
`landing_resolution` (`routes_into_checkout`, `entry_page_fallback`,
|
|
1493
|
-
`first_page_fallback`), `control_text`, `control_kind` (`add_to_cart
|
|
1494
|
-
`checkout_link`), `package_id`, `
|
|
1555
|
+
`first_page_fallback`), `control_text`, `control_kind` (`add_to_cart`,
|
|
1556
|
+
`checkout_link`, or `checkout_button`), `package_id`, `bundle_card_selection`
|
|
1557
|
+
when `--select-package` clicked a bundle card, `sdk_ready`, `arrived_url`, the
|
|
1495
1558
|
`checkout_selection_surface` probe result, `selection_surface_probe`
|
|
1496
1559
|
(`loaded`, `reused`, or `failed`), and `selection_surface_probe_error` when it
|
|
1497
1560
|
failed. The failure codes are
|
|
@@ -1527,6 +1590,21 @@ own 30s default, so a caller-supplied budget only ever tightens the ceiling. A
|
|
|
1527
1590
|
stuck required field fails as that field rather than as an anonymous step
|
|
1528
1591
|
timeout; a slow-but-working funnel waits no longer than it did before.
|
|
1529
1592
|
|
|
1593
|
+
**`checkout_step_submitted` — one rung per `checkout_step`.** On a path whose
|
|
1594
|
+
Checkout is preceded by `checkout_step` pages, each step's rung checks the page
|
|
1595
|
+
is on that step, clicks the submit control of its
|
|
1596
|
+
`form[data-next-checkout-step]` (the SDK validates the step's fields, keeps the
|
|
1597
|
+
answers and navigates), and waits for the page to land on the step's declared
|
|
1598
|
+
next page (`expected_next_url`). It then fills whatever fields the arrived page
|
|
1599
|
+
renders. A step whose form carries no `data-next-checkout-step`, or whose
|
|
1600
|
+
submit never reaches the declared next page (usually a required field the
|
|
1601
|
+
step validates), fails the rung by name; nothing is reserved or submitted, so
|
|
1602
|
+
the creation classifier reads it as `not_created`. Evidence: `step_page_id`,
|
|
1603
|
+
`step_number`, `expected_next_url`, `arrived_url` and the arrived page's
|
|
1604
|
+
`fields` trace. A path that starts on its first step, with no page routing into
|
|
1605
|
+
it, skips `entered_via_landing`: the step page fills the cart and the guard
|
|
1606
|
+
before submit checks it.
|
|
1607
|
+
|
|
1530
1608
|
**`cart_created` — cart-API observation.** The step reports what the cart API
|
|
1531
1609
|
actually returned: the most recent `POST /api/v1/carts/` response's `status`, an
|
|
1532
1610
|
`ok` flag, `line_count` when the response body exposes lines, `response_count`,
|
|
@@ -1566,17 +1644,43 @@ Two flags target funnels the default-tier drive cannot prove:
|
|
|
1566
1644
|
|
|
1567
1645
|
- `--select-package <ref[:qty],...>` — **strict** package/bundle card selection.
|
|
1568
1646
|
Each ref is matched against rendered selector/bundle cards
|
|
1569
|
-
(`[data-next-package-id]`, `[data-next-bundle-card][data-next-bundle-id]
|
|
1570
|
-
|
|
1647
|
+
(`[data-next-package-id]`, `[data-next-bundle-card][data-next-bundle-id]`, and
|
|
1648
|
+
the package a bundle card's `data-next-bundle-items` declares, so
|
|
1649
|
+
`--select-package 1:2` finds the card declaring
|
|
1650
|
+
`[{"packageId":1,"quantity":2}]` whether or not an inner node repeats the
|
|
1651
|
+
package id) and clicked. An explicit quantity requires one unambiguous rendered card whose
|
|
1571
1652
|
`data-next-bundle-items` composition contains exactly that package and
|
|
1572
|
-
quantity; package identity alone is not enough.
|
|
1653
|
+
quantity; package identity alone is not enough. A card declaring several
|
|
1654
|
+
items matches by any one of them (`4:1` finds a kit declaring
|
|
1655
|
+
`[{"packageId":1,"quantity":2},{"packageId":4,"quantity":1}]`); a card
|
|
1656
|
+
declaring the item alone is preferred over one declaring it among others. The card must then expose a
|
|
1573
1657
|
selected-state marker (`data-next-selected="true"` / `.next-selected`) after
|
|
1574
1658
|
the click. A missing card, ambiguous composition, wrong quantity, or
|
|
1575
1659
|
unverifiable/refused selection **fails the `selected_bundle` step** instead
|
|
1576
1660
|
of silently driving the pre-selected default tier. A bundle ref remains an
|
|
1577
1661
|
authoritative identity when exactly one rendered card declares it. Use this
|
|
1578
1662
|
flag to traverse non-default tiers of a multi-tier selector. `--cart` remains
|
|
1579
|
-
the best-effort variant.
|
|
1663
|
+
the best-effort variant. For a package that bundle cards declare, `--cart
|
|
1664
|
+
<ref:qty>` clicks the card whose items carry that package at that quantity,
|
|
1665
|
+
and clicks nothing when the selected card already does; refs no bundle card
|
|
1666
|
+
declares (an order bump) are clicked as before. The cards are read again
|
|
1667
|
+
before each ref, so a selector that re-renders after a click is clicked as
|
|
1668
|
+
it now stands. Beside `--select-package`,
|
|
1669
|
+
`--cart` never clicks another card in the chosen card's selector group, and
|
|
1670
|
+
a card in no recognised selector container
|
|
1671
|
+
(`[data-next-bundle-selector]`, `[data-next-selector-id]`,
|
|
1672
|
+
`[data-next-cart-selector]`) counts as in that group: a
|
|
1673
|
+
ref the chosen card carries is left as it is, a ref an order bump or a card
|
|
1674
|
+
in another selector group carries is applied through that control, and any
|
|
1675
|
+
other ref is left unapplied and named in the `selected_bundle` detail
|
|
1676
|
+
(`--cart 3:1 not applied: ...`). The display-parity row reconciles the order
|
|
1677
|
+
against the packages and quantities every selected bundle card declares, not
|
|
1678
|
+
its `data-next-bundle-id`. Without `--select-package` it also fails when an
|
|
1679
|
+
explicit `--cart <ref:qty>` is not in the order at that quantity: a
|
|
1680
|
+
different quantity (`--cart 1:2` that ordered one unit) or no line for the
|
|
1681
|
+
package at all (`--cart 1:2,3:1` on a swap-mode selector that kept only the
|
|
1682
|
+
`3:1` card); beside `--select-package` it judges the order against the card
|
|
1683
|
+
that flag chose.
|
|
1580
1684
|
- `--apply-coupon <code>` — types the code into the rendered coupon/promo input
|
|
1581
1685
|
(the SDK's `[data-next-checkout-field="coupon"]` or
|
|
1582
1686
|
`input[data-next-coupon="input"]`, then hand-rolled `coupon`/`voucher`/`promo`
|
|
@@ -1709,7 +1813,7 @@ browser launch, prints the planned count, lists the planned paths (up to 40 ids;
|
|
|
1709
1813
|
past that the remainder is counted, never cut silently, and `--select-package
|
|
1710
1814
|
<ref[:qty]>` lists one tier's paths), and names the exact `--max-test-orders <count>` raise. For example, a linear three-offer graph has
|
|
1711
1815
|
eight terminal paths plus the checkout baseline, so it requires
|
|
1712
|
-
`--max-test-orders 9`.
|
|
1816
|
+
`--max-test-orders 9`. Unless the operator has already said test orders are fine for this campaign, ask once, up front, before placing these real store order records; then proceed. The command has no permission flag.
|
|
1713
1817
|
|
|
1714
1818
|
`--max-test-orders` bounds **planned paths**, which is not the same as real
|
|
1715
1819
|
purchases. `--max-order-creations` bounds **actual order creations**, defaults to
|
|
@@ -1849,7 +1953,7 @@ and hardcoded phone numbers that differ from CampaignSpec `campaign.store_phone`
|
|
|
1849
1953
|
If a static claim is intentionally preserved, wrap it in an element with
|
|
1850
1954
|
`data-skip-market-lint="true"` and record why in the assembly report.
|
|
1851
1955
|
|
|
1852
|
-
Test orders
|
|
1956
|
+
Test orders need no allowlist and no permission flag; unless the operator has already said test orders are fine for this campaign, ask once, up front, before the first one (each is a real order record). A separate concern is the
|
|
1853
1957
|
**SDK origin allowlist**: the Campaign Cart SDK must be allowed to load on the
|
|
1854
1958
|
tested origin for the campaign API key, or runtime checks (and the live page
|
|
1855
1959
|
itself) may not initialize. Localhost on any port is globally available as a
|
|
@@ -1864,8 +1968,8 @@ npm run campaigns-os -- qa policy set \
|
|
|
1864
1968
|
--allowed-domains-confirmed true
|
|
1865
1969
|
```
|
|
1866
1970
|
|
|
1867
|
-
There is no permission flag for test orders
|
|
1868
|
-
<mode
|
|
1971
|
+
There is no permission flag for test orders; unless the operator has already said test orders are fine for this campaign, ask once, up front, before placing these real store order records, then run `--test-order
|
|
1972
|
+
<mode>`. The former `--test-orders-allowed` /
|
|
1869
1973
|
`--sandbox-test-card-confirmed` flags and the `qa.test_orders_allowed` /
|
|
1870
1974
|
`qa.sandbox_test_card_confirmed` packet fields they set were removed in
|
|
1871
1975
|
supported surface 1.28.0: nothing had read their values since the gate itself
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
How a checkout of this repository at one commit becomes a usable installed runtime, and how a consumer decides whether a prepared one is still trustworthy. Everything below is generated from `contracts/runtime-recipe.campaigns-os-node-v1.json`, which is the only authority for these values.
|
|
10
10
|
|
|
11
|
-
Recipe kind `campaigns-os-node-v1`, revision `1.0.
|
|
11
|
+
Recipe kind `campaigns-os-node-v1`, revision `1.0.3`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.57.0`.
|
|
12
12
|
|
|
13
13
|
## What this is
|
|
14
14
|
|
|
@@ -81,9 +81,10 @@ Not redundant with the install step. Because install runs with lifecycle scripts
|
|
|
81
81
|
|
|
82
82
|
The complete input set, enumerated explicitly rather than globbed. The compiler's configured include globs are NOT the input set: two root modules enter the compilation transitively through imports from the entry module and are emitted, so a fingerprint derived from the globs would cover 36 of the 38 compiled sources and miss one of the larger emitted surfaces. Test fixtures and the package's own tests are not inputs; nothing under them is emitted. A checker resolves the compiler's actual file list and asserts it equals this enumeration, so this list cannot rot silently.
|
|
83
83
|
|
|
84
|
-
Fingerprint algorithm `sha256`, over
|
|
84
|
+
Fingerprint algorithm `sha256`, over 47 enumerated files:
|
|
85
85
|
|
|
86
86
|
- `campaign-spec/analytics-vocabulary.ts`
|
|
87
|
+
- `campaign-spec/checkout-flow.ts`
|
|
87
88
|
- `campaign-spec/index.ts`
|
|
88
89
|
- `campaign-spec/normalize.ts`
|
|
89
90
|
- `campaign-spec/package.json`
|
|
@@ -91,7 +92,9 @@ Fingerprint algorithm `sha256`, over 42 enumerated files:
|
|
|
91
92
|
- `campaign-spec/rules/analytics-contract-shape.ts`
|
|
92
93
|
- `campaign-spec/rules/assembly-hints-shape.ts`
|
|
93
94
|
- `campaign-spec/rules/campaign-metadata.ts`
|
|
95
|
+
- `campaign-spec/rules/checkout-forward-target.ts`
|
|
94
96
|
- `campaign-spec/rules/checkout-has-success-url.ts`
|
|
97
|
+
- `campaign-spec/rules/checkout-step-reaches-checkout.ts`
|
|
95
98
|
- `campaign-spec/rules/cycle-detection.ts`
|
|
96
99
|
- `campaign-spec/rules/design-source-shape.ts`
|
|
97
100
|
- `campaign-spec/rules/downsell-without-upsell.ts`
|
|
@@ -102,9 +105,11 @@ Fingerprint algorithm `sha256`, over 42 enumerated files:
|
|
|
102
105
|
- `campaign-spec/rules/funnel-weight-sum.ts`
|
|
103
106
|
- `campaign-spec/rules/index.ts`
|
|
104
107
|
- `campaign-spec/rules/offer-ref-integrity.ts`
|
|
108
|
+
- `campaign-spec/rules/one-checkout-per-path.ts`
|
|
105
109
|
- `campaign-spec/rules/package-pricing-sanity.ts`
|
|
106
110
|
- `campaign-spec/rules/page-count.ts`
|
|
107
111
|
- `campaign-spec/rules/page-id-uniqueness.ts`
|
|
112
|
+
- `campaign-spec/rules/pre-payment-forward-target.ts`
|
|
108
113
|
- `campaign-spec/rules/promo-code-input-validation.ts`
|
|
109
114
|
- `campaign-spec/rules/promo-codes-shape.ts`
|
|
110
115
|
- `campaign-spec/rules/route-field-ignored-for-page-type.ts`
|
|
@@ -132,7 +137,7 @@ The output directory is a build product. It is untracked and git-ignored, no com
|
|
|
132
137
|
|
|
133
138
|
Directory `campaign-spec/dist`. Committed: `false`. Type entry `campaign-spec/dist/index.d.ts`.
|
|
134
139
|
|
|
135
|
-
Expected inventory is derived, never listed twice. For every enumerated input under module_source_root whose path ends in .ts, the build is expected to emit campaign-spec/dist/<path relative to module_source_root, with .ts replaced> once per entry in emitted_extensions. The expected inventory is exactly that set: nothing missing, nothing extra. Emitted extensions: `.js`, `.d.ts`. At this revision that derivation yields
|
|
140
|
+
Expected inventory is derived, never listed twice. For every enumerated input under module_source_root whose path ends in .ts, the build is expected to emit campaign-spec/dist/<path relative to module_source_root, with .ts replaced> once per entry in emitted_extensions. The expected inventory is exactly that set: nothing missing, nothing extra. Emitted extensions: `.js`, `.d.ts`. At this revision that derivation yields 86 files.
|
|
136
141
|
|
|
137
142
|
### Mandatory checks
|
|
138
143
|
|
package/docs/skills-revision.md
CHANGED
|
@@ -16,7 +16,7 @@ that the copy on disk moved.
|
|
|
16
16
|
`skills.json` carries one top-level field:
|
|
17
17
|
|
|
18
18
|
```json
|
|
19
|
-
"bundle_revision": "1.
|
|
19
|
+
"bundle_revision": "1.57.0+skills.1"
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
The spelling is `<package version>+skills.<n>`:
|
|
@@ -25,7 +25,7 @@ The spelling is `<package version>+skills.<n>`:
|
|
|
25
25
|
skills ship with (`check-skill-versions.mjs` fails if the two disagree);
|
|
26
26
|
- `<n>` is a plain counter, not a semver component. It says "this is the *n*th
|
|
27
27
|
skill-text revision published against that package version" and it **resets
|
|
28
|
-
with the prefix**. `1.
|
|
28
|
+
with the prefix**. `1.57.0+skills.1` is therefore ahead of `1.40.0+skills.7`.
|
|
29
29
|
|
|
30
30
|
It is one identity for the bundle as a whole, on purpose. Per-skill versions
|
|
31
31
|
still exist and still gate per-skill changes, but an agent that loaded one skill
|
|
@@ -37,7 +37,7 @@ The first body line of every bundled `SKILL.md`, immediately after the
|
|
|
37
37
|
frontmatter, is exactly:
|
|
38
38
|
|
|
39
39
|
```
|
|
40
|
-
Bundle revision: 1.
|
|
40
|
+
Bundle revision: 1.57.0+skills.1
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
followed by a short paragraph telling the agent to run the check below at the
|
|
@@ -48,7 +48,7 @@ text the agent is actually reading, not from a file it would have to go and open
|
|
|
48
48
|
## The check
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
npx --no-install campaigns-os tooling status --skills-revision 1.
|
|
51
|
+
npx --no-install campaigns-os tooling status --skills-revision 1.57.0+skills.1
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
The value is compared against the bundle revision of the **CLI the command runs
|
|
@@ -90,20 +90,20 @@ reports the choice as `skills.scope` (`requested`, `installed_platforms`, or
|
|
|
90
90
|
"revision_check": "match",
|
|
91
91
|
"skills_revision": {
|
|
92
92
|
"status": "match",
|
|
93
|
-
"requested": "1.
|
|
93
|
+
"requested": "1.57.0+skills.1",
|
|
94
94
|
"spelling": "bundle",
|
|
95
|
-
"on_disk": "1.
|
|
95
|
+
"on_disk": "1.57.0+skills.1",
|
|
96
96
|
"on_disk_skill": null,
|
|
97
|
-
"message": "match (1.
|
|
97
|
+
"message": "match (1.57.0+skills.1)"
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
The text view prints one named line, as a header above the rest of the status:
|
|
102
102
|
|
|
103
103
|
```
|
|
104
|
-
Skills revision: match (1.
|
|
105
|
-
Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.
|
|
106
|
-
Skills revision: unchecked (on disk 1.
|
|
104
|
+
Skills revision: match (1.57.0+skills.1)
|
|
105
|
+
Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.57.0+skills.1 — start a fresh session
|
|
106
|
+
Skills revision: unchecked (on disk 1.57.0+skills.1)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
`unchecked` is the state when the flag is absent. It is not an error — an
|
|
@@ -13,7 +13,7 @@ implementation detail, however stable it looks.
|
|
|
13
13
|
|
|
14
14
|
| Surface | Contract | Change discipline |
|
|
15
15
|
|---|---|---|
|
|
16
|
-
| `schemas/*.schema.json` (all of them) | The portable contract catalog: CampaignSpec, Design Source Package, Build Packet, Build Context, Assembly Report, Doctor Output, sidecar-bundle conformance, Run Record, Workflow Finding, Build Brief, Source-HTML Manifest, Tooling Orientation, Release Ledger, QA Verdict, the QA Verdict sidecar projection, Runtime Recipe, and the legacy-migration inventory/plan/receipt trio. | Hashed. Any content change requires updating the recorded hash **and** bumping `surface_version` in the same PR. A shape change that alters meaning gets a new schema-version const — one version identifier must never cover two shapes (the 2026-08 assembly-report drift is the incident this rule encodes). Additions to an open `v0` schema are expected and consumers must tolerate unknown fields; the security-sensitive legacy-migration schemas are closed, so additions there require a new lineage. 1.28.0 (RL entry `surface_version: 1.28.0`, breaking) removed the two required Build Packet booleans `qa.test_orders_allowed` and `qa.sandbox_test_card_confirmed` (nothing read them; test orders run from `--test-order <mode>` alone), added `local-serve` to the `deploy.target` enum, and added the optional `remit_result` / `remit_base_kind` fields to the Run Record. 1.30.0 (additive) added the optional `data_layer` record to the QA Verdict's `test_orders[]` entries — the order's `dl_purchase` reading (#325). 1.33.0 (additive) added the optional `qa_verdict_publish` block to the Run Record — which verdict was posted to the QA portal, by `qa run` or `qa publish`, and what the portal answered, in the `remit_result` vocabulary (#328). |
|
|
16
|
+
| `schemas/*.schema.json` (all of them) | The portable contract catalog: CampaignSpec, Design Source Package, Build Packet, Build Context, Assembly Report, Doctor Output, sidecar-bundle conformance, Run Record, Workflow Finding, Build Brief, Source-HTML Manifest, Tooling Orientation, Release Ledger, QA Verdict, the QA Verdict sidecar projection, Runtime Recipe, and the legacy-migration inventory/plan/receipt trio. | Hashed. Any content change requires updating the recorded hash **and** bumping `surface_version` in the same PR. A shape change that alters meaning gets a new schema-version const — one version identifier must never cover two shapes (the 2026-08 assembly-report drift is the incident this rule encodes). Additions to an open `v0` schema are expected and consumers must tolerate unknown fields; the security-sensitive legacy-migration schemas are closed, so additions there require a new lineage. 1.28.0 (RL entry `surface_version: 1.28.0`, breaking) removed the two required Build Packet booleans `qa.test_orders_allowed` and `qa.sandbox_test_card_confirmed` (nothing read them; test orders run from `--test-order <mode>` alone), added `local-serve` to the `deploy.target` enum, and added the optional `remit_result` / `remit_base_kind` fields to the Run Record. 1.30.0 (additive) added the optional `data_layer` record to the QA Verdict's `test_orders[]` entries — the order's `dl_purchase` reading (#325). 1.33.0 (additive) added the optional `qa_verdict_publish` block to the Run Record — which verdict was posted to the QA portal, by `qa run` or `qa publish`, and what the portal answered, in the `remit_result` vocabulary (#328). 1.55.0 (breaking) adds the optional publish decision `reason`, record `closed_by` closer, QA observation `verdict_run_id`, `build_fingerprint`, and `stale`, lifecycle `stages[].wait_ms` and `stages[].finding_codes`, and run-level `needs_input_count` and `failure_count` while removing the unused `agent_usage` field from the same v0 Run Record shape. 1.56.0 (breaking) adds `schemas/campaign-spec.v5.schema.json` (CampaignSpec `5.0`: the `checkout_step` page type, and `checkout` meaning the page that takes payment; 4.2/4.3 specs still validate against the v4 schema and are upgraded on read) and the `success_url` and `step_number` keys to the closed `page_kit.frontmatter` object of the Build Packet and Build Context v0 schemas, so a validator holding the 1.55.0 schemas refuses a packet that carries them (campaigns-os#641). |
|
|
17
17
|
| CLI commands: `start`, `prepare-build`, `build`, `polish`, `checkpoint`, `page-kit`, `spec`, `doctor`, `bundle`, `next`, `theme`, `tooling`, `install-skills`, `install-agent-context`, `validate-assembly-report`, `telemetry`, `standardize`, `qa`, `findings`, `run-record`, `run`, `sdk`, `demo`, `login`, `logout` | Scriptable entry points. The founding list (1.0.0) was the argv surface Campaigns Agent's remit fixture pins; 1.25.0 adds the partner entry path the public guides instruct — `install-skills` (skill install), `tooling status` (preflight), `next --packet` (the runtime cursor) — plus `theme`, `install-agent-context`, `validate-assembly-report`, and `telemetry`, since consent is part of the partner contract. `validate-build-packet`, formerly an undocumented and unsupported alias of `doctor`, was removed in 1.25.0+agent.7 (RL-0036) and now gets the unknown-command error; use `doctor`. `standardization-report`, a supported but redundant second spelling of `standardize` — same flags, same output, same exit codes — was removed in 1.27.0 and now gets the same unknown-command error; use `standardize`. Removing a supported command is a breaking change and is why that release moved the minor. `bundle check` validates the canonical JSON readback set and makes QA required only under `--require-qa`. `polish capture` owns page-load evidence production. `checkpoint waive` currently accepts five registered gates: `page_kit.store_profile`, `page_kit.sdk_version`, `polish.hidden_eager_media`, `built_output.upsell_selector_scope`, and `source_html.producer_provenance` (per page, with `--page <page_id>`). `page-kit sync` (added in 1.29.0) writes the CampaignSpec's Store Profile fields and SDK pin into the target's `_data/campaigns.json` entry — the repair the first two gates name — and is the only `page-kit` subcommand. `spec derive` (added in 1.31.0) is the reverse write for the repo-derived field class (#432): the target's SDK pin, page routes and analytics ids into the packet's local CampaignSpec; it is the only `spec` subcommand, and the `page_kit.sdk_version.repo_newer` advisory names it; its `--write-map` flag (1.33.0+agent.2) also records the derived pin in the saved Map's Build hints field through the proxy Worker, never moving a Map pin backwards. `qa publish` (added in 1.33.0) posts an already-stored verdict to the QA portal without a re-run or an order, refusing a stale `spec_hash` or a verdict its Run Record already records as published (#328). Within Polish, only the broader Source Freshness waiver remains on its existing report lane; theme and QA decisions also retain their existing lanes. | Any change to this list — adding, renaming, or removing a command — bumps `surface_version` in the same PR, and since 1.25.0 `check-supported-surface.mjs --base` enforces that (before, only hashed and named entries owed a bump, so `checkpoint` landed unbumped). Subcommands, registered gates, and flags may grow freely beneath a listed command. Removing a listed command from dispatch fails the gate outright. Do not infer support for an unregistered checkpoint from the top-level command. |
|
|
18
18
|
| `sdk storage-check` and `docs/sdk-storage-compatibility.md` | Read-only AST compatibility report for explicitly scoped tracked campaign HTML/JS before an SDK bump; consumes the supplied SDK-owned manifest and records its SHA-256 and verified or unverified local Git provenance. Incompatible and unknown findings exit 2; a clean scan is static source evidence only. | Additive supported CLI and named documentation; no automatic merchant integration repair. |
|
|
19
19
|
| `sdk repin` | Rewrites the version segment of semver-pinned Campaign Cart loader.js / campaign-cart.css URLs in a static repo (no `_data/campaigns.json`) to a target version, default the SDK support policy's `preferred_minimum`. It previews by default. `--apply` writes the files and the change record `.campaign-runtime/sdk-repin.json` (`campaigns-os-sdk-repin/v0`). `@latest`, `@main`, commit and prerelease refs, and pins at or above the target, are reported and never rewritten. A page-kit repo is refused with exit 2. Usage is in `docs/sdk-storage-compatibility.md`. | Additive supported subcommand under the listed `sdk` command. It never changes anything in a URL except the version segment. |
|
package/docs/versioning.md
CHANGED
|
@@ -23,7 +23,10 @@ This repo uses independent compatibility versions:
|
|
|
23
23
|
- QA Verdict sidecar projection: same `"1.0"` literal, projection guarantees in
|
|
24
24
|
`schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json` (one contract, two
|
|
25
25
|
schema files — the sidecar is an allowlist projection, never a second lineage)
|
|
26
|
-
- CampaignSpec: `
|
|
26
|
+
- CampaignSpec: `5.0` (JSON Schema: `schemas/campaign-spec.v5.schema.json`), with
|
|
27
|
+
`4.2`–`4.3` still read (`schemas/campaign-spec.v4.schema.json`) and upgraded on
|
|
28
|
+
read: each `checkout` page whose resolved forward target is another `checkout`
|
|
29
|
+
becomes `checkout_step`
|
|
27
30
|
- starter-template agent contract: `1`
|
|
28
31
|
- commerce surface catalog: `2`
|
|
29
32
|
- Tooling Orientation: `campaigns-os-tooling-orientation/v1`
|
|
@@ -51,7 +51,8 @@ own schemas and evolve independently). Instead it carries:
|
|
|
51
51
|
|
|
52
52
|
- **Stable envelope** — `schema_version` (`campaigns-os-run-record/v0`),
|
|
53
53
|
`run_id`, package version, the command that ran, an `argv` *shape* (flag names
|
|
54
|
-
present, not raw values), `created_at`, consent state,
|
|
54
|
+
present, not raw values), `created_at`, consent state, remit status, and
|
|
55
|
+
optional `closed_by` (`run_end`, `qa_auto_end`, `stale_sweep`, or `manual`).
|
|
55
56
|
- **Run identity** — `map_id`, `campaign_slug`, `template_family`,
|
|
56
57
|
`entry_point_shape`. (Best-effort; missing identity never blocks capture.)
|
|
57
58
|
- **Source artifact refs** — for the Build Packet, Build Context, Assembly
|
|
@@ -64,6 +65,17 @@ own schemas and evolve independently). Instead it carries:
|
|
|
64
65
|
this run.
|
|
65
66
|
- **Findings snapshot** — this run's Workflow Findings (see channel below).
|
|
66
67
|
|
|
68
|
+
When a QA verdict is present, `observations.qa.verdict_run_id` names the same
|
|
69
|
+
verdict that supplies `disposition`. `build_fingerprint` is the QA stage's
|
|
70
|
+
recorded build fingerprint only when the Assembly Report binds that stage to
|
|
71
|
+
the same verdict; otherwise it is `null`. `stale` compares that fingerprint
|
|
72
|
+
with the Assembly Report's recorded build fingerprint using the same comparison
|
|
73
|
+
as `next`: `true` when they differ, `false` when they match, and `null` when
|
|
74
|
+
either fingerprint is unknown. A rebuild without `record build` therefore reads
|
|
75
|
+
`stale: false` when the recorded fingerprints still match; doctor's
|
|
76
|
+
`built_output.fingerprint_stale` checks the output on disk. All three fields
|
|
77
|
+
are optional for older records; without a verdict, `observations.qa` is absent.
|
|
78
|
+
|
|
67
79
|
### Run identity
|
|
68
80
|
|
|
69
81
|
A single canonical `campaigns_os_run_id` is minted at the run boundary and
|
|
@@ -78,6 +90,45 @@ Stage timings and repair-loop count are captured from the command lifecycle
|
|
|
78
90
|
journal when a run session or explicit lifecycle journal is active. They remain
|
|
79
91
|
best-effort signal: telemetry records the commands Campaigns OS can observe, not
|
|
80
92
|
every thought, browser click, or external editor action in an agent session.
|
|
93
|
+
Each new journal entry records `wait_ms` as non-negative whole milliseconds
|
|
94
|
+
blocked on terminal input; an invocation without a prompt records 0. Legacy
|
|
95
|
+
entries may omit it or hold null, and Run Record stages preserve unknown wait
|
|
96
|
+
as absent or null rather than inventing 0. `duration_ms` still includes prompt
|
|
97
|
+
wait, so observed command work is `duration_ms - wait_ms` when wait is known. Tier-2 phases carry their
|
|
98
|
+
own measured wait when the prompt occurs while that phase is active; the journal
|
|
99
|
+
also retains the invocation total. Nested prompt hooks count their outer wait
|
|
100
|
+
interval once. Authentication prompts are outside lifecycle capture by policy.
|
|
101
|
+
|
|
102
|
+
On a non-zero doctor or QA exit, optional `finding_codes` is an array of at most
|
|
103
|
+
five distinct string codes in first-seen order; null is invalid. Codes belong
|
|
104
|
+
to the phase active when they were recorded. Codes recorded outside any phase,
|
|
105
|
+
and codes in a legacy entry, are invocation-level only; aggregation assigns them
|
|
106
|
+
to the entry's last phase. A successful stage has no finding codes. The optional run-level
|
|
107
|
+
`needs_input_count` and `failure_count` are non-negative integer counts; null is
|
|
108
|
+
invalid and absence means a legacy record did not report the count.
|
|
109
|
+
`needs_input_count` counts journal entries with exit 2 and `failure_count`
|
|
110
|
+
counts entries with any other non-zero exit. Current command
|
|
111
|
+
paths use 0 for success, 1 for operational errors, 2 for missing or blocked
|
|
112
|
+
inputs, and 4 for blocked QA; the other-non-zero rule also covers a future
|
|
113
|
+
status without reclassifying exit 2. Both counts are per command invocation,
|
|
114
|
+
including a command that emits multiple Tier-2 stages. `repair_loop_count`
|
|
115
|
+
continues to count repeated commands plus explicit repair-loop hooks.
|
|
116
|
+
|
|
117
|
+
| Terminal input site | Wait hook | Capture context |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| Consent question (`src/consent.mjs`) | Around the injectable `ask` call | Active command, including `run-record` |
|
|
120
|
+
| Store question (`src/login.mjs`) | Around readline `question` | Authentication runs outside lifecycle capture |
|
|
121
|
+
| Finding stage, kind, summary, details (`src/cli.mjs`) | Around each readline `question` | Active `findings add` command; omitted fields do not prompt |
|
|
122
|
+
|
|
123
|
+
| Non-zero finding source | Codes recorded |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `doctor` | `result.errors[].code` before rendering and exit 2 |
|
|
126
|
+
| `start` and `build` | Their embedded doctor `errors[].code` before the prepare result sets exit 2 |
|
|
127
|
+
| `qa run` and `qa parity` | Blocked verdict assertions: `evidence.code`, or assertion `id` when no code is present, before returning from QA dispatch with exit 4 |
|
|
128
|
+
|
|
129
|
+
Spec resolution exceptions and prepare-build without a doctor result do not
|
|
130
|
+
expose doctor or QA codes. They keep their existing exit and timing evidence,
|
|
131
|
+
with no invented finding code.
|
|
81
132
|
|
|
82
133
|
### Validation
|
|
83
134
|
|
|
@@ -246,7 +297,11 @@ remit(path, payload, proxyBase) // mirrors qa-node.mjs postVerdict
|
|
|
246
297
|
for a later post of the stored verdict), `attempted` / `ok` / `error` /
|
|
247
298
|
`endpoint` (`/api/qa/verdicts`), a `state` (`skipped` when the run's
|
|
248
299
|
publish was off, `ok`, `failed`), the `result` in the same vocabulary as
|
|
249
|
-
`remit_result`, the `base_kind`,
|
|
300
|
+
`remit_result`, the `base_kind`, `published_at`, and optional `reason`.
|
|
301
|
+
The reason records the actual QA publish decision: `loopback_base_url`,
|
|
302
|
+
`portal_managed_default`, `consent_off`, `flag_opt_out`, `flag_opt_in`,
|
|
303
|
+
`default`, or `local_spec`. A later `qa publish` has no QA run decision and
|
|
304
|
+
records a null reason. `qa run` hands the
|
|
250
305
|
block to the session through its QA attempt, so `run end` and the auto-end
|
|
251
306
|
stamp it; `qa publish` stamps the record whose `qa_verdict` artifact
|
|
252
307
|
references the verdict, reads `state: "ok"` as already published, and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextcommerce/campaigns-os",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.57.0",
|
|
4
4
|
"description": "Toolkit for agent-assisted NEXT campaign builds.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"default": "./src/legacy-migration.mjs"
|
|
45
45
|
},
|
|
46
46
|
"./schemas/campaign-spec.v4.schema.json": "./schemas/campaign-spec.v4.schema.json",
|
|
47
|
+
"./schemas/campaign-spec.v5.schema.json": "./schemas/campaign-spec.v5.schema.json",
|
|
47
48
|
"./schemas/campaigns-os-legacy-migration-inventory.v0.schema.json": "./schemas/campaigns-os-legacy-migration-inventory.v0.schema.json",
|
|
48
49
|
"./schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json": "./schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json",
|
|
49
50
|
"./schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json": "./schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json",
|
|
@@ -66,6 +67,8 @@
|
|
|
66
67
|
"check:spec": "node --test \"campaign-spec/test/**/*.test.ts\"",
|
|
67
68
|
"check": "npm run check:provenance && npm run check:workflows && npm run check:changelog-structure && npm run build:spec && npm run check:tests && npm run check:spec && npm run check:fixtures && npm run check:legacy-migration && npm run check:spec-conformance && npm run check:private-strings && npm run check:template-doctrine && npm run check:slot-manifest && npm run check:skill-versions && npm run check:cart-readiness && npm run check:sidecar-bundle && npm run check:supported-surface && npm run check:effects && npm run check:release-ledger && npm run check:runtime-recipe && npm run check:orientation-docs && npm run check:runtime-docs && npm run check:pack -- --skip-prepare",
|
|
68
69
|
"check:tests": "node ./scripts/check-tests.mjs",
|
|
70
|
+
"check:fast": "node ./scripts/check-fast.mjs",
|
|
71
|
+
"hooks:install": "node ./scripts/install-git-hooks.mjs",
|
|
69
72
|
"check:legacy-migration": "node ./scripts/check-legacy-migration.mjs",
|
|
70
73
|
"check:fixtures": "node ./scripts/check-fixtures.mjs",
|
|
71
74
|
"check:spec-conformance": "node ./scripts/check-campaign-spec-conformance.mjs",
|
|
@@ -191,7 +191,9 @@
|
|
|
191
191
|
},
|
|
192
192
|
"permalink": { "type": "string", "minLength": 1 },
|
|
193
193
|
"next_url": { "type": "string", "minLength": 1 },
|
|
194
|
-
"decline_url": { "type": "string", "minLength": 1 }
|
|
194
|
+
"decline_url": { "type": "string", "minLength": 1 },
|
|
195
|
+
"success_url": { "type": "string", "minLength": 1, "description": "On a page that leads into a Checkout (select, checkout_step, or a landing page whose forward links reach one): that Checkout's post-payment destination, for the next-success-url meta tag the SDK reads for express orders. Never the next step (campaigns-os#641)." },
|
|
196
|
+
"step_number": { "type": "integer", "minimum": 1, "description": "On a checkout_step page: its position among its path's steps, from 1, for the step form's data-next-step-number." }
|
|
195
197
|
}
|
|
196
198
|
}
|
|
197
199
|
},
|
|
@@ -193,7 +193,9 @@
|
|
|
193
193
|
},
|
|
194
194
|
"permalink": { "type": "string", "minLength": 1 },
|
|
195
195
|
"next_url": { "type": "string", "minLength": 1 },
|
|
196
|
-
"decline_url": { "type": "string", "minLength": 1 }
|
|
196
|
+
"decline_url": { "type": "string", "minLength": 1 },
|
|
197
|
+
"success_url": { "type": "string", "minLength": 1, "description": "On a page that leads into a Checkout (select, checkout_step, or a landing page whose forward links reach one): that Checkout's post-payment destination, for the next-success-url meta tag the SDK reads for express orders. Never the next step (campaigns-os#641)." },
|
|
198
|
+
"step_number": { "type": "integer", "minimum": 1, "description": "On a checkout_step page: its position among its path's steps, from 1, for the step form's data-next-step-number." }
|
|
197
199
|
}
|
|
198
200
|
}
|
|
199
201
|
},
|