@nextcommerce/campaigns-os 1.55.0 → 1.59.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 +124 -0
- package/CONTEXT.md +11 -0
- package/campaign-spec/README.md +15 -1
- package/campaign-spec/dist/checkout-flow.d.ts +97 -0
- package/campaign-spec/dist/checkout-flow.js +245 -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/campaign-cart-sdk-support-policy.v0.json +2 -2
- package/contracts/commerce-surface-catalog.json +26 -27
- package/contracts/effects.v1.json +5 -5
- 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 +1137 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +6 -1
- package/contracts/supported-surface.json +9 -5
- package/contracts/template-brand-contract.shared-commerce.v0.json +1 -1
- package/docs/build-packet.md +68 -10
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/qa-and-test-orders.md +124 -22
- 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/package.json +2 -1
- package/schemas/campaign-runtime-build-context.v0.schema.json +4 -1
- package/schemas/campaign-runtime-build-packet.v0.schema.json +4 -1
- package/schemas/campaign-spec.v5.schema.json +586 -0
- 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 +4 -3
- package/skills.json +10 -10
- package/src/adapter-decision-contract.mjs +1 -1
- package/src/build-brief.mjs +14 -5
- package/src/built-site-scope.mjs +91 -0
- package/src/cli-helpers.mjs +129 -0
- package/src/cli.mjs +117 -57
- package/src/commercial-journey.mjs +25 -2
- package/src/design-source-package.mjs +1 -1
- package/src/design-source-publication.mjs +7 -7
- package/src/doctor/checks.mjs +269 -16
- package/src/doctor/inspect.mjs +4 -2
- package/src/doctor/next-step.mjs +18 -9
- package/src/doctor-sidecar.mjs +5 -3
- package/src/gate-actions.mjs +21 -1
- package/src/input-refresh.mjs +6 -6
- package/src/local-preview-policy.mjs +110 -12
- package/src/polish-node.mjs +8 -7
- package/src/qa-browser.mjs +363 -36
- package/src/qa-cart-entry.mjs +183 -14
- package/src/qa-node.mjs +38 -34
- package/src/qa-test-order-topology.mjs +118 -1
- package/src/qa-verdict-discovery.mjs +21 -4
- package/src/qa-verdict.mjs +24 -1
- package/src/runtime-state-ignore.mjs +7 -5
- package/src/sdk-attribute-index.mjs +8 -7
- package/src/source-html-intake.mjs +58 -7
- package/src/stage-ledger.mjs +4 -2
- package/src/stage-record.mjs +203 -36
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"_growth_note": "When a bound below is genuinely reached, the answer is to find out why before raising it. A dependency install that exceeds its bound on a warm machine is a supply-chain change, not a slow morning; an output inventory that exceeds its file or byte bound is a build that went wrong, not a package that grew 50x overnight. Raising a bound is the fallback, it advances recipe_revision, and it owes a release-ledger entry. Widening the accepted npm range follows the same path, and its trigger is external and checkable: widen when a Node release line ships that npm major by default, not when a particular machine happens to have it installed.",
|
|
4
4
|
"schema": "campaigns-os-runtime-recipe/v1",
|
|
5
5
|
"recipe_kind": "campaigns-os-node-v1",
|
|
6
|
-
"recipe_revision": "1.0.
|
|
6
|
+
"recipe_revision": "1.0.3",
|
|
7
7
|
"refusal_reason_code": "runtime_recipe_refused",
|
|
8
8
|
"fail_closed": true,
|
|
9
9
|
"unperformable_check_disposition": "failed",
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
"fingerprint_algorithm": "sha256",
|
|
107
107
|
"files": [
|
|
108
108
|
"campaign-spec/analytics-vocabulary.ts",
|
|
109
|
+
"campaign-spec/checkout-flow.ts",
|
|
109
110
|
"campaign-spec/index.ts",
|
|
110
111
|
"campaign-spec/normalize.ts",
|
|
111
112
|
"campaign-spec/package.json",
|
|
@@ -113,7 +114,9 @@
|
|
|
113
114
|
"campaign-spec/rules/analytics-contract-shape.ts",
|
|
114
115
|
"campaign-spec/rules/assembly-hints-shape.ts",
|
|
115
116
|
"campaign-spec/rules/campaign-metadata.ts",
|
|
117
|
+
"campaign-spec/rules/checkout-forward-target.ts",
|
|
116
118
|
"campaign-spec/rules/checkout-has-success-url.ts",
|
|
119
|
+
"campaign-spec/rules/checkout-step-reaches-checkout.ts",
|
|
117
120
|
"campaign-spec/rules/cycle-detection.ts",
|
|
118
121
|
"campaign-spec/rules/design-source-shape.ts",
|
|
119
122
|
"campaign-spec/rules/downsell-without-upsell.ts",
|
|
@@ -124,9 +127,11 @@
|
|
|
124
127
|
"campaign-spec/rules/funnel-weight-sum.ts",
|
|
125
128
|
"campaign-spec/rules/index.ts",
|
|
126
129
|
"campaign-spec/rules/offer-ref-integrity.ts",
|
|
130
|
+
"campaign-spec/rules/one-checkout-per-path.ts",
|
|
127
131
|
"campaign-spec/rules/package-pricing-sanity.ts",
|
|
128
132
|
"campaign-spec/rules/page-count.ts",
|
|
129
133
|
"campaign-spec/rules/page-id-uniqueness.ts",
|
|
134
|
+
"campaign-spec/rules/pre-payment-forward-target.ts",
|
|
130
135
|
"campaign-spec/rules/promo-code-input-validation.ts",
|
|
131
136
|
"campaign-spec/rules/promo-codes-shape.ts",
|
|
132
137
|
"campaign-spec/rules/route-field-ignored-for-page-type.ts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "The downstream contract manifest. Everything listed here is SUPPORTED SURFACE: consumers (campaigns-agent, campaign-builder, the private ops repo, page-kit campaign repos) may depend on it, and changing it is a deliberate act — hashed entries require a surface_version bump in the same change (check-supported-surface.mjs --base, mirroring the skills.json bump gate), named entries must keep existing at their path, cli_commands must keep resolving in the CLI dispatch, package_exports must stay exported, and every entry must ship in the npm pack (files[] coverage). Anything NOT listed here — src/** internals, scripts/** checkers, examples/**, prompts/**, contracts/** other than this file and the entries named[] below (the orientation contract, the release ledger, and the consumer-facing orientation fixtures) — is implementation: consumers may read it for context but must not build on it, and it can change without notice. Rationale and the compatibility promise: docs/supported-surface.md.",
|
|
3
|
-
"surface_version": "1.
|
|
3
|
+
"surface_version": "1.59.0",
|
|
4
4
|
"package_exports": [
|
|
5
5
|
"./commercial-journey",
|
|
6
6
|
"./commercial-parity",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"./campaign-spec",
|
|
9
9
|
"./legacy-migration",
|
|
10
10
|
"./schemas/campaign-spec.v4.schema.json",
|
|
11
|
+
"./schemas/campaign-spec.v5.schema.json",
|
|
11
12
|
"./schemas/campaigns-os-legacy-migration-inventory.v0.schema.json",
|
|
12
13
|
"./schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json",
|
|
13
14
|
"./schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json",
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
"sha256": "53269453dfdaac4b286124d445b60b0c46205dfbaf8037ac61b30254a32df53a"
|
|
52
53
|
},
|
|
53
54
|
"contracts/runtime-recipe.campaigns-os-node-v1.json": {
|
|
54
|
-
"sha256": "
|
|
55
|
+
"sha256": "ab0537a4756310d3a35b400d3932e46695a820f1b8b398f52f6fd0906098bc1d"
|
|
55
56
|
},
|
|
56
57
|
"schemas/campaign-build-brief.v1.schema.json": {
|
|
57
58
|
"sha256": "d524003184131398dc15fdb84385af1be0de0c69d6c5ede7278689a5b6101b81"
|
|
@@ -63,14 +64,17 @@
|
|
|
63
64
|
"sha256": "625a6f2ed6bee726f9cddad750e64a54a7e03744c777f5a70afe4a39afdd9848"
|
|
64
65
|
},
|
|
65
66
|
"schemas/campaign-runtime-build-context.v0.schema.json": {
|
|
66
|
-
"sha256": "
|
|
67
|
+
"sha256": "2aa8f652f6bb602cdbadada34b5793da1bb8842223fcae2fd24700aac14f4de6"
|
|
67
68
|
},
|
|
68
69
|
"schemas/campaign-runtime-build-packet.v0.schema.json": {
|
|
69
|
-
"sha256": "
|
|
70
|
+
"sha256": "e57d31f190a7ddfbf63afb39bc67762a09dff25707c841e84a4e1c6ee9afd9ed"
|
|
70
71
|
},
|
|
71
72
|
"schemas/campaign-spec.v4.schema.json": {
|
|
72
73
|
"sha256": "af56c3de638d3c4e1fd5df8c2d733754d9ad16caa23b5bec92ce3a6f71bd0271"
|
|
73
74
|
},
|
|
75
|
+
"schemas/campaign-spec.v5.schema.json": {
|
|
76
|
+
"sha256": "2923382e94ac51f6a19a3f9dfd0993d8c30fab33cd9be3d6901f314e02f5d960"
|
|
77
|
+
},
|
|
74
78
|
"schemas/campaigns-os-legacy-migration-inventory.v0.schema.json": {
|
|
75
79
|
"sha256": "d6edc4572900d15fdaf6b8c59916b0d885303c1d398bdadeb7c58285522c9049"
|
|
76
80
|
},
|
|
@@ -120,7 +124,7 @@
|
|
|
120
124
|
"sha256": "dfc9abed38d456969e47a21f606d308a03bdf036f3466f7d6e47a602747dacf4"
|
|
121
125
|
},
|
|
122
126
|
"contracts/effects.v1.json": {
|
|
123
|
-
"sha256": "
|
|
127
|
+
"sha256": "9c1d69e44eab7403ca3ff80f850214d89b0a02fdc51544beef37dbd0a653eeaa"
|
|
124
128
|
},
|
|
125
129
|
"schemas/campaigns-os-effects.v1.schema.json": {
|
|
126
130
|
"sha256": "3eadd22169ab98bc7c2f682af2751267605170581182158d96be035b0cfe44dc"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"asset_pin": {
|
|
57
57
|
"repo": "NextCommerceCo/campaign-cart-starter-templates",
|
|
58
|
-
"sha": "
|
|
58
|
+
"sha": "d5ff501ab57aef160a7108341de8a952006a28a4",
|
|
59
59
|
"path": "src/<family>/assets/<asset>",
|
|
60
60
|
"note": "asset_sha256 is the sha256 of each shipped starter asset at this commit (the commerce-surface-catalog _synced_from_sha pin; the bytes are identical across every family). A served asset whose bytes hash to the shipped value is the untouched starter asset and counts as residue even though its markup names no method; different bytes mean it was edited in place. check-template-doctrine verifies these hashes against the pinned checkout."
|
|
61
61
|
},
|
package/docs/build-packet.md
CHANGED
|
@@ -19,7 +19,8 @@ The current schema is `schemas/campaign-runtime-build-packet.v0.schema.json`.
|
|
|
19
19
|
|
|
20
20
|
A saved Map is optional for a prepared-HTML build. The coding agent authors an
|
|
21
21
|
ordinary CampaignSpec from the brief, source design and configured campaign's
|
|
22
|
-
real commerce values, following `schemas/campaign-spec.
|
|
22
|
+
real commerce values, following `schemas/campaign-spec.v5.schema.json` (a 4.2/4.3
|
|
23
|
+
spec still reads, upgraded on read; see `docs/versioning.md`). The
|
|
23
24
|
operator supplies the selected store/campaign, public Campaigns API key, intended
|
|
24
25
|
pages and commercial choices, plus store contact details and policy URLs. Verify
|
|
25
26
|
the store/campaign binding, and read the package, offer and shipping references
|
|
@@ -827,7 +828,7 @@ Blockers (not waivable — the markup provably does not do what it says):
|
|
|
827
828
|
- `CHECKOUT_NOT_FORM` — `data-next-checkout` on an element that is not `<form>`.
|
|
828
829
|
- `WRONG_FIELD_NAME` — `data-next-checkout-field` with a value the SDK does not
|
|
829
830
|
map. The set is vendored from the SDK at a named tag
|
|
830
|
-
(`src/sdk-attribute-index.mjs`, currently v0.4.
|
|
831
|
+
(`src/sdk-attribute-index.mjs`, currently v0.4.42: `email`, `fname`, `lname`,
|
|
831
832
|
`phone`, `address1`, `address2`, `city`, `province`, `postal`, `country`,
|
|
832
833
|
`payment-method`, `accepts_marketing`, `cc-number`, `cc-month`, `cc-year`,
|
|
833
834
|
`exp-month`, `exp-year`, `cvv`, the legacy `card-*` spellings, and any
|
|
@@ -1020,13 +1021,25 @@ than hand-adding the field. Note that a committed artifact set always reads
|
|
|
1020
1021
|
stale to the readback once HEAD moves past it — freshness proof is a
|
|
1021
1022
|
regeneration at HEAD, not a property a commit can preserve.
|
|
1022
1023
|
|
|
1023
|
-
Commit durable packet/context/report artifacts when they represent a real build handoff. The rest of `.campaign-runtime/` is machine-local and is not for the campaign repository: `run-session.json`, `command-lifecycle.jsonl`, `agent-deviations.jsonl`, `workflow-findings.jsonl`, `run-records/`, `fetched-specs/`, `polish-evidence/`, `evidence/`, and `*.log`/`*.tmp` are per-machine, append-only, or carry live URLs and absolute paths, and so are the full QA verdicts `qa run` writes under the target's `qa-output/`. `start`, `prepare-build`, `install-agent-context`, and `run start` write a managed ignore block for
|
|
1024
|
+
Commit durable packet/context/report artifacts when they represent a real build handoff. The rest of `.campaign-runtime/` is machine-local and is not for the campaign repository: `run-session.json`, `command-lifecycle.jsonl`, `agent-deviations.jsonl`, `workflow-findings.jsonl`, `run-records/`, `fetched-specs/`, `progress/`, `polish-evidence/`, `evidence/`, and `*.log`/`*.tmp` are per-machine, append-only, or carry live URLs and absolute paths, and so are the full QA verdicts `qa run` writes under the target's `qa-output/`. `start`, `prepare-build`, `install-agent-context`, and `run start` write a managed ignore block for that set into the target's `.gitignore` (keyed on its marker line; edit the list beneath it freely). An existing block gains `qa-output/` and `.campaign-runtime/progress/` when they are missing, since those hold live URLs, order references and remit records. The readback bundle (`build-context.json`, `assembly-report.json`, `doctor-output.json`, `qa-verdict.json`), `input/`, `theme/`, `agent-context/`, and `setup-handoff.json` are deliberately not ignored. The Campaigns API key is a public, browser-side, domain-allowlisted key and may already be present in the local CampaignSpec as `campaign.campaigns_api_key`; do not duplicate it into the packet unless the spec is unavailable. Do not commit raw private API responses, backend secrets, or temporary media exports.
|
|
1024
1025
|
|
|
1025
1026
|
Packet-mode `doctor` is inspection-only by default and preserves retained evidence and active run journals, even when lifecycle capture is configured. Use `--write` to intentionally record fresh evidence; `--no-write` takes precedence. Inspection still reports current blockers and keeps the same exit status.
|
|
1026
1027
|
|
|
1027
1028
|
`campaigns-os doctor --packet <packet> --write` restates its outcome on the Assembly Report's `stages.doctor` (status, command, outputs, blockers, warnings, `checked_at`). A re-run that reaches the same outcome leaves the report's bytes unchanged rather than refreshing the timestamp alone, so a digest taken of the report — a Run Record's `assembly_report` sha256 — keeps verifying across repeated doctor runs; a changed outcome still rewrites the file.
|
|
1028
1029
|
|
|
1029
|
-
`campaigns-os start` / `campaigns-os prepare-build` writes packet, context, report, and generated doctor-output paths
|
|
1030
|
+
`campaigns-os start` / `campaigns-os prepare-build` writes portable packet, context, report, and generated doctor-output paths. Resolve each path from its reader's base:
|
|
1031
|
+
|
|
1032
|
+
| Field | Base | Writer |
|
|
1033
|
+
|---|---|---|
|
|
1034
|
+
| Build Packet `spec.local_path`, `design_source_package.path`, `source_html.root`, `build_brief.input_path`, `build_brief.normalized_path`, `assembly.target_repo`, `assembly.commerce_catalog.path` | Build Packet file's directory, including when `--out` places it under `config/` | `start` / `prepare-build`; `record brief` and stage records retain this base for older absolute values |
|
|
1035
|
+
| Build Packet `source_html.pages[].path`; Build Context `page_map[].source_path` | Source HTML root | `start` / `prepare-build`; later context writes retain it |
|
|
1036
|
+
| Build Packet and Build Context `source_html.pages[].page_kit.target_path` / `page_map[].page_kit.target_path` | Page Kit output directory | `start` / `prepare-build`; later packet/context writes retain it |
|
|
1037
|
+
| Build Packet `assembly.output_dir` and `source_html.pages[].page_kit.output_path`; Build Context `page_map[].output_path` and `page_map[].page_kit.output_path` | Target repository root | `start` / `prepare-build`; later packet/context writes retain it |
|
|
1038
|
+
| Build Context and Assembly Report `design_source_package.path` | Directory of that context or report file (normally `.campaign-runtime/`) | `start` / `prepare-build`; later report writes retain it |
|
|
1039
|
+
| Normalized Campaign Build Brief `_meta.input_path` | Directory of the normalized brief file | `start` / `prepare-build`; `record brief` and `record spec` retain it |
|
|
1040
|
+
| Other listed Build Context, Assembly Report, and doctor-output filesystem paths | Target repository root | Intake, `doctor --write`, `next`, and stage writes |
|
|
1041
|
+
|
|
1042
|
+
Outside source inputs use ordinary relative paths from the applicable base; keep them at the recorded locations when moving the repository. Manifest paths embedded in decision evidence use relative paths from the target repository root. A stored command such as `next.command` uses a relative packet path and runs from the target repository root. `campaigns-os doctor` continues to accept older absolute-path packets; `--strip-paths` remains available when inspecting an older packet. Machine-local run sessions and journals remain ignored and may use absolute paths.
|
|
1030
1043
|
|
|
1031
1044
|
`start` / `prepare-build` also run the [Brand Theme Bridge](./brand-theme-bridge.md)
|
|
1032
1045
|
in `inspect_only` mode. The optional theme evidence lives in `context.theme`,
|
|
@@ -1194,6 +1207,20 @@ recorded build). `polish capture` refuses by name when the built route root is
|
|
|
1194
1207
|
missing, unreadable, or drifted; symbolic links are never build output and are
|
|
1195
1208
|
skipped by the walk.
|
|
1196
1209
|
|
|
1210
|
+
`record build` also keeps the manifest itself on `stages.assembly.build_manifest`,
|
|
1211
|
+
a sorted list of `{ "path", "sha256" }` entries that hashes to `build_fingerprint`.
|
|
1212
|
+
When the output drifts, `built_output.fingerprint_stale` and the `polish capture`
|
|
1213
|
+
refusal name the paths that are extra, missing, or changed against it, at most 20
|
|
1214
|
+
per list followed by `(+N more)`; doctor's issue `detail.drift` carries the counts
|
|
1215
|
+
and the same capped lists. An extra path shaped like a macOS or iCloud sync
|
|
1216
|
+
conflict copy (`<name> 2.<ext>`, `<name> 3.<ext>`, ...) whose unsuffixed original
|
|
1217
|
+
also exists is marked `[sync conflict copy]` with a hint to remove those copies and
|
|
1218
|
+
rebuild; a trailing number alone (`chapter 12.html`) is not flagged. A record
|
|
1219
|
+
made before build kept a manifest, or a manifest that does not hash to the recorded
|
|
1220
|
+
fingerprint, refuses as before and says the manifest is unavailable; run
|
|
1221
|
+
`record build` again to record one. Cleaning `_site/` before a build is Page Kit's
|
|
1222
|
+
job, not this check's.
|
|
1223
|
+
|
|
1197
1224
|
A stale or missing Assembly Source Package Fingerprint is waivable only as an
|
|
1198
1225
|
exceptional Source Freshness Waiver. The waiver must be structured in
|
|
1199
1226
|
`waivers[]`, with `scope: "assembly_source_package_freshness"` or an
|
|
@@ -1463,10 +1490,29 @@ Fresh `prepare-build` output also writes `source_html.pages[].page_kit` for mapp
|
|
|
1463
1490
|
- `target_path` is the page file relative to `assembly.output_dir` (`checkout.html`, `receipt.html`, etc.).
|
|
1464
1491
|
- `output_path` is the same target file relative to `assembly.target_repo`.
|
|
1465
1492
|
- `public_route` is the rendered campaign-rooted route Page Kit should produce.
|
|
1466
|
-
- `page_type` is the CPK runtime/analytics vocabulary (`product`, `checkout`, `upsell`, `receipt`), not the richer CampaignSpec or producer page type. CampaignSpec `select` pages project as CPK `checkout` because they
|
|
1467
|
-
- `frontmatter` names the Page Kit frontmatter fields the build should write or preserve.
|
|
1493
|
+
- `page_type` is the CPK runtime/analytics vocabulary (`product`, `checkout`, `upsell`, `receipt`), not the richer CampaignSpec or producer page type. CampaignSpec `select` and `checkout_step` pages project as CPK `checkout` because they render the SDK checkout surface ahead of the page that takes payment.
|
|
1494
|
+
- `frontmatter` names the Page Kit frontmatter fields the build should write or preserve. Beside `page_type`, `permalink`, `next_url` and `decline_url`, multi-step checkout paths follow the starter-template contract. On a `select` or `checkout_step` page on a path to a Checkout, `next_url` is that Checkout's post-payment destination, the meaning `next_url` has on every checkout page, and feeds the page's `next-success-url` meta tag, which the SDK reads for express orders; `next_step` is the page's forward link, rendered as the step form's `data-next-checkout-step` or the select page's checkout link; `step_number`, on a `checkout_step` only, is its position on its path from 1, for the step form's `data-next-step-number`. A landing page keeps `next_url` as its call-to-action target, so an express landing page takes its `next-success-url` from the Checkout's `next_url`. `success_url` is no longer emitted; the schema still accepts it so packets written by 1.56.0 and 1.57.0 validate.
|
|
1468
1495
|
- `permalink_required` is true when Page Kit's filename-derived route would not match `public_route`.
|
|
1469
1496
|
|
|
1497
|
+
An entry page (`is_entry: true`) declares the campaign root when `page_url` is
|
|
1498
|
+
empty, `/`, `/<slug>/`, or `<slug>`.
|
|
1499
|
+
If Page Kit emits its mapped target file, or a materialised template-stock
|
|
1500
|
+
family page, at that file's route without a root output, doctor recognizes the
|
|
1501
|
+
built page and warns that the root is unserved. The warning names both routes:
|
|
1502
|
+
add a redirect from the root to the emitted route or set
|
|
1503
|
+
`permalink: /<slug>/` on the entry page, the campaign root as Page Kit emits it.
|
|
1504
|
+
It uses `campaign.route_root`: a root-served campaign names `/` and
|
|
1505
|
+
`/<file-route>/`; a slug-served campaign names `/<slug>/` and
|
|
1506
|
+
`/<slug>/<file-route>/`. The built files stay under `_site/<slug>/` in both cases.
|
|
1507
|
+
`qa resolve --packet campaign-runtime.build.json --base-url <preview-url>` then
|
|
1508
|
+
prints the locally emitted entry route. An explicit permalink on that entry's
|
|
1509
|
+
Page Kit source file takes precedence, even if stale output remains at the
|
|
1510
|
+
file route. Doctor still warns if that permalink serves a non-root route and
|
|
1511
|
+
the root output is absent. With no permalink, a root `index.html` takes
|
|
1512
|
+
precedence over the recorded file route; with a non-root permalink, a present
|
|
1513
|
+
root output suppresses the warning while QA keeps the permalink Entry URL.
|
|
1514
|
+
Another page's `index.html` cannot stand in for the entry.
|
|
1515
|
+
|
|
1470
1516
|
The Design Source Package should reference this projection without confusing it
|
|
1471
1517
|
with Surface Identity. Surface Identity is the campaign-facing join key; Page Kit
|
|
1472
1518
|
`page_type`, public routes, output paths, CampaignSpec/Map Builder page IDs,
|
|
@@ -1754,14 +1800,26 @@ hand-editing `.campaign-runtime/` JSON:
|
|
|
1754
1800
|
| `campaigns-os record build --packet <p> [--build-environment <development\|production>] [--adapter-decision <key>=<value>[,<key>=<value>...]]` | `stages.assembly` completed with `build_fingerprint` = doctor's `derived.build_output_fingerprint.value`, `source_package_material_fingerprint` = the report's Design Source Package material fingerprint when present, `evidence.build_environment` = the `--build-environment` value when given (kept from the last record otherwise), and the specified scalar `report.adapter_decisions`; all recordable decisions go in one comma-separated flag (a repeated flag keeps only the last list); `wrapper_policy` stays an intake choice. `stages.polish` resets to `required` (`required_by` build, `required_for` qa) unless its evidence is bound to this exact output; a completed `stages.deploy` whose `source_build_fingerprint` names other output resets to `required` the same way, without the old probe's `outputs` and `evidence`, and its prior record is kept in `stages.deploy.history` (a deploy with no `source_build_fingerprint`, recorded before deploy stamped it, is kept) | doctor cannot compute the fingerprint (no `_site/<public_route_slug>/`), setup is still required, `stages.setup` is not terminal, or an adapter decision key/value is invalid |
|
|
1755
1801
|
| `campaigns-os record polish --packet <p> --evidence <file>` | `stages.polish` from the file (`docs/polish-evidence.md` §7: completed, blocked or skipped), bound to doctor's current fingerprint; `report.theme.repair_loop_defect` when the file sets it | build is not recorded for the current output, the file has a shape error (named by field), or, for a completed status, the polish gate doctor evaluates would not pass on the result |
|
|
1756
1802
|
| `campaigns-os record theme --packet <p>` | `report.theme`: status `applied`, `load_order` `after-next-core`, `css_path`, `commerce_pages` and per-page evidence read from each built commerce page's stylesheet links; any earlier theme waiver is cleared | build is not recorded for the current output, the campaign ships no commerce pages, a built commerce page that loads `next-core.css` does not load `brand-theme.css` (or `checkout-brand.css`) after it or links one missing from the built output, or no built commerce page loads `next-core.css` |
|
|
1757
|
-
| `campaigns-os record deploy --packet <p> --base-url <url>` | the packet's `deploy.preview_url` and `stages.deploy` completed with the URL in `outputs`, one evidence line per built page that answered (each page is requested under the URL first), and `source_build_fingerprint` = the recorded `stages.assembly.build_fingerprint` it probed, so a later `record build` of different output makes deploy owed again and `next` routes back to `record deploy` before QA; a byte-identical rebuild keeps it current | the
|
|
1803
|
+
| `campaigns-os record deploy --packet <p> --base-url <url>` | the packet's `deploy.preview_url` and `stages.deploy` completed with the URL in `outputs`, one evidence line per built page that answered (each page is requested under the URL first) and, on a hosted target, one per script or stylesheet served as built, and `source_build_fingerprint` = the recorded `stages.assembly.build_fingerprint` it probed, so a later `record build` of different output makes deploy owed again and `next` routes back to `record deploy` before QA; a byte-identical rebuild keeps it current | the URL does not name the campaign's route root; on a `local-serve` packet, the URL is not a loopback origin or a built page does not answer 2xx; on any other deploy target, the URL is not https (plain http only on a loopback host), a built page does not answer 200, or a script or stylesheet the built pages load from that origin is missing or served with other bytes (sha256) than the built output (each one named; HTML is not compared, since hosts inject markup); polish is not recorded, the built output changed since build was recorded, or the theme gate is blocked |
|
|
1758
1804
|
|
|
1759
1805
|
Each command also refuses a stage `next` has not reached: while doctor's
|
|
1760
1806
|
prepare-build gate is set (`next` answers prepare-build) or while an earlier
|
|
1761
1807
|
stage in the order below is not terminal. The exception is the one `next`
|
|
1762
1808
|
makes: on the local preview, a polish doctor carries forward (never recorded
|
|
1763
1809
|
for this build) does not hold `record deploy` back, as it does not hold `next`;
|
|
1764
|
-
polish stays owed and QA reports it.
|
|
1810
|
+
polish stays owed and QA reports it. If every mapped page is template stock on
|
|
1811
|
+
a hosted preview, doctor carries the same missing-evidence shape forward.
|
|
1812
|
+
On a hosted target, record an unset hosted preview URL with
|
|
1813
|
+
`campaigns-os qa policy set --packet <p> --preview-url <url>`, then record the
|
|
1814
|
+
deploy with `campaigns-os record deploy --packet <p> --base-url <preview url>`. The hosted preview must be a non-loopback
|
|
1815
|
+
HTTP(S) URL whose origin or QA-resolved campaign route path differs from `deploy.production_url`;
|
|
1816
|
+
query and fragment text does not make the production page a preview. With
|
|
1817
|
+
`deploy.preview_url` recorded, run `campaigns-os next qa --packet <p>` and
|
|
1818
|
+
`campaigns-os qa run --packet <p> --base-url <preview-url> --browser
|
|
1819
|
+
--test-order common`. The recorded hosted preview satisfies the deploy step
|
|
1820
|
+
for this all-template shape. After QA records a verdict for the current build,
|
|
1821
|
+
`campaigns-os next --packet <p>` reaches closeout without a hosted deploy
|
|
1822
|
+
record. QA against the packet's production URL retains the strict gate.
|
|
1765
1823
|
|
|
1766
1824
|
Each command reads the same packet, Build Context and Assembly Report `next`
|
|
1767
1825
|
reads (`--context` / `--report` override them the same way), validates what it
|
|
@@ -1791,7 +1849,7 @@ build` after every page-kit build; a rebuild that changes the output needs
|
|
|
1791
1849
|
rebuilt output, `next` asks for QA again if the last QA record names the
|
|
1792
1850
|
previous build fingerprint, even while its stage status still says completed.
|
|
1793
1851
|
|
|
1794
|
-
Stage order: `setup → build → polish → deploy → qa`. The picker walks this list and returns the first stage whose recorded status isn't terminal (`completed`, `completed_with_warnings`, `skipped`). During Polish, install the package-owned browser first, then run `campaigns-os polish capture` against the served current build before recording a terminal `stages.polish.status` or proceeding to deploy/QA; the producer attaches package-owned `visual_review.page_load` evidence and never marks the stage complete itself.
|
|
1852
|
+
Stage order: `setup → build → polish → deploy → qa`. The picker walks this list and returns the first stage whose recorded status isn't terminal (`completed`, `completed_with_warnings`, `skipped`). During Polish, when a design route exists, install the package-owned browser first, then run `campaigns-os polish capture` against the served current build before recording a terminal `stages.polish.status` or proceeding to deploy/QA; the producer attaches package-owned `visual_review.page_load` evidence and never marks the stage complete itself. Template-stock campaigns with no design route have no capture to run and follow the preview policy above.
|
|
1795
1853
|
|
|
1796
1854
|
| Stage | Report key | Owner |
|
|
1797
1855
|
|---|---|---|
|
|
@@ -1824,7 +1882,7 @@ Result shape (with `--json`):
|
|
|
1824
1882
|
Terminal states:
|
|
1825
1883
|
|
|
1826
1884
|
- **`stage: "doctor-blocked"`** — doctor returned errors. Resolve the blockers and re-run `campaigns-os doctor` to confirm before calling `next` again.
|
|
1827
|
-
- **`stage: "done"`** — every stage is
|
|
1885
|
+
- **`stage: "done"`** — every stage is terminal. Pipeline is complete. To repeat build work, do the work and use `record build`; it makes downstream stages owed as needed. Use `record setup` or `record polish` after repeating those stages, and `qa run` for QA. `record deploy` records the served deploy, a loopback URL for a local-serve target or an https URL for a hosted one. Then call `next` again.
|
|
1828
1886
|
- **`stage_blocked: true`** — the picker returned a stage whose recorded status is `blocked`. Don't run the prompt as-is; clear the blocker first.
|
|
1829
1887
|
|
|
1830
1888
|
The legacy form `campaigns-os next <stage>` (e.g. `next build`) still works and is the way to force a specific stage when you want to override the picker.
|
package/docs/local-setup.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
For a new campaign, create an empty working folder and run this from it:
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm init -y && npm install --save-exact next-campaign-page-kit@0.2.0 && npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.
|
|
6
|
+
npm init -y && npm install --save-exact next-campaign-page-kit@0.2.0 && npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.59.0 && npx --no-install campaigns-os tooling setup --target . --platform claude
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
`npm init -y` gives the folder its own `package.json`. Without one, npm
|
|
@@ -26,7 +26,7 @@ Ledger schema id: `campaigns-os-release-ledger/v1`
|
|
|
26
26
|
Change policy version: `1.0.0`
|
|
27
27
|
Reason-code vocabulary version: `1.0.0`
|
|
28
28
|
Limits version: `1.0.0`
|
|
29
|
-
Supported surface at generation time: `1.
|
|
29
|
+
Supported surface at generation time: `1.59.0`
|
|
30
30
|
|
|
31
31
|
## Forward compatibility
|
|
32
32
|
|
|
@@ -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,27 @@ 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
|
|
121
|
+
warning starting "Carried forward on the local preview" or "Carried forward on the hosted preview"; `next` moves past
|
|
122
122
|
polish to deploy and QA; 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
|
+
record the deploy with `campaigns-os record deploy --packet <packet> --base-url <preview-url>`
|
|
127
|
+
(on a hosted target it takes an https URL, requires every built page to answer
|
|
128
|
+
200, and compares the served scripts and stylesheets with the build by sha256),
|
|
129
|
+
then run `campaigns-os qa run --packet <packet> --base-url <preview-url> --browser --test-order common`. After QA records the current-build verdict, run `campaigns-os next --packet <packet>` for closeout. The hosted exception
|
|
130
|
+
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
|
|
131
|
+
the packet's `deploy.production_url` stays blocked. A `local-serve` packet
|
|
132
|
+
served from any other host also gets strict gates. `record polish` and the
|
|
133
|
+
waiver commands keep them strict. Progress
|
|
127
134
|
snapshots record a carried-forward gate as `not_applicable`, because their
|
|
128
135
|
schema has no carried-forward state.
|
|
129
136
|
|
|
@@ -179,8 +186,9 @@ line and the printed per-URL rows say which URLs those were rather than leaving
|
|
|
179
186
|
an operator to infer it from `counts.unreachable`. It is `null` only when every
|
|
180
187
|
probed URL resolved, or when nothing was probed at all.
|
|
181
188
|
|
|
182
|
-
Resolve appends `campaign.public_route_slug`
|
|
183
|
-
|
|
189
|
+
Resolve appends `campaign.public_route_slug` to the preview base for `base_url`
|
|
190
|
+
and page URLs, including when `campaign.route_root` is `/`. A subpath on
|
|
191
|
+
`--base-url` stays ahead of the slug. When
|
|
184
192
|
every derived route is dead, one extra probe of the host without that slug
|
|
185
193
|
separates the two causes and says which it found:
|
|
186
194
|
`route_probe.route_root_mismatch` (the host serves the campaign under a
|
|
@@ -308,6 +316,21 @@ for that entry URL. If Entry URLs are empty, still point at a deleted preview, o
|
|
|
308
316
|
fail their own HTTP assertion, fix `--base-url` or the packet deploy URL before
|
|
309
317
|
continuing.
|
|
310
318
|
|
|
319
|
+
For an `is_entry` page with `page_url` empty, `/`, `/<slug>/`, or `<slug>`, `qa resolve` uses that page's
|
|
320
|
+
explicit Page Kit permalink first, then the campaign root when
|
|
321
|
+
`_site/<slug>/index.html` exists. If neither applies and Page Kit emitted the
|
|
322
|
+
entry at its recorded page-file route, the printed Entry URL uses that locally
|
|
323
|
+
emitted route. Doctor warns that the Map still points at the unserved root;
|
|
324
|
+
add a redirect from the root to the emitted route or set `permalink: /<slug>/`
|
|
325
|
+
on the entry page. A non-root permalink also gets the warning when root output
|
|
326
|
+
is absent; QA keeps that permalink as its Entry URL. A present root output
|
|
327
|
+
suppresses the warning. The default
|
|
328
|
+
probe checks the printed URL on the host. For either `campaign.route_root: "/"`
|
|
329
|
+
or the default route root, the file route is joined under `base_url` as
|
|
330
|
+
`/<slug>/<file-route>/` (after any preview subpath). The root output, when
|
|
331
|
+
present, uses `base_url` itself. The route-root declaration still determines
|
|
332
|
+
the public route named in doctor's unserved-root warning.
|
|
333
|
+
|
|
311
334
|
`--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
335
|
|
|
313
336
|
```bash
|
|
@@ -1321,6 +1344,7 @@ Assembly Report's `stages.qa`:
|
|
|
1321
1344
|
"declared_order_path_depth": "common",
|
|
1322
1345
|
"declared_typed_card_depth": "common",
|
|
1323
1346
|
"order_paths_executed": 3,
|
|
1347
|
+
"test_order_attempts": 3,
|
|
1324
1348
|
"orders_created": 3,
|
|
1325
1349
|
"orders_verified": 3,
|
|
1326
1350
|
"all_orders_test_mode": true
|
|
@@ -1333,7 +1357,9 @@ readback bundle, where the verdict's own order arrays are deliberately emptied
|
|
|
1333
1357
|
(see the committed verdict sidecar above) — so the signal that a purchase
|
|
1334
1358
|
happened has to be numbers, not the orders themselves. `all_orders_test_mode` is
|
|
1335
1359
|
`null`, not `false`, when nothing ran: "no order left test mode" and "no order
|
|
1336
|
-
ran" are different facts.
|
|
1360
|
+
ran" are different facts. `order_paths_executed` counts planned paths and
|
|
1361
|
+
`test_order_attempts` counts every attempt, so a re-run adds an attempt and not a
|
|
1362
|
+
path.
|
|
1337
1363
|
|
|
1338
1364
|
Beside it, `stages.qa.evidence` carries the build the verdict judged and the
|
|
1339
1365
|
outcome of the gates doctor's static scan can only approximate:
|
|
@@ -1446,7 +1472,21 @@ timeout still leaves the ladder up to the point of failure. Ladder entries carry
|
|
|
1446
1472
|
object. Evidence is resolved even when the step fails or times out, because the
|
|
1447
1473
|
failing path is the one worth reading.
|
|
1448
1474
|
|
|
1449
|
-
|
|
1475
|
+
Five steps write structured evidence today.
|
|
1476
|
+
|
|
1477
|
+
**Multi-step checkout paths (campaigns-os#641).** The ladder drives the
|
|
1478
|
+
Checkout — the page that takes payment (`type: "checkout"`) — on the tested
|
|
1479
|
+
path, never the first page typed checkout. When `checkout_step` pages lead
|
|
1480
|
+
into it, the runner lands on the first step: through a `select` page ahead of
|
|
1481
|
+
it when there is one (`entered_via_landing` with `control_kind:
|
|
1482
|
+
checkout_button`), otherwise the step fills the cart itself.
|
|
1483
|
+
`customer_fields_filled` fills only the fields that step renders, one
|
|
1484
|
+
`checkout_step_submitted` rung per step follows, and the order is submitted
|
|
1485
|
+
only on the Checkout. With
|
|
1486
|
+
split-test paths in one campaign, each Checkout reached from an entry page is
|
|
1487
|
+
its own path; `tiers` plans read declared tiers and bumps from every page of
|
|
1488
|
+
that path (the select page or first step declares the cart), and coupons from
|
|
1489
|
+
its steps and Checkout.
|
|
1450
1490
|
|
|
1451
1491
|
**`entered_via_landing` — cart entry.** The first rung of the ladder, before
|
|
1452
1492
|
`opened_checkout`. A checkout renders its customer form whether or not the SDK
|
|
@@ -1490,7 +1530,13 @@ checkout — never a receipt or an offer page), navigates there, waits for the
|
|
|
1490
1530
|
SDK, and clicks the cart-entry control: an SDK add-to-cart control
|
|
1491
1531
|
(`[data-next-action="add-to-cart"]`, the only attribute the SDK activates the
|
|
1492
1532
|
feature on), or a link into the checkout URL carrying `?forcePackageId=`,
|
|
1493
|
-
which is what the certified `shop-single-step` landing renders
|
|
1533
|
+
which is what the certified `shop-single-step` landing renders, or, on a
|
|
1534
|
+
page with bundle cards (`[data-next-bundle-card]`, a `select` page), a checkout
|
|
1535
|
+
button: the SDK's `[data-next-action="checkout"]` or a plain link into the
|
|
1536
|
+
checkout URL. There the swap-mode selector already wrote the cart and the SDK
|
|
1537
|
+
persists it, so the runner keeps the pre-selected card, or with
|
|
1538
|
+
`--select-package <ref>` clicks the bundle card carrying that ref first,
|
|
1539
|
+
strictly, before the button. A control
|
|
1494
1540
|
spelled any other way is not a cart entry, so a page that offers nothing else
|
|
1495
1541
|
fails the step by name (`cart_entry_control_missing`) rather than clicking a
|
|
1496
1542
|
control the SDK never wired and waiting out the navigation budget. A visible
|
|
@@ -1508,17 +1554,51 @@ cart away. `opened_checkout` then records the arrival instead of re-opening.
|
|
|
1508
1554
|
|
|
1509
1555
|
Evidence: `landing_url`, `landing_page_id`, `landing_page_type`,
|
|
1510
1556
|
`landing_resolution` (`routes_into_checkout`, `entry_page_fallback`,
|
|
1511
|
-
`first_page_fallback`), `control_text`, `control_kind` (`add_to_cart
|
|
1512
|
-
`checkout_link`), `package_id`, `
|
|
1557
|
+
`first_page_fallback`), `control_text`, `control_kind` (`add_to_cart`,
|
|
1558
|
+
`checkout_link`, or `checkout_button`), `package_id`, `bundle_card_selection`
|
|
1559
|
+
when `--select-package` clicked a bundle card, `variant_selections` when the
|
|
1560
|
+
runner chose slot variants (below), `sdk_ready`, `arrived_url`, the
|
|
1513
1561
|
`checkout_selection_surface` probe result, `selection_surface_probe`
|
|
1514
1562
|
(`loaded`, `reused`, or `failed`), and `selection_surface_probe_error` when it
|
|
1515
1563
|
failed. The failure codes are
|
|
1516
1564
|
`cart_entry_unresolved` (no selection surface on checkout and no entry page
|
|
1517
1565
|
resolves from the topology), `cart_entry_control_missing` (the entry page
|
|
1518
1566
|
renders no control, or none carrying the requested ref), and
|
|
1519
|
-
`cart_entry_no_navigation` (the click did not reach the checkout URL)
|
|
1520
|
-
|
|
1521
|
-
|
|
1567
|
+
`cart_entry_no_navigation` (the click did not reach the checkout URL), and
|
|
1568
|
+
`cart_entry_variant_unfilled` (a bundle slot's variant control has no in-stock
|
|
1569
|
+
option or row, or its row could not be clicked, named by slot). Each fails the path inside the step budget with the
|
|
1570
|
+
code as the first word of the error, never as a step timeout.
|
|
1571
|
+
|
|
1572
|
+
On a select page whose bundle slots each need a variant (size, colour) before
|
|
1573
|
+
Next, the runner chooses one per slot after any bundle card click and before
|
|
1574
|
+
the checkout control, one field at a time. It looks at every visible, enabled
|
|
1575
|
+
slot variant select (`[data-next-variant-selectors] select` or
|
|
1576
|
+
`.next-slot-variant-field select`, the native select the SDK renders, counted
|
|
1577
|
+
visible when its field is):
|
|
1578
|
+
|
|
1579
|
+
- When the field shows a dropdown UI (an `os-dropdown` with a
|
|
1580
|
+
`.os-card__variant-dropdown-toggle` and `os-dropdown-item` rows, as the
|
|
1581
|
+
olympus-mv-two-step starter renders), the runner clicks the toggle and then a
|
|
1582
|
+
row, like a shopper, even when the select is already filled. A page may count
|
|
1583
|
+
a field as chosen only when a row was clicked. It clicks the row matching the
|
|
1584
|
+
select's current value when that row is in stock, so the cart stays what the
|
|
1585
|
+
SDK chose, and otherwise the first in-stock row. After the toggle click it
|
|
1586
|
+
finds the field again by slot identity and the row again by its value, and
|
|
1587
|
+
waits up to 2 s for that row to show, so a menu that opens late or re-renders
|
|
1588
|
+
its rows is handled.
|
|
1589
|
+
- With no dropdown UI, an empty select gets its first in-stock option, with
|
|
1590
|
+
`input` and `change` dispatched. A filled native select is left alone.
|
|
1591
|
+
|
|
1592
|
+
In stock means enabled, not hidden, and with a non-empty value. For a row it
|
|
1593
|
+
also means not `[disabled]`, `aria-disabled`, `.next-oos`,
|
|
1594
|
+
`.next-variant-unavailable` or `data-available="false"`, and that the row's
|
|
1595
|
+
native option is not disabled. Each choice is recorded in
|
|
1596
|
+
`variant_selections` as `{ bundle_id, slot, variant_code, value, via }`, where
|
|
1597
|
+
`via` is `dropdown` or `select`, so the ordered variants can be checked against
|
|
1598
|
+
the cart lines. A field with nothing to choose, or a row that does not show
|
|
1599
|
+
or cannot be clicked, fails the path with `cart_entry_variant_unfilled` before Next is
|
|
1600
|
+
clicked. A page whose filled selects have no dropdown UI is left untouched and
|
|
1601
|
+
`variant_selections` is omitted.
|
|
1522
1602
|
|
|
1523
1603
|
Which page a funnel enters the cart from is still inferred from topology and
|
|
1524
1604
|
the rendered checkout. Recording it authoritatively on the spec is the open
|
|
@@ -1545,6 +1625,21 @@ own 30s default, so a caller-supplied budget only ever tightens the ceiling. A
|
|
|
1545
1625
|
stuck required field fails as that field rather than as an anonymous step
|
|
1546
1626
|
timeout; a slow-but-working funnel waits no longer than it did before.
|
|
1547
1627
|
|
|
1628
|
+
**`checkout_step_submitted` — one rung per `checkout_step`.** On a path whose
|
|
1629
|
+
Checkout is preceded by `checkout_step` pages, each step's rung checks the page
|
|
1630
|
+
is on that step, clicks the submit control of its
|
|
1631
|
+
`form[data-next-checkout-step]` (the SDK validates the step's fields, keeps the
|
|
1632
|
+
answers and navigates), and waits for the page to land on the step's declared
|
|
1633
|
+
next page (`expected_next_url`). It then fills whatever fields the arrived page
|
|
1634
|
+
renders. A step whose form carries no `data-next-checkout-step`, or whose
|
|
1635
|
+
submit never reaches the declared next page (usually a required field the
|
|
1636
|
+
step validates), fails the rung by name; nothing is reserved or submitted, so
|
|
1637
|
+
the creation classifier reads it as `not_created`. Evidence: `step_page_id`,
|
|
1638
|
+
`step_number`, `expected_next_url`, `arrived_url` and the arrived page's
|
|
1639
|
+
`fields` trace. A path that starts on its first step, with no page routing into
|
|
1640
|
+
it, skips `entered_via_landing`: the step page fills the cart and the guard
|
|
1641
|
+
before submit checks it.
|
|
1642
|
+
|
|
1548
1643
|
**`cart_created` — cart-API observation.** The step reports what the cart API
|
|
1549
1644
|
actually returned: the most recent `POST /api/v1/carts/` response's `status`, an
|
|
1550
1645
|
`ok` flag, `line_count` when the response body exposes lines, `response_count`,
|
|
@@ -1780,7 +1875,7 @@ deciding what to do next, it classifies what the attempt did to the store:
|
|
|
1780
1875
|
|
|
1781
1876
|
| Classification | What it means | What the runner does |
|
|
1782
1877
|
|---|---|---|
|
|
1783
|
-
| `not_created` | The path failed before the checkout was submitted (including the runner's own named refusals: `cart_entry_unresolved`, `cart_entry_control_missing`, `cart_entry_no_navigation`, `cart_empty_before_submit`), or the platform rejected every order create it saw. Nothing reached the store. | Re-runs the path once, if the creation budget has a slot no still-unrun planned path needs. This is the bounded retry for a transient miss; the re-run decides the assertion. |
|
|
1878
|
+
| `not_created` | The path failed before the checkout was submitted (including the runner's own named refusals: `cart_entry_unresolved`, `cart_entry_control_missing`, `cart_entry_no_navigation`, `cart_entry_variant_unfilled`, `cart_empty_before_submit`), or the platform rejected every order create it saw. Nothing reached the store. | Re-runs the path once, if the creation budget has a slot no still-unrun planned path needs. This is the bounded retry for a transient miss; the re-run decides the assertion. |
|
|
1784
1879
|
| `created` | An order exists and was read back — the failure happened after the purchase (most often a receipt that did not render its line items). | Runs a **read-only recovery pass**: reloads the receipt the order already produced, re-reads the persisted order, and re-checks the buyer-visible receipt surface and the voucher read-back. It clicks nothing, applies nothing, and submits nothing. |
|
|
1785
1880
|
| `ambiguous` | The submit may have created an order this runner cannot see: a ref id with an unusable read-back, a lost create response, a network-failed create, or a 4xx that follows an earlier 2xx on the same endpoint. | Stops. It never resubmits, and the assertion names the check an operator should run — look for an existing order against the run's QA email or the observed ref id. |
|
|
1786
1881
|
|
|
@@ -1805,6 +1900,13 @@ is not re-run and its assertion records why under
|
|
|
1805
1900
|
re-runs for those paths. A re-run that stops on the budget never becomes the
|
|
1806
1901
|
deciding result: it proved nothing, so the first attempt's real failure stands.
|
|
1807
1902
|
|
|
1903
|
+
Every attempt is its own `test_orders[]` entry, because a re-run can place its
|
|
1904
|
+
own real order. Each entry carries `plan_id`, the planned path it belongs to,
|
|
1905
|
+
and `attempt`, 1 for the first try and 2 for the re-run, so a run with five
|
|
1906
|
+
planned paths that each re-ran once holds ten entries for five paths. The
|
|
1907
|
+
`qa run` summary prints the three counts apart, for example
|
|
1908
|
+
`Test orders: 5 paths, 10 attempts, 0 orders created`.
|
|
1909
|
+
|
|
1808
1910
|
Recovery may only clear a failure on evidence it actually re-read. If the
|
|
1809
1911
|
receipt reload's persisted-order read-back fails or never happens, the pass stops
|
|
1810
1912
|
honestly: the read-back failure is itself a remaining failure, and the receipt
|
|
@@ -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.59.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
|
|