@nextcommerce/campaigns-os 1.60.0 → 1.62.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/AGENTS.md +2 -1
- package/CHANGELOG.md +244 -0
- package/README.md +86 -438
- package/agents/claude/CLAUDE.md +20 -0
- package/agents/codex/AGENTS.md +19 -0
- package/agents/copilot/copilot-instructions.md +19 -0
- package/agents/cursor/campaigns-os.mdc +21 -0
- package/campaign-spec/README.md +7 -0
- package/campaign-spec/dist/analytics-vendors.d.ts +40 -0
- package/campaign-spec/dist/analytics-vendors.js +44 -0
- package/campaign-spec/dist/checkout-flow.d.ts +18 -0
- package/campaign-spec/dist/checkout-flow.js +28 -0
- package/campaign-spec/dist/index.d.ts +4 -2
- package/campaign-spec/dist/index.js +5 -1
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +8 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.js +39 -0
- package/campaign-spec/dist/rules/campaign-currencies.d.ts +29 -0
- package/campaign-spec/dist/rules/campaign-currencies.js +130 -0
- package/campaign-spec/dist/rules/checkout-path-has-packages.d.ts +31 -0
- package/campaign-spec/dist/rules/checkout-path-has-packages.js +79 -0
- package/campaign-spec/dist/rules/index.d.ts +2 -0
- package/campaign-spec/dist/rules/index.js +6 -0
- package/campaign-spec/dist/types.d.ts +43 -9
- package/compatibility.json +1 -1
- package/contracts/brand-theme-target-tokens.next-core.v0.json +95 -25
- package/contracts/campaign-cart-sdk-support-policy.v0.json +2 -2
- package/contracts/commerce-surface-catalog.json +68 -61
- package/contracts/effects.v1.json +549 -165
- package/contracts/fixtures/runtime-recipe/accept/current.json +3 -0
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +3 -0
- package/contracts/fixtures/runtime-recipe/dist-states.json +1 -1
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +3 -0
- package/contracts/release-ledger.json +1767 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +3 -0
- package/contracts/supported-surface.json +10 -10
- package/contracts/template-brand-contract.shared-commerce.v0.json +23 -1
- package/docs/access-model.md +20 -0
- package/docs/brand-theme-bridge.md +62 -3
- package/docs/build-packet.md +456 -36
- package/docs/campaign-build-brief.md +16 -0
- package/docs/campaign-standardization-report.md +4 -4
- package/docs/campaigns-os-build-flow.md +112 -2
- package/docs/campaignspec-authoring-examples.md +141 -0
- package/docs/design-source-package.md +64 -8
- package/docs/effects.md +17 -9
- package/docs/entry-points.md +188 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/polish-evidence.md +42 -2
- package/docs/progress-snapshots.md +7 -4
- package/docs/qa-and-test-orders.md +197 -3
- package/docs/quickstart.md +438 -0
- package/docs/readback.md +154 -6
- package/docs/runtime-readiness.md +6 -3
- package/docs/sdk-storage-compatibility.md +4 -2
- package/docs/skills-revision.md +10 -10
- package/docs/source-adapters.md +213 -0
- package/package.json +10 -3
- package/schemas/campaign-design-source-package.v0.schema.json +8 -0
- package/schemas/campaign-runtime-build-context.v0.schema.json +9 -0
- package/schemas/campaign-runtime-build-packet.v0.schema.json +9 -0
- package/schemas/campaign-spec.v4.schema.json +19 -3
- package/schemas/campaign-spec.v5.schema.json +19 -3
- package/schemas/campaigns-os-readback.v2.schema.json +238 -1
- package/schemas/source-html-manifest.v0.schema.json +3 -0
- package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
- 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 +5 -4
- package/skills/next-campaigns-os/SKILL.md +7 -4
- package/skills/next-campaigns-os-setup/SKILL.md +5 -3
- package/skills/next-campaigns-polish/SKILL.md +8 -10
- package/skills/next-campaigns-qa/SKILL.md +7 -4
- package/skills/next-campaigns-sdk-bump/SKILL.md +151 -25
- package/skills.json +11 -11
- package/src/brand-theme.mjs +415 -9
- package/src/brief-summary.mjs +6 -2
- package/src/browser-launch.mjs +2 -1
- package/src/build-brief.mjs +13 -3
- package/src/built-site-scope.mjs +2 -2
- package/src/campaign-workspace.mjs +288 -15
- package/src/change-scope.mjs +284 -0
- package/src/cli.mjs +358 -97
- package/src/commercial-journey.mjs +16 -3
- package/src/commercial-parity.mjs +15 -7
- package/src/content-residue.mjs +4 -2
- package/src/design-file-reference.mjs +93 -0
- package/src/design-source-package.mjs +91 -1
- package/src/design-source-publication.mjs +9 -6
- package/src/directory-lock.mjs +73 -19
- package/src/doctor/checks.mjs +87 -81
- package/src/doctor/ci.mjs +177 -0
- package/src/doctor/inspect.mjs +41 -6
- package/src/doctor/next-step.mjs +7 -0
- package/src/doctor/source-provenance.mjs +74 -0
- package/src/doctor-sidecar.mjs +3 -2
- package/src/evidence-debt.mjs +159 -0
- package/src/finding-cause.mjs +12 -11
- package/src/input-refresh.mjs +339 -32
- package/src/invocation.mjs +7 -1
- package/src/live-campaign-refs.mjs +7 -2
- package/src/local-preview-policy.mjs +35 -2
- package/src/page-kit-analytics-vendors.mjs +132 -0
- package/src/parity/compare.mjs +316 -0
- package/src/parity/design-access.mjs +54 -0
- package/src/parity/element-parity-record.mjs +569 -0
- package/src/parity/probe.mjs +205 -0
- package/src/parity/snapshot.mjs +110 -0
- package/src/png-header.mjs +87 -0
- package/src/polish-browser.mjs +189 -10
- package/src/polish-captures-record.mjs +52 -0
- package/src/polish-deadline.mjs +14 -0
- package/src/polish-gate.mjs +25 -6
- package/src/polish-node.mjs +81 -7
- package/src/polish-readability.mjs +221 -9
- package/src/polish-visual-fixtures.mjs +74 -0
- package/src/polish-visual.mjs +613 -0
- package/src/progress-node.mjs +3 -2
- package/src/qa-analytics-correctness.mjs +14 -2
- package/src/qa-analytics-parity.mjs +111 -6
- package/src/qa-browser.mjs +40 -4
- package/src/qa-commercial-parity.mjs +50 -3
- package/src/qa-node.mjs +235 -22
- package/src/qa-publish.mjs +9 -8
- package/src/qa-sidecar.mjs +16 -4
- package/src/qa-vendor-beacons.mjs +232 -0
- package/src/qa-vendor-endpoints.mjs +306 -0
- package/src/qc-accept.mjs +176 -6
- package/src/qc-check-registry.mjs +42 -2
- package/src/qc-results.mjs +477 -42
- package/src/readback.mjs +113 -21
- package/src/route-identity.mjs +10 -0
- package/src/run-record.mjs +18 -10
- package/src/runtime-state-ignore.mjs +6 -2
- package/src/sdk-attribute-index.mjs +11 -11
- package/src/sdk-markup.mjs +77 -2
- package/src/sdk-storage-compatibility.mjs +115 -6
- package/src/source-html-intake.mjs +15 -1
- package/src/source-html-manifest.mjs +7 -0
- package/src/source-html-server.mjs +129 -0
- package/src/spec-derive-campaign.mjs +216 -0
- package/src/spec-derive.mjs +473 -53
- package/src/spec-identity.mjs +14 -0
- package/src/stage-ledger.mjs +40 -1
- package/src/stage-record.mjs +100 -17
- package/src/target-lock.mjs +11 -1
- package/src/theme-gate.mjs +2 -2
- package/src/visual-diff.mjs +296 -0
- package/src/visual-reference.mjs +126 -0
package/docs/readback.md
CHANGED
|
@@ -8,7 +8,9 @@ most once, plus two fixed Git metadata files (the nearest `.git` entry at the
|
|
|
8
8
|
target or an ancestor, and that Git directory's `logs/HEAD` reflog) for the
|
|
9
9
|
freshness comparison, and the CampaignSpec and normalized Campaign Build Brief
|
|
10
10
|
the Build Packet names, whose current content decides each stage's effective
|
|
11
|
-
status.
|
|
11
|
+
status. It also reads and hashes regular files in the packet's built output
|
|
12
|
+
at `_site/<public_route_slug>/` to assess build-bound evidence debt. Symlinks
|
|
13
|
+
are skipped by the build fingerprint machinery.
|
|
12
14
|
|
|
13
15
|
The safety contract is the point of the command. The readback **writes nothing**
|
|
14
16
|
under the target — not even a lifecycle journal entry, which every other command
|
|
@@ -21,7 +23,7 @@ For the same reason the readback resolves **no run session** and sweeps no stale
|
|
|
21
23
|
one. Its `--packet` names the Build Packet to *project*, not a Build Packet to
|
|
22
24
|
act on, so an active run session — here, at the target, or bound to some other
|
|
23
25
|
packet entirely — never changes what this command reads or what it exits with.
|
|
24
|
-
|
|
26
|
+
The only reader of a `--packet` file is the
|
|
25
27
|
readback's own 32 MiB-bounded one, which refuses an oversized packet as an
|
|
26
28
|
`unreadable` artifact row rather than loading it.
|
|
27
29
|
|
|
@@ -41,8 +43,8 @@ campaigns-os readback --example [--json]
|
|
|
41
43
|
The default output is the rendered human view. `--json` emits the same
|
|
42
44
|
projection as one `campaigns-os-readback/v2` object on stdout so a caller can
|
|
43
45
|
gate on it; `schemas/campaigns-os-readback.v2.schema.json` is that object's
|
|
44
|
-
shape,
|
|
45
|
-
path: the JSON serializes what the text view already computes and adds no
|
|
46
|
+
shape, including the additive `evidence_debt` field. The two modes share one
|
|
47
|
+
computation path: the JSON serializes what the text view already computes and adds no
|
|
46
48
|
interpretation the text view does not also carry.
|
|
47
49
|
|
|
48
50
|
## Exit codes
|
|
@@ -118,6 +120,15 @@ STAGES [assembly report; report status: prepared; effective: prepared]
|
|
|
118
120
|
deploy pending
|
|
119
121
|
qa pending
|
|
120
122
|
|
|
123
|
+
EVIDENCE DEBT [the readback's own projection layer: recorded build bindings versus built output]
|
|
124
|
+
polish: unknown — evidence currency is unknown (build_output_missing); absence of proof is not no debt
|
|
125
|
+
recorded: unknown; current: unknown
|
|
126
|
+
qa: unknown — evidence currency is unknown (build_output_missing); absence of proof is not no debt
|
|
127
|
+
recorded: unknown; current: unknown
|
|
128
|
+
changes against assembly manifest: unknown (build_output_missing)
|
|
129
|
+
recorded: unknown; current: unknown
|
|
130
|
+
Page changes use the assembly baseline; older stage manifests are not reconstructed.
|
|
131
|
+
|
|
121
132
|
BUILD CONTEXT [build context; source adapter: html_funnel, status: prepared]
|
|
122
133
|
|
|
123
134
|
DOCTOR [doctor output; status: ready_with_warnings]
|
|
@@ -182,6 +193,23 @@ STAGES [assembly report; report status: completed; effective: prepared]
|
|
|
182
193
|
},
|
|
183
194
|
"skip_cascades": [{ "blocked_by": "polish.evidence_incomplete", "families": ["meta-tags"] }],
|
|
184
195
|
"divergences": [{ "stage": "polish", "assertion_ids": ["polish.evidence_incomplete"] }],
|
|
196
|
+
"evidence_debt": {
|
|
197
|
+
"polish": {
|
|
198
|
+
"status": "unknown", "matches_current_build": null,
|
|
199
|
+
"recorded_build_fingerprint": null, "current_build_fingerprint": null,
|
|
200
|
+
"reason": "build_output_missing"
|
|
201
|
+
},
|
|
202
|
+
"qa": {
|
|
203
|
+
"status": "unknown", "matches_current_build": null,
|
|
204
|
+
"recorded_build_fingerprint": null, "current_build_fingerprint": null,
|
|
205
|
+
"reason": "build_output_missing"
|
|
206
|
+
},
|
|
207
|
+
"changes": {
|
|
208
|
+
"status": "unknown", "reason": "build_output_missing",
|
|
209
|
+
"recorded_build_fingerprint": null, "current_build_fingerprint": null,
|
|
210
|
+
"changed_pages": [], "unmapped_files": [], "extra": [], "missing": [], "changed": []
|
|
211
|
+
}
|
|
212
|
+
},
|
|
185
213
|
"clean": false
|
|
186
214
|
}
|
|
187
215
|
```
|
|
@@ -267,6 +295,20 @@ carries no parseable value — the readback does not choose. It names the
|
|
|
267
295
|
candidates on stderr, says `--packet` is required, and exits `2`. No JSON object
|
|
268
296
|
is emitted in that case.
|
|
269
297
|
|
|
298
|
+
**Freshness chooses among runs of one funnel, never among funnels.** A project
|
|
299
|
+
that holds several funnels has one packet per funnel
|
|
300
|
+
(`campaign-runtime-<route>.build.json` beside the default; see
|
|
301
|
+
[Several funnels in one project](build-packet.md#several-funnels-in-one-project)).
|
|
302
|
+
When the valid candidates name more than one `campaign.public_route_slug`, the
|
|
303
|
+
readback refuses the same way, naming each packet with its route, and exits
|
|
304
|
+
`2`. Pass `--packet` to project one funnel. The doctor output, Build Context,
|
|
305
|
+
Assembly Report, QA verdict and findings export rows then read from the runtime
|
|
306
|
+
directory that packet records (`assembly.runtime_dir`), or from
|
|
307
|
+
`.campaign-runtime/` when it records none. A packet whose `assembly.runtime_dir`
|
|
308
|
+
is not its own funnel's directory is refused with exit `2`, since the readback
|
|
309
|
+
cannot tell whose records to show. An explicit `--doctor`, `--context`,
|
|
310
|
+
`--report`, `--qa-verdict` or `--findings` path still wins.
|
|
311
|
+
|
|
270
312
|
Values are compared at **microsecond precision** — the precision Python's
|
|
271
313
|
`datetime.fromisoformat` keeps, and the precision the Python readback this
|
|
272
314
|
command ports compared at; a seventh or later fractional digit is truncated, not
|
|
@@ -397,6 +439,106 @@ in first-seen order:
|
|
|
397
439
|
Empty when no QA verdict loaded or none of its assertions were skipped. This
|
|
398
440
|
grouping is the readback's own projection layer.
|
|
399
441
|
|
|
442
|
+
### `evidence_debt`
|
|
443
|
+
|
|
444
|
+
An additive top-level object in `campaigns-os-readback/v2`, emitted in JSON and
|
|
445
|
+
as an **EVIDENCE DEBT** text section. This assessment reads built bytes; it uses
|
|
446
|
+
no timestamps or reflog entries, starts no process, and writes nothing. It
|
|
447
|
+
does not rerun doctor, Polish, or QA and does not reinterpret their verdicts.
|
|
448
|
+
|
|
449
|
+
`polish` and `qa` each carry:
|
|
450
|
+
|
|
451
|
+
- `status`: the closed set `current`, `owed`, `unknown`;
|
|
452
|
+
- `matches_current_build`: fingerprint equality only (`true` or `false`), or `null`
|
|
453
|
+
when either fingerprint is absent;
|
|
454
|
+
- `recorded_build_fingerprint`: the stage's build binding, or `null`;
|
|
455
|
+
- `current_build_fingerprint`: the fingerprint computed from current built
|
|
456
|
+
output with `computeBuildFingerprint`, or `null`;
|
|
457
|
+
- `reason`: `null` for `current`; `build_fingerprint_changed`,
|
|
458
|
+
`stage_required_by_build`, `stage_required_by_brief`,
|
|
459
|
+
`stage_required_by_spec`, or `stage_required` for `owed`; or an explicit
|
|
460
|
+
reason for `unknown`.
|
|
461
|
+
|
|
462
|
+
Polish uses `stages.polish.source_build_fingerprint`, falling back to
|
|
463
|
+
`stages.polish.evidence.source_build_fingerprint`, with the same precedence as
|
|
464
|
+
the Polish gate. QA uses `stages.qa.evidence.qc_build_fingerprint`, falling back
|
|
465
|
+
to `source_build_fingerprint`, and the same build-binding comparison as `next`.
|
|
466
|
+
It does not require uncommitted capture files to be present.
|
|
467
|
+
|
|
468
|
+
`current` means the stage's binding equals the bytes on disk and the assembly
|
|
469
|
+
fingerprint is available and the stage is not recorded as `required`. `owed`
|
|
470
|
+
means that binding differs from the current build, or the lifecycle recorded
|
|
471
|
+
the stage as `required`, even without a stage fingerprint. Its `required_by`
|
|
472
|
+
selects `stage_required_by_build`, `stage_required_by_brief`, or
|
|
473
|
+
`stage_required_by_spec`; an absent or unrecognized source gives
|
|
474
|
+
`stage_required`. Both comparisons work without a manifest, including reports recorded
|
|
475
|
+
before manifests existed. **`unknown` never means no debt.** Missing or unreadable
|
|
476
|
+
built output or a missing assembly fingerprint still makes the affected stage
|
|
477
|
+
explicitly `unknown`; a missing stage fingerprint does too unless the stage is
|
|
478
|
+
`required`. The unknown stage reason codes are
|
|
479
|
+
`packet_unavailable`, `build_output_location_unknown`, `build_output_missing`,
|
|
480
|
+
`build_output_unreadable`, `assembly_build_fingerprint_missing`, and
|
|
481
|
+
`stage_build_fingerprint_missing`.
|
|
482
|
+
When both stage and disk fingerprints are available, `matches_current_build`
|
|
483
|
+
reports their equality even if a missing assembly fingerprint makes status unknown.
|
|
484
|
+
`matches_current_build: true` together with `status: owed` means the lifecycle
|
|
485
|
+
requires the stage again over matching evidence. Consumers decide whether
|
|
486
|
+
evidence is owed from `status`, never from `matches_current_build`.
|
|
487
|
+
|
|
488
|
+
`changes` compares current output to **the retained assembly manifest**, using
|
|
489
|
+
the existing file drift machinery. It carries:
|
|
490
|
+
|
|
491
|
+
- `status`: `known` or `unknown`, with `reason` (`null` when known);
|
|
492
|
+
- `recorded_build_fingerprint` and `current_build_fingerprint`: the assembly
|
|
493
|
+
manifest baseline and the disk fingerprint;
|
|
494
|
+
- `extra`, `missing`, `changed`: complete file path lists from the manifest
|
|
495
|
+
comparison, without the human drift summary's truncation;
|
|
496
|
+
- `changed_pages`: `{ "page_id": "landing", "built_path": "landing/index.html" }`
|
|
497
|
+
entries for added, removed, or modified HTML in the built-site page map;
|
|
498
|
+
- `unmapped_files`: every differing path outside that map, including shared
|
|
499
|
+
CSS, scripts and assets. These changes still make build-bound evidence owed.
|
|
500
|
+
|
|
501
|
+
Paths are relative to `_site/<public_route_slug>/` and sorted; page IDs use
|
|
502
|
+
`resolveBuiltSiteScope`'s route-derived identities (nested routes use hyphens).
|
|
503
|
+
Removed HTML uses that same route/ID projection. Empty lists with
|
|
504
|
+
`changes.status: unknown` do not establish that no pages changed.
|
|
505
|
+
A missing or unusable manifest makes only `changes.status` unknown, with
|
|
506
|
+
`build_manifest_unavailable` as its reason. A manifest is usable only if its
|
|
507
|
+
entries hash to the assembly's recorded fingerprint.
|
|
508
|
+
If the built page map cannot be resolved (for example, output contains assets
|
|
509
|
+
but no HTML), page changes are unknown with `build_output_pages_unresolved`;
|
|
510
|
+
stage bindings still follow the fingerprint comparison.
|
|
511
|
+
|
|
512
|
+
`changes.kind` classifies this drift (`none`, `page_only`,
|
|
513
|
+
`commerce_affecting` or `unknown`) and `changes.reasons` lists why it counts as
|
|
514
|
+
commerce, each `{ "code", "path"? }`. The rule is in
|
|
515
|
+
[what a change owes](campaigns-os-build-flow.md#what-a-change-owes).
|
|
516
|
+
|
|
517
|
+
The assembly baseline is explicit because it need not equal an older stage's
|
|
518
|
+
binding. If a later `record build` replaced that manifest, `changes` can be
|
|
519
|
+
empty while older Polish/QA bindings are still owed. An empty **known** list then means only that
|
|
520
|
+
no files differ from the retained assembly manifest. The pages changed since
|
|
521
|
+
the build a stage reviewed are on that stage instead. An owed stage carries:
|
|
522
|
+
|
|
523
|
+
- `change`: `{ "kind", "since_build_fingerprint", "changed_pages",
|
|
524
|
+
"unmapped_files", "reasons" }`, read from the rebuilds `record build` kept in
|
|
525
|
+
`stages.assembly.build_changes` plus the drift on disk. `kind` is `unknown`
|
|
526
|
+
when that history does not reach the reviewed build (reason
|
|
527
|
+
`change_history_unavailable`) or the drift cannot be read. `null` on a stage
|
|
528
|
+
that is not owed.
|
|
529
|
+
- Polish only, `advisory`: `true` when the change is `page_only` and no brief or
|
|
530
|
+
spec change requires Polish. Advisory Polish is reported; `next`, `doctor
|
|
531
|
+
--ci` and the QA gate do not require it.
|
|
532
|
+
- QA only, `scope` and `owed_pages`: `pages` with the changed pages when the
|
|
533
|
+
change is `page_only` (`qa run --page` pays it), `full` otherwise, `null`
|
|
534
|
+
when QA is not owed.
|
|
535
|
+
|
|
536
|
+
If recording removed a
|
|
537
|
+
stage binding entirely, that stage reads unknown unless its recorded status is
|
|
538
|
+
`required`, which reports owed. `current` is a build-binding
|
|
539
|
+
assessment only; input currency, stage completion, waivers and verdicts remain
|
|
540
|
+
separate.
|
|
541
|
+
|
|
400
542
|
### `divergences`
|
|
401
543
|
|
|
402
544
|
Where two artifacts record different states for the same stage: the assembly
|
|
@@ -413,7 +555,7 @@ failure lines up with a completed stage. This too is the readback's own layer.
|
|
|
413
555
|
|
|
414
556
|
## `clean`
|
|
415
557
|
|
|
416
|
-
`clean` is true if and only if **all
|
|
558
|
+
`clean` is true if and only if **all six** of the following hold:
|
|
417
559
|
|
|
418
560
|
1. **Every artifact the readback found is loaded and recognized.** Formally: no
|
|
419
561
|
artifact is in state `unreadable` or `unrecognized`. An `absent` artifact
|
|
@@ -439,6 +581,12 @@ failure lines up with a completed stage. This too is the readback's own layer.
|
|
|
439
581
|
failed to check.
|
|
440
582
|
4. **`divergences` is empty.**
|
|
441
583
|
5. **`doctor.error_count` is zero.**
|
|
584
|
+
6. **Neither `evidence_debt.polish.status` nor `evidence_debt.qa.status` is
|
|
585
|
+
`owed`**, except Polish marked `advisory` for a page-only change. Built
|
|
586
|
+
output that moved since recorded evidence cannot read clean.
|
|
587
|
+
An `unknown` status does not independently make this integrity flag false;
|
|
588
|
+
it remains unknown, never a claim that no evidence is owed. Callers must
|
|
589
|
+
inspect `evidence_debt` even when `clean` is true.
|
|
442
590
|
|
|
443
591
|
Doctor warnings — of either group — do not affect `clean`. Neither does a
|
|
444
592
|
blocked QA verdict, a blocked assembly report, or a non-empty `skip_cascades`.
|
|
@@ -449,7 +597,7 @@ blocked QA verdict, a blocked assembly report, or a non-empty `skip_cascades`.
|
|
|
449
597
|
campaign. It means: the readback read every artifact that was there, understood
|
|
450
598
|
all of them, can show — for every artifact that recorded an age — that none of
|
|
451
599
|
them is older than the checkout, found no contradiction between them, and saw no
|
|
452
|
-
doctor error. Campaigns OS remains the
|
|
600
|
+
doctor error, and found no known build-bound evidence debt. Campaigns OS remains the
|
|
453
601
|
lifecycle and verdict authority; the readback never reinterprets a verdict.
|
|
454
602
|
|
|
455
603
|
The practical consequence for a caller: `clean: true` says the artifacts are
|
|
@@ -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.3`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.
|
|
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.62.0`.
|
|
12
12
|
|
|
13
13
|
## What this is
|
|
14
14
|
|
|
@@ -81,8 +81,9 @@ 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 50 enumerated files:
|
|
85
85
|
|
|
86
|
+
- `campaign-spec/analytics-vendors.ts`
|
|
86
87
|
- `campaign-spec/analytics-vocabulary.ts`
|
|
87
88
|
- `campaign-spec/checkout-flow.ts`
|
|
88
89
|
- `campaign-spec/index.ts`
|
|
@@ -91,9 +92,11 @@ Fingerprint algorithm `sha256`, over 47 enumerated files:
|
|
|
91
92
|
- `campaign-spec/routing.ts`
|
|
92
93
|
- `campaign-spec/rules/analytics-contract-shape.ts`
|
|
93
94
|
- `campaign-spec/rules/assembly-hints-shape.ts`
|
|
95
|
+
- `campaign-spec/rules/campaign-currencies.ts`
|
|
94
96
|
- `campaign-spec/rules/campaign-metadata.ts`
|
|
95
97
|
- `campaign-spec/rules/checkout-forward-target.ts`
|
|
96
98
|
- `campaign-spec/rules/checkout-has-success-url.ts`
|
|
99
|
+
- `campaign-spec/rules/checkout-path-has-packages.ts`
|
|
97
100
|
- `campaign-spec/rules/checkout-step-reaches-checkout.ts`
|
|
98
101
|
- `campaign-spec/rules/cycle-detection.ts`
|
|
99
102
|
- `campaign-spec/rules/design-source-shape.ts`
|
|
@@ -137,7 +140,7 @@ The output directory is a build product. It is untracked and git-ignored, no com
|
|
|
137
140
|
|
|
138
141
|
Directory `campaign-spec/dist`. Committed: `false`. Type entry `campaign-spec/dist/index.d.ts`.
|
|
139
142
|
|
|
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
|
|
143
|
+
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 92 files.
|
|
141
144
|
|
|
142
145
|
### Mandatory checks
|
|
143
146
|
|
|
@@ -8,7 +8,7 @@ npx --no-install campaigns-os sdk storage-check --target . --target-sdk 0.4.38 -
|
|
|
8
8
|
|
|
9
9
|
Omit `--json` for the concise human report. Exit 0 means source-compatible; exit 2 means incompatible or unknown; invalid arguments or manifests exit 1. No merchant files or pins are rewritten. This is independent of doctor's built HTML markup check.
|
|
10
10
|
|
|
11
|
-
The manifest is generated and owned by Campaign Cart, from its storage registry. The scanner has no independent SDK key list and fetches no remote code. Supply `docs/compatibility/storage-migrations.v1.json` from an unmodified checkout of a Campaign Cart release tag, v0.4.40 or later. The report records the full file SHA-256, SDK version, supported target range, registry/extractor/input digest, and Git provenance. When the supplied bytes equal their repository HEAD blob, provenance is `verified-git-blob` with commit and repository path. Proposed, modified, or copied manifests are labeled `unverified-local-file`; the label never certifies a release. Review/pin SDK provenance separately before acting on findings. A release manifest covers the target range it declares, which may end below its own SDK version: the v0.4.40 manifest, for example, covers 0.4.38 only. Targets outside the manifest's supported SDK range report unknown. A manifest whose SDK version is below its supported maximum is invalid.
|
|
11
|
+
The manifest is generated and owned by Campaign Cart, from its storage registry. The scanner has no independent SDK key list and fetches no remote code. Supply `docs/compatibility/storage-migrations.v1.json` from an unmodified checkout of a Campaign Cart release tag, v0.4.40 or later. The report records the full file SHA-256, SDK version, supported target range, registry/extractor/input digest, and Git provenance. When the supplied bytes equal their repository HEAD blob, provenance is `verified-git-blob` with commit and repository path. Proposed, modified, or copied manifests are labeled `unverified-local-file`; the label never certifies a release. Review/pin SDK provenance separately before acting on findings. A release manifest covers the target range it declares, which may end below its own SDK version: the v0.4.40 manifest, for example, covers 0.4.38 only. Targets outside the manifest's supported SDK range report unknown (exit 2), and the human report says so before anything else: its first line names the range the manifest covers and the requested target it does not, and the count line repeats it. With a v0.4.43 manifest, `--target-sdk 0.4.43` reads `checked against SDK 0.4.38, not the requested target 0.4.43`. `--json` carries `targetCovered: false`. A manifest whose SDK version is below its supported maximum is invalid.
|
|
12
12
|
|
|
13
13
|
`--target` must name the Git root. `--scope` is mandatory: comma-separated literal repository-relative directory/file paths; use `.` only when the complete repository is intended. Add shared JavaScript directories explicitly. `--exclude` uses the same syntax and records explicit exclusions. Archives receive no implicit exemption. Only Git-tracked `.html`, `.htm`, `.js`, `.mjs`, and `.cjs` files in the selected scope are scanned, using current working-tree bytes and per-file digests; untracked files and built dependencies are outside this evidence. A selected HTML page's local script outside the selected files reports unknown. In a Page Kit campaign (a page under `src/<slug>/`), a script src written `{{ 'js/x.js' | campaign_asset }}` resolves to `src/<slug>/assets/js/x.js`, as Page Kit serves it, and a page's frontmatter `scripts:` entries resolve the same way, since a layout's `{% for script in scripts %}` loop loads them through that filter. Any other `campaign_asset` value, a path that resolves outside the assets folder, and frontmatter that is not valid YAML or whose `scripts` is not a list of paths report unknown. Relative scripts affected by an HTML `<base href>` also report unknown; review their actual dependency paths. Remote SDK and third-party scripts are not fetched or analyzed.
|
|
14
14
|
|
|
@@ -16,7 +16,9 @@ Acorn parses JavaScript ASTs; parse5 identifies executable inline HTML scripts a
|
|
|
16
16
|
|
|
17
17
|
Dynamic storage keys, unresolved storage methods, shadowed storage names, missing migration release evidence, unsupported target versions, and JavaScript parse failures report unknown. This bounded analysis does not execute merchant code or prove computed keys correct. It does not follow arbitrary data flow, dynamic imports, event-handler attributes, generated code or runtime-loaded script graphs. Inspect unresolved findings and include all campaign sources in scope; a clean result is only static tracked source compatibility evidence. Browser behavior, order creation, tracking and analytics still require their own proof, especially where multiple campaigns share an origin. Use the SDK public store API where the manifest recommends it; do not invent a prefix-first merchant repair.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
A `dynamic-key`, `unsupported-storage-method` or `storage-object-escape` line inside a loop, or an array callback such as `filter` or `forEach`, that tests a key it read from storage (a `k = sessionStorage.key(i)` variable, a `for…of Object.keys(sessionStorage)` or `for…in sessionStorage` variable, or the element parameter, the second for `reduce`, of a callback chained on `Object.keys(sessionStorage)`) with `k.indexOf('<prefix>') === 0` (or `!== 0`) or `k.startsWith('<prefix>')`, where the prefix is a scoped SDK key from the manifest (`next-cart-state` for `next-cart-state{__scope}`), ends in `[prefix loop: <prefix>]` and carries `prefixScan` in `--json`, when the line is where that key came from or reads that key by name. Other storage use in the loop, such as passing `sessionStorage` to a function, is not marked: the prefix test does not explain it. One note below the findings explains them all. Such a loop finds the SDK key with or without its scope suffix, so scoping alone does not break it. The lines still report unknown: Campaign Cart scopes these keys so campaigns on one origin cannot read each other's copy, and a prefix match reads whichever copy it meets first. Where the manifest names a public store for the key, as it does `useOrderStore` for `next-order`, the note names it too.
|
|
20
|
+
|
|
21
|
+
Storage `length` property reads are unaffected metadata (`storage-metadata-read`). Storage `key()` enumeration, extracted method references, and mutations of reserved members remain unknown; enumeration cannot prove an SDK key lookup safe. Git discovery failures return one bounded CLI error. The tracked-file inventory lists only the paths under `--scope`, so repository size does not limit it; when it fails, the error reads `Cannot inventory tracked Git source files: <reason>` with Git's own first stderr line as the reason, or, when Git printed nothing, how it ended (its exit status, the signal that stopped it, or a spawn error code), or a request to narrow a scope whose list passes 256 MiB. npm 1.61.0 and earlier listed the whole repository and stopped with `Cannot inventory tracked Git source files.` once that list passed 1 MiB (about 25,000 paths); update the install rather than running the check on a copy, which can lose tracked files. Local script diagnostics retain repository-relative spellings from HTML source, including `../` paths; excluded/outside sources are not read. The Git root and operator target are compared by filesystem device/inode identity, so supported case aliases and symlinks can name the same root. Source containment uses realpaths below the Git root without case folding. Extra positional CLI arguments are rejected so an accidental scope or version value cannot silently disappear.
|
|
20
22
|
|
|
21
23
|
## Rewriting hardcoded pins in a static repo
|
|
22
24
|
|
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.62.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.62.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.62.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.62.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.62.0+skills.1",
|
|
94
94
|
"spelling": "bundle",
|
|
95
|
-
"on_disk": "1.
|
|
95
|
+
"on_disk": "1.62.0+skills.1",
|
|
96
96
|
"on_disk_skill": null,
|
|
97
|
-
"message": "match (1.
|
|
97
|
+
"message": "match (1.62.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.62.0+skills.1)
|
|
105
|
+
Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.62.0+skills.1 — start a fresh session
|
|
106
|
+
Skills revision: unchecked (on disk 1.62.0+skills.1)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
`unchecked` is the state when the flag is absent. It is not an error — an
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Source Adapters
|
|
2
|
+
|
|
3
|
+
The current release uses `html_funnel`: prepared HTML/CSS/assets provided by the developer.
|
|
4
|
+
|
|
5
|
+
The adapter is deliberately explicit so the Build Packet, doctor, build, polish, deploy, and QA gates can reason about the source pages the same way every time.
|
|
6
|
+
|
|
7
|
+
> **Where does the source HTML come from?** That's the entry-point question, covered in a separate doc: [docs/entry-points.md](./entry-points.md) names the five recognized shapes (template-stock, Figma-driven, AI-generated, hand-authored, mixed) and explains how each populates the inputs this adapter consumes.
|
|
8
|
+
|
|
9
|
+
Source adapter inputs are compatibility contracts, not the final design-source
|
|
10
|
+
contract. `prepare-build` may translate them into Design Source Contributions in
|
|
11
|
+
the public Design Source Package while keeping the existing source-html manifest
|
|
12
|
+
available for current producers.
|
|
13
|
+
|
|
14
|
+
## `html_funnel`
|
|
15
|
+
|
|
16
|
+
Input:
|
|
17
|
+
|
|
18
|
+
- local folder
|
|
19
|
+
- one or more `.html` pages
|
|
20
|
+
- assets referenced by those pages
|
|
21
|
+
- explicit or inferred page mapping to CampaignSpec pages
|
|
22
|
+
- Page Kit target projection in `source_html.pages[].page_kit` after
|
|
23
|
+
`prepare-build`
|
|
24
|
+
|
|
25
|
+
When no source-html manifest exists, `prepare-build` may infer page mappings
|
|
26
|
+
from filenames. If more than one HTML file can satisfy the same active
|
|
27
|
+
CampaignSpec page, or if every matching file was already assigned to a sibling
|
|
28
|
+
page, Campaigns OS blocks `prepare_build`, records
|
|
29
|
+
`context.source.ambiguous_candidates`, and drafts
|
|
30
|
+
`context.source.manifest_draft`. Copy that draft to
|
|
31
|
+
`.campaigns-os/source-html-manifest.json`, choose the intended candidate path for
|
|
32
|
+
each ambiguous page, and rerun `start`/`prepare-build`. The fallback matcher
|
|
33
|
+
should not silently choose among ambiguous source or asset-mirror pages.
|
|
34
|
+
|
|
35
|
+
The `.html` files should be prepared for page-kit ingestion. This does not mean
|
|
36
|
+
"rewrite everything into Liquid." Page Kit source files are HTML files with
|
|
37
|
+
optional YAML frontmatter and optional Liquid filters/includes. Liquid is only
|
|
38
|
+
needed where the page must call page-kit helpers such as `campaign_link`,
|
|
39
|
+
`campaign_asset`, or `campaign_include`.
|
|
40
|
+
|
|
41
|
+
`source_html.pages[].path` is the source file path relative to the source root.
|
|
42
|
+
For mixed producers it may point at `figma-export/landing.html` or
|
|
43
|
+
`checkout/index.html`. Build agents should use the sibling `page_kit` block for
|
|
44
|
+
the target Page Kit file, route, CPK `page_type`, and frontmatter projection.
|
|
45
|
+
|
|
46
|
+
## Adapter Decisions
|
|
47
|
+
|
|
48
|
+
`prepare-build` records the reusable conversion contract in three places:
|
|
49
|
+
|
|
50
|
+
- `packet.source_html.adapter_contract`
|
|
51
|
+
- `context.adapter_decisions`
|
|
52
|
+
- `report.adapter_decisions`
|
|
53
|
+
|
|
54
|
+
These fields are intentionally machine-readable so doctor can name unfinished
|
|
55
|
+
work instead of relying on chat history. After the build, record each true
|
|
56
|
+
scalar choice with `record build --adapter-decision <key>=<value>`; put several
|
|
57
|
+
choices in one comma-separated flag. A repeated flag keeps only the last list.
|
|
58
|
+
`wrapper_policy` is selected at intake, not through this record flag.
|
|
59
|
+
|
|
60
|
+
- `raw_html_conversion_status`: wrapper stripping, frontmatter, asset moves,
|
|
61
|
+
script/style refs, CTA rewrites, route policy, and layout choice are still
|
|
62
|
+
`pending` until build records `completed` or `not_required` with
|
|
63
|
+
`record build --adapter-decision raw_html_conversion_status=<value>`.
|
|
64
|
+
- `source_asset_strategy`: page-kit campaigns should normally use
|
|
65
|
+
`pagekit_campaign_asset_root`, where `src/<slug>/assets/*` publishes as
|
|
66
|
+
`/<slug>/*`.
|
|
67
|
+
- `commerce_shell_adoption`: runtime commerce pages should be
|
|
68
|
+
`template_clone_first_verified` or `sdk_surfaces_preserved` before a completed
|
|
69
|
+
build handoff. `custom_html_experimental` is a doctor blocker on runtime
|
|
70
|
+
pages.
|
|
71
|
+
- `route_rewrite_policy`: record whether CampaignSpec routes and CTAs were
|
|
72
|
+
rewritten through campaign-aware routes/helpers.
|
|
73
|
+
- `template_files_copied`: records the selected template family as an atomic
|
|
74
|
+
slice. A complete/verified slice covers `pages`, `_includes`, `_layouts`,
|
|
75
|
+
`assets/css`, `assets/js`, and `frontmatter_vocabulary`; its `paths` are
|
|
76
|
+
target-repo-relative proof paths that doctor checks once assembly is complete.
|
|
77
|
+
- `config_script_strategy`: records how campaign config scripts load
|
|
78
|
+
(`campaign_asset`, `frontmatter_script`, `inline`, or `not_required`).
|
|
79
|
+
- `wrapper_policy`: records whether document wrappers were stripped, preserved,
|
|
80
|
+
or not required (`strip_document_wrappers`, `preserve_document_wrappers`,
|
|
81
|
+
`not_required`, `unknown`). Unlike the other decisions here, this one is
|
|
82
|
+
selectable before the build stage runs — see
|
|
83
|
+
[Selecting the wrapper policy at intake](#selecting-the-wrapper-policy-at-intake).
|
|
84
|
+
- `frontmatter_policy`: records whether Page Kit YAML frontmatter was created,
|
|
85
|
+
preserved, or intentionally not required.
|
|
86
|
+
- `script_style_reference_policy`: records whether scripts/styles were moved to
|
|
87
|
+
frontmatter, campaign assets, inline blocks, or raw passthrough.
|
|
88
|
+
- `cta_rewrite_policy`: records how CTA destinations were rewritten from
|
|
89
|
+
CampaignSpec routes.
|
|
90
|
+
- `layout_choice`: records the Page Kit layout strategy used for the prepared
|
|
91
|
+
source.
|
|
92
|
+
|
|
93
|
+
Raw AI-generated HTML normally needs this conversion pass before build:
|
|
94
|
+
|
|
95
|
+
1. Keep the page-owned body markup that represents the design intent.
|
|
96
|
+
2. Remove document wrappers such as `<!doctype>`, `<html>`, `<head>`, and `<body>` so the page can be wrapped by the campaign layout.
|
|
97
|
+
3. Add YAML frontmatter for `title`, the `page_kit.frontmatter.page_type`, route/permalink or `next_url`, and any `styles`/`scripts`.
|
|
98
|
+
4. Move reusable `<style>` blocks into `src/<slug>/assets/css/...` and reference them from frontmatter; keep tiny page-specific styles inline only when that matches the target campaign style.
|
|
99
|
+
5. Move referenced images/fonts/assets into the campaign asset tree and use `campaign_asset` when paths need to be campaign-rooted. Page Kit copies `src/<slug>/assets/*` to the campaign root in built output: `src/<slug>/assets/config.js` renders as `/<slug>/config.js`, and `src/<slug>/assets/products/foo.png` renders as `/<slug>/products/foo.png`. Do not leave raw `/assets/...` or `/<slug>/assets/...` references in rendered pages unless that nested `assets/` directory exists intentionally.
|
|
100
|
+
6. Replace internal page links and CTA destinations with CampaignSpec-derived routes, usually through `campaign_link`.
|
|
101
|
+
7. For checkout, upsell, downsell, receipt, payment, totals, and submit controls, preserve or recreate the starter-template SDK contract rather than trusting raw source commerce markup.
|
|
102
|
+
8. Run page-kit build and inspect `_site/<slug>/` for a complete body, expected meta tags, campaign-rooted links, and SDK runtime markers.
|
|
103
|
+
|
|
104
|
+
A single-file static landing page is a good source artifact, but it is not
|
|
105
|
+
automatically a complete page-kit page.
|
|
106
|
+
|
|
107
|
+
## Source preparation check
|
|
108
|
+
|
|
109
|
+
The preparation steps above used to be tacit knowledge; the doctor now checks
|
|
110
|
+
them deterministically (#262). The check runs as the `source_html.preparation`
|
|
111
|
+
slot in the Doctor Check Registry (phase `source`) on every mapped
|
|
112
|
+
`source_html.pages[].path` file, so `start` and `build` — which embed doctor —
|
|
113
|
+
gate on it before assembly, and a plain `campaigns-os doctor` re-checks the
|
|
114
|
+
current files after source edits. Blocking findings are doctor **errors**: they
|
|
115
|
+
set status `blocked` and make doctor's `next` block name the same stage the
|
|
116
|
+
`next` command would (`prepare-build` while the prepare-build stage records
|
|
117
|
+
the blockers, `doctor-blocked` otherwise), like any other unprepared input. The check is scoped to the certified template families'
|
|
118
|
+
page-kit ingestion expectations; it is not a general HTML linter.
|
|
119
|
+
|
|
120
|
+
| Code | Severity | Meaning and fix |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| `source_html.prep.document_wrapper` | error (warning under a recorded `wrapper_policy: "preserve_document_wrappers"`) | A mapped source page is still a full browser document. Once the converted page-kit page exists at the page's `page_kit.output_path`, that page is checked instead of the design, since it is the file page-kit builds. Strip `<!doctype>`, `<html>`, `<head>`, and `<body>` so the campaign layout can wrap the page (conversion step 2; [docs/quickstart.md](./quickstart.md) "Prepare Raw HTML Source"), or record `preserve_document_wrappers` as an explicit adapter decision — the manifest `wrapper_policy` key or `--wrapper-policy`, see [Selecting the wrapper policy at intake](#selecting-the-wrapper-policy-at-intake). |
|
|
123
|
+
| `source_html.prep.frontmatter_residue` | error | Leftover or broken YAML frontmatter: a leading `---` fence that never closes, or a frontmatter block (page-kit keys such as `page_type:`, `next_url:`) embedded below content where page-kit would render it literally. Keep exactly one closed frontmatter block at the very top of the file, or none when the packet's `page_kit.frontmatter` projection supplies it (conversion step 3). |
|
|
124
|
+
| `source_html.prep.internal_link_unrooted` | warning | An internal link/CTA still targets a source file (`checkout.html`) instead of a CampaignSpec-derived route. Replace it, usually via `campaign_link` (conversion step 6). Warning rather than error because CTA rewrites are sanctioned build-stage work recorded under `cta_rewrite_policy`; the adapter gates own the completed-assembly block. |
|
|
125
|
+
|
|
126
|
+
### Selecting the wrapper policy at intake
|
|
127
|
+
|
|
128
|
+
`preserve_document_wrappers` is a real choice, so whoever hands over the source
|
|
129
|
+
can make it — without editing the Build Packet the build stage writes. Two
|
|
130
|
+
channels select it, both in the vocabulary above:
|
|
131
|
+
|
|
132
|
+
**The source-html manifest.** Declare it once, next to the pages it applies to,
|
|
133
|
+
in `.campaigns-os/source-html-manifest.json`:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"schema_version": "source-html-manifest/v0",
|
|
138
|
+
"wrapper_policy": "preserve_document_wrappers",
|
|
139
|
+
"pages": [{ "page_id": "landing", "path": "landing.html" }]
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**The CLI flag.** Select it per run on the command that prepares the build:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
campaigns-os prepare-build --spec <spec.json> --source <html-dir> \
|
|
147
|
+
--target <page-kit-dir> --template-family <family> \
|
|
148
|
+
--wrapper-policy preserve_document_wrappers
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`start` and `build` take the same flag, since both run the prepare step.
|
|
152
|
+
|
|
153
|
+
**Order of operations.** Decide the wrapper policy before you capture
|
|
154
|
+
`source_screenshot` proof or record `source_hash`, because both describe the
|
|
155
|
+
file as it is handed over. `source_hash` is the sha256 of the source file at
|
|
156
|
+
manifest generation time; `doctor` recomputes it and reports
|
|
157
|
+
`source_html.pages.source_hash` when the file no longer matches. So under the
|
|
158
|
+
default `strip_document_wrappers` policy, strip first, then capture the
|
|
159
|
+
screenshots and compute the hashes of the stripped files. Under
|
|
160
|
+
`preserve_document_wrappers` nothing is stripped, the existing screenshots and
|
|
161
|
+
hashes stay valid, and the design-source gate and the preparation check read
|
|
162
|
+
the same file. Stripping after the manifest was written invalidates its hashes
|
|
163
|
+
and its screenshots, and a stripped page is no longer the standalone document
|
|
164
|
+
its `source_screenshot` proof was captured from.
|
|
165
|
+
|
|
166
|
+
**Precedence.** The flag wins over the manifest key, and with neither the
|
|
167
|
+
default is `strip_document_wrappers` — the same order the template family uses
|
|
168
|
+
for its CLI override and its spec hint (see
|
|
169
|
+
[docs/build-packet.md](./build-packet.md), "Authoring-Time Hints"). The
|
|
170
|
+
resolved value is written to
|
|
171
|
+
`source_html.adapter_contract.wrapper_policy` on the packet, and a non-default
|
|
172
|
+
selection is echoed on stderr with the channel that set it. A value outside the
|
|
173
|
+
vocabulary fails the run when it comes from the flag. From the manifest key it
|
|
174
|
+
does not: the manifest is used as written — `pages[]`, `producer_provenance`
|
|
175
|
+
and `files[]` all survive — the key alone is ignored, the default policy
|
|
176
|
+
applies, and one warning names the key, the value, and the accepted values.
|
|
177
|
+
Dropping a whole manifest over one optional hint would cost far more than the
|
|
178
|
+
hint is worth.
|
|
179
|
+
|
|
180
|
+
Selecting `preserve_document_wrappers` does not silence the finding: the
|
|
181
|
+
document wrappers are still reported on every page that carries them, as a
|
|
182
|
+
warning rather than a blocking error.
|
|
183
|
+
|
|
184
|
+
Asset-path rooting (raw `/assets/...` references, missing or out-of-root
|
|
185
|
+
files) stays owned by the prepare-build source asset crawl and its
|
|
186
|
+
`source_asset.*` codes (conversion step 5); the preparation check does not
|
|
187
|
+
duplicate them.
|
|
188
|
+
|
|
189
|
+
Known-unprepared and prepared reference fixtures live at
|
|
190
|
+
`fixtures/source-prep/`; `src/source-prep.test.mjs` and
|
|
191
|
+
`src/doctor-source-prep.test.mjs` pin each code and the pass path.
|
|
192
|
+
|
|
193
|
+
Wedge relevance: the Stage 0 wedge run protocol (#259) includes this check —
|
|
194
|
+
wedge runs treat a `source_html.prep.*` blocker as a classified workflow
|
|
195
|
+
signal at the source-preparation handoff, not founder-interpretation territory.
|
|
196
|
+
|
|
197
|
+
Checkout, cart, upsell, receipt, payment, totals, and submit behavior should remain governed by starter-template contracts and SDK wiring. Source files can carry page design, but live commerce behavior comes from the campaign setup and runtime surfaces.
|
|
198
|
+
|
|
199
|
+
If a source page marks a region as SDK-owned, for example with
|
|
200
|
+
`data-commerce-zone="checkout-form"` or comments like `SDK-OWNED: ... provided
|
|
201
|
+
by the limos checkout commerce surface`, treat that as an instruction to adopt
|
|
202
|
+
the selected starter-template family shell for that runtime surface. It is not
|
|
203
|
+
permission to invent a custom checkout shell and paste only borrowed partials
|
|
204
|
+
inside it. `prepare-build` records these regions in the Build Context, and
|
|
205
|
+
doctor warns until the build path has a clear shell-adoption decision.
|
|
206
|
+
|
|
207
|
+
Template families are not single-file dependencies. If setup copies a starter
|
|
208
|
+
family into a target campaign, copy the matching pages together with their
|
|
209
|
+
family `_includes/`, `_layouts/`, `assets/css/`, and `assets/js/` dependencies.
|
|
210
|
+
Copying only `checkout.html` and `receipt.html` can leave Liquid includes,
|
|
211
|
+
layouts, CSS, or JavaScript unresolved. Record the copied or verified target
|
|
212
|
+
paths in `template_files_copied.paths`; a completed report with missing paths is
|
|
213
|
+
not doctor-clean.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextcommerce/campaigns-os",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.62.0",
|
|
4
4
|
"description": "Toolkit for agent-assisted NEXT campaign builds.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -104,7 +104,9 @@
|
|
|
104
104
|
"yaml": "^2.9.1"
|
|
105
105
|
},
|
|
106
106
|
"optionalDependencies": {
|
|
107
|
-
"
|
|
107
|
+
"pixelmatch": "^7.2.0",
|
|
108
|
+
"playwright": "^1.63.0",
|
|
109
|
+
"pngjs": "^7.0.0"
|
|
108
110
|
},
|
|
109
111
|
"devDependencies": {
|
|
110
112
|
"typescript": "^5.9.3"
|
|
@@ -153,6 +155,11 @@
|
|
|
153
155
|
"docs/progress-snapshots.md",
|
|
154
156
|
"demo",
|
|
155
157
|
"docs/demo-preview.md",
|
|
156
|
-
"docs/local-setup.md"
|
|
158
|
+
"docs/local-setup.md",
|
|
159
|
+
"docs/quickstart.md",
|
|
160
|
+
"docs/access-model.md",
|
|
161
|
+
"docs/entry-points.md",
|
|
162
|
+
"docs/source-adapters.md",
|
|
163
|
+
"docs/campaignspec-authoring-examples.md"
|
|
157
164
|
]
|
|
158
165
|
}
|
|
@@ -272,8 +272,15 @@
|
|
|
272
272
|
"sha256": { "$ref": "#/$defs/sha256" },
|
|
273
273
|
"role": { "type": "string", "minLength": 1 },
|
|
274
274
|
"media_type": { "type": "string", "minLength": 1 },
|
|
275
|
+
"design_file_type": { "type": "string", "minLength": 1, "pattern": "\\S" },
|
|
275
276
|
"notes": { "type": ["string", "null"] }
|
|
276
277
|
},
|
|
278
|
+
"allOf": [
|
|
279
|
+
{
|
|
280
|
+
"if": { "properties": { "design_file_type": true }, "required": ["design_file_type"] },
|
|
281
|
+
"then": { "properties": { "role": { "const": "design_file" } }, "required": ["role"] }
|
|
282
|
+
}
|
|
283
|
+
],
|
|
277
284
|
"anyOf": [
|
|
278
285
|
{ "properties": { "path": true }, "required": ["path"] },
|
|
279
286
|
{ "properties": { "url": true }, "required": ["url"] }
|
|
@@ -460,6 +467,7 @@
|
|
|
460
467
|
"screenshot_refs": { "$ref": "#/$defs/stringList" },
|
|
461
468
|
"reference_refs": { "$ref": "#/$defs/stringList" },
|
|
462
469
|
"template_reference_id": { "type": "string", "minLength": 1 },
|
|
470
|
+
"derived_from": { "type": "string", "minLength": 1, "pattern": "\\S" },
|
|
463
471
|
"notes": { "type": ["string", "null"] }
|
|
464
472
|
},
|
|
465
473
|
"allOf": [
|