@nextcommerce/campaigns-os 1.46.0 → 1.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +153 -3
  2. package/README.md +1 -1
  3. package/compatibility.json +1 -1
  4. package/contracts/commerce-surface-catalog.json +1204 -129
  5. package/contracts/effects.v1.json +3 -3
  6. package/contracts/release-ledger.json +338 -0
  7. package/contracts/supported-surface.json +2 -2
  8. package/contracts/template-brand-contract.shared-commerce.v0.json +3 -3
  9. package/docs/build-packet.md +22 -2
  10. package/docs/local-setup.md +7 -4
  11. package/docs/orientation-contract-reference.md +1 -1
  12. package/docs/qa-and-test-orders.md +19 -1
  13. package/docs/runtime-readiness.md +1 -1
  14. package/docs/sdk-storage-compatibility.md +1 -1
  15. package/docs/skills-revision.md +10 -10
  16. package/package.json +1 -1
  17. package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
  18. package/skills/campaign-readback-classification/SKILL.md +3 -3
  19. package/skills/campaign-run-evidence/SKILL.md +3 -3
  20. package/skills/contribution-intake/SKILL.md +3 -3
  21. package/skills/next-campaigns-build/SKILL.md +4 -4
  22. package/skills/next-campaigns-os/SKILL.md +3 -3
  23. package/skills/next-campaigns-os-setup/SKILL.md +3 -3
  24. package/skills/next-campaigns-polish/SKILL.md +3 -3
  25. package/skills/next-campaigns-qa/SKILL.md +4 -4
  26. package/skills.json +10 -10
  27. package/src/built-script-syntax.mjs +116 -15
  28. package/src/diagnostic.mjs +2 -1
  29. package/src/doctor/checks.mjs +0 -1
  30. package/src/qa-analytics-parity.mjs +37 -2
  31. package/src/qa-binding-evidence.mjs +4 -2
  32. package/src/qa-browser.mjs +74 -12
  33. package/src/sdk-markup.mjs +6 -45
  34. package/src/sdk-storage-compatibility.mjs +3 -2
  35. package/src/tooling-setup.mjs +9 -0
@@ -4486,7 +4486,7 @@
4486
4486
  ],
4487
4487
  "effect_test": "effects: start --force",
4488
4488
  "test_scope": "full",
4489
- "notes": "DESTRUCTIVE: --force overwrites an assembly report that carries stage evidence, printing the stage keys it cleared. This is the flag that makes `start` destructive; the base form refuses instead."
4489
+ "notes": "DESTRUCTIVE: --force overwrites an assembly report that carries stage evidence, printing the stage keys it cleared. This is the flag that makes `start` destructive; the base form refuses instead. It also regenerates a stale Design Source Package that this intake synthesized itself: the previous Assembly Report records origin \"synthesized\" and the package bytes still match it. An adopted or hand-edited package is never replaced. The declared writes already cover that path."
4490
4490
  },
4491
4491
  {
4492
4492
  "command": "start",
@@ -4998,7 +4998,7 @@
4998
4998
  ],
4999
4999
  "effect_test": "effects: prepare-build --force",
5000
5000
  "test_scope": "full",
5001
- "notes": "DESTRUCTIVE: --force overwrites an assembly report that carries stage evidence, printing the stage keys it cleared. This is the flag that makes `prepare-build` destructive; the base form refuses instead. Everything else it writes is the base form's intake."
5001
+ "notes": "DESTRUCTIVE: --force overwrites an assembly report that carries stage evidence, printing the stage keys it cleared. This is the flag that makes `prepare-build` destructive; the base form refuses instead. It also regenerates a stale Design Source Package that this intake synthesized itself: the previous Assembly Report records origin \"synthesized\" and the package bytes still match it. An adopted or hand-edited package is never replaced. The declared writes already cover that path. Everything else it writes is the base form's intake."
5002
5002
  },
5003
5003
  {
5004
5004
  "command": "build",
@@ -5412,7 +5412,7 @@
5412
5412
  ],
5413
5413
  "effect_test": "effects: build --force",
5414
5414
  "test_scope": "full",
5415
- "notes": "DESTRUCTIVE: --force overwrites an assembly report that carries stage evidence, printing the stage keys it cleared. This is the flag that makes `build` destructive; the base form refuses instead. Everything else it writes is the base form's intake."
5415
+ "notes": "DESTRUCTIVE: --force overwrites an assembly report that carries stage evidence, printing the stage keys it cleared. This is the flag that makes `build` destructive; the base form refuses instead. It also regenerates a stale Design Source Package that this intake synthesized itself: the previous Assembly Report records origin \"synthesized\" and the package bytes still match it. An adopted or hand-edited package is never replaced. The declared writes already cover that path. Everything else it writes is the base form's intake."
5416
5416
  },
5417
5417
  {
5418
5418
  "command": "spec",
@@ -3828,6 +3828,344 @@
3828
3828
  }
3829
3829
  ],
3830
3830
  "entry_sha256": "5c5b4362711e980c81eab5e1b643c42de1d8bfd74684dd73596f036925bfbeef"
3831
+ },
3832
+ {
3833
+ "id": "RL-0191",
3834
+ "sequence": 183,
3835
+ "date": "2026-10-01",
3836
+ "kind": "release",
3837
+ "surface_version": null,
3838
+ "changelog_section": "1.46.0+agent.4",
3839
+ "changelog_sha256": "894c809bfbd9a3345cd56d5ff7664730b9fc3035ee9181f735dae1527f975e2b",
3840
+ "agent_impact": "Skill text only. The next-campaigns-qa and next-campaigns-build skills now state that a required checkout field bound only on a type=\"hidden\" input, a disabled control, a read-only input or read-only textarea, or an aria-disabled=\"true\" control does not count as bound for browser-commerce-structure, and QA reports it in fields_bound.missing. Bundled skills carry revision 1.46.0+skills.2: an agent that loaded 1.46.0+skills.1 text should start a fresh session. QA behaviour is unchanged.",
3841
+ "compatibility": "compatible",
3842
+ "migration": "none",
3843
+ "changes": [
3844
+ {
3845
+ "class": "named_surface",
3846
+ "path": "docs/skills-revision.md",
3847
+ "surface_entry": "docs/skills-revision.md",
3848
+ "summary": "Use the current 1.46.0+skills.2 bundle in executable examples."
3849
+ },
3850
+ {
3851
+ "class": "skill",
3852
+ "path": "skills.json",
3853
+ "surface_entry": "skills.json",
3854
+ "summary": "Advance the bundled skill revision to 1.46.0+skills.2 and each skill version one patch."
3855
+ },
3856
+ {
3857
+ "class": "skill",
3858
+ "path": "skills/campaign-lifecycle-orientation/SKILL.md",
3859
+ "surface_entry": null,
3860
+ "summary": "Publish the 1.46.0+skills.2 bundle revision."
3861
+ },
3862
+ {
3863
+ "class": "skill",
3864
+ "path": "skills/campaign-readback-classification/SKILL.md",
3865
+ "surface_entry": null,
3866
+ "summary": "Publish the 1.46.0+skills.2 bundle revision."
3867
+ },
3868
+ {
3869
+ "class": "skill",
3870
+ "path": "skills/campaign-run-evidence/SKILL.md",
3871
+ "surface_entry": null,
3872
+ "summary": "Publish the 1.46.0+skills.2 bundle revision."
3873
+ },
3874
+ {
3875
+ "class": "skill",
3876
+ "path": "skills/contribution-intake/SKILL.md",
3877
+ "surface_entry": null,
3878
+ "summary": "Publish the 1.46.0+skills.2 bundle revision."
3879
+ },
3880
+ {
3881
+ "class": "skill",
3882
+ "path": "skills/next-campaigns-build/SKILL.md",
3883
+ "surface_entry": null,
3884
+ "summary": "State that a checkout field bound only on a hidden, disabled, read-only or aria-disabled control does not count as bound for QA; publish the 1.46.0+skills.2 bundle revision."
3885
+ },
3886
+ {
3887
+ "class": "skill",
3888
+ "path": "skills/next-campaigns-os-setup/SKILL.md",
3889
+ "surface_entry": null,
3890
+ "summary": "Publish the 1.46.0+skills.2 bundle revision."
3891
+ },
3892
+ {
3893
+ "class": "skill",
3894
+ "path": "skills/next-campaigns-os/SKILL.md",
3895
+ "surface_entry": null,
3896
+ "summary": "Publish the 1.46.0+skills.2 bundle revision."
3897
+ },
3898
+ {
3899
+ "class": "skill",
3900
+ "path": "skills/next-campaigns-polish/SKILL.md",
3901
+ "surface_entry": null,
3902
+ "summary": "Publish the 1.46.0+skills.2 bundle revision."
3903
+ },
3904
+ {
3905
+ "class": "skill",
3906
+ "path": "skills/next-campaigns-qa/SKILL.md",
3907
+ "surface_entry": null,
3908
+ "summary": "State that a required checkout field bound only on a hidden, disabled, read-only or aria-disabled control is not bound and is reported in fields_bound.missing; publish the 1.46.0+skills.2 bundle revision."
3909
+ }
3910
+ ],
3911
+ "entry_sha256": "8e891f36ce261c325f0837bf4c37ca145f60dbdd3ef55ab0ac9c4c4f3f92de7c"
3912
+ },
3913
+ {
3914
+ "id": "RL-0192",
3915
+ "sequence": 184,
3916
+ "date": "2026-10-01",
3917
+ "kind": "release",
3918
+ "surface_version": null,
3919
+ "changelog_section": "1.46.0+agent.5",
3920
+ "changelog_sha256": "16cbe0246a154be864bf97c671e3f82d4118aa8d7ed64722168fb756492c69a7",
3921
+ "agent_impact": "doctor no longer reports built_output.sdk_markup.checkout_bump_is_upsell. A selected checkout order bump is a line item on the checkout order; data-next-is-upsell=\"true\" tags that line as an upsell so platform order reports show upsell items apart from core items, and that is the intended default. Do not remove the attribute, and do not advise anyone to: it would report the bump as a core item. A bump include opts out with is_upsell: false. A run that recorded the warning under an earlier version can disregard it.",
3922
+ "compatibility": "compatible",
3923
+ "migration": "none",
3924
+ "changes": [
3925
+ {
3926
+ "class": "cli_surface",
3927
+ "path": "src/doctor/checks.mjs",
3928
+ "surface_entry": "doctor",
3929
+ "summary": "Stop passing each built page type to the SDK markup check, which no longer reads it now that built_output.sdk_markup.checkout_bump_is_upsell is retired."
3930
+ }
3931
+ ],
3932
+ "entry_sha256": "5f833a760aad0eada255118b29c4b2098fff4bfa4d364d31be121e7655a135ef"
3933
+ },
3934
+ {
3935
+ "id": "RL-0193",
3936
+ "sequence": 185,
3937
+ "date": "2026-10-01",
3938
+ "kind": "amendment",
3939
+ "amends": "RL-0187",
3940
+ "amendment_reason": "RL-0187 and its 1.45.0+agent.5 section told users to remove data-next-is-upsell=\"true\" from checkout bump includes. Tagging a checkout bump line as an upsell is the intended default, so order reports separate upsell items from core items; removing the attribute would report the bump as a core item. The section now says the advice was wrong and that the warning is retired in 1.46.0+agent.5; this amendment carries the corrected section hash.",
3941
+ "surface_version": null,
3942
+ "changelog_section": "1.45.0+agent.5",
3943
+ "changelog_sha256": "74564091423695f83383d296ce664656bc84cb9d21f7f044daffc0315d87110e",
3944
+ "agent_impact": "Disregard RL-0187's advice to remove data-next-is-upsell=\"true\" from a checkout bump. The upsell tag on the bump line is the intended default and the warning that recommended removing it is retired (RL-0192). RL-0187's theme generate and brand-theme-bridge changes stand.",
3945
+ "compatibility": "compatible",
3946
+ "migration": "none",
3947
+ "changes": [
3948
+ {
3949
+ "class": "cli_surface",
3950
+ "path": null,
3951
+ "surface_entry": "doctor",
3952
+ "summary": "Correct the recorded repair advice for built_output.sdk_markup.checkout_bump_is_upsell: the attribute is intended, and the warning is retired."
3953
+ }
3954
+ ],
3955
+ "entry_sha256": "ba5bdb8718c7b405c7cc60e1129f76ebe10011f099aa151bf893d617cc0c4270"
3956
+ },
3957
+ {
3958
+ "id": "RL-0194",
3959
+ "sequence": 186,
3960
+ "date": "2026-10-01",
3961
+ "kind": "release",
3962
+ "surface_version": null,
3963
+ "changelog_section": "1.46.0+agent.6",
3964
+ "changelog_sha256": "8cb11f8b85a7b9abe6791d75a7b7915491477bbe2d3e2f01dae2fb5ca458943a",
3965
+ "agent_impact": "sdk storage-check now accepts a release manifest whose SDK version is above its supported range, such as Campaign Cart v0.4.40 (range 0.4.38 only). Targets outside the declared range still report unknown; a manifest whose SDK version is below its supported maximum is still refused.",
3966
+ "compatibility": "compatible",
3967
+ "migration": "none",
3968
+ "changes": [
3969
+ {
3970
+ "class": "named_surface",
3971
+ "path": "docs/sdk-storage-compatibility.md",
3972
+ "surface_entry": "docs/sdk-storage-compatibility.md",
3973
+ "summary": "A release manifest covers the range it declares, which may end below its own SDK version; targets outside it report unknown."
3974
+ }
3975
+ ],
3976
+ "entry_sha256": "f064edf5031f8fd36197984145f5cbcfca3efd78a76982d3132e254b81964884"
3977
+ },
3978
+ {
3979
+ "id": "RL-0195",
3980
+ "sequence": 187,
3981
+ "date": "2026-10-01",
3982
+ "kind": "release",
3983
+ "surface_version": null,
3984
+ "changelog_section": "1.46.0+agent.8",
3985
+ "changelog_sha256": "c3aa9f4e74bac6c2dbeb05fea5bf7da84bebdb556244c7aafe8481a4248149e6",
3986
+ "agent_impact": "Install next-campaign-page-kit without --save-dev: the documented setup command now installs it with --save-exact as a runtime dependency and the toolkit with --save-dev --save-exact. tooling setup warns, and still proceeds, when page-kit is declared only in devDependencies.",
3987
+ "compatibility": "compatible",
3988
+ "migration": "none",
3989
+ "changes": [
3990
+ {
3991
+ "class": "named_surface",
3992
+ "path": "docs/local-setup.md",
3993
+ "surface_entry": "docs/local-setup.md",
3994
+ "summary": "Split the copy-paste install so page-kit is installed with --save-exact and stays in dependencies, while the toolkit keeps --save-dev --save-exact; note the setup warning for a dev-only page-kit."
3995
+ }
3996
+ ],
3997
+ "entry_sha256": "d13bc2cf8c66ea894b8929b103455153e2a7465e316e71d817ac310aabd58526"
3998
+ },
3999
+ {
4000
+ "id": "RL-0196",
4001
+ "sequence": 188,
4002
+ "date": "2026-10-01",
4003
+ "kind": "release",
4004
+ "surface_version": null,
4005
+ "changelog_section": "1.46.0+agent.9",
4006
+ "changelog_sha256": "6e2850725b14b051323f4a75652c02536ca7aafcd6771d317a07614fae6f6b4b",
4007
+ "agent_impact": "When qa run analytics parity picks its candidate automatically (no --analytics-candidate) and that page is not a receipt, a missing Purchase is now analytics-parity:purchase-present MANUAL_REVIEW/WARN with evidence.page_mismatch, not a blocker. Read page_mismatch and pass the candidate receipt with --analytics-candidate to compare Purchase. An explicit candidate still blocks on a missing Purchase.",
4008
+ "compatibility": "compatible",
4009
+ "migration": "none",
4010
+ "changes": [
4011
+ {
4012
+ "class": "named_surface",
4013
+ "path": "docs/qa-and-test-orders.md",
4014
+ "surface_entry": "docs/qa-and-test-orders.md",
4015
+ "summary": "Describe how the automatic parity candidate reports purchase-present when it is not a receipt page, and the page_mismatch evidence."
4016
+ }
4017
+ ],
4018
+ "entry_sha256": "044a9de53405d944e05045dc62f6f4575b0ebe387225b176904bdd8b02ddcf09"
4019
+ },
4020
+ {
4021
+ "id": "RL-0197",
4022
+ "sequence": 189,
4023
+ "date": "2026-10-01",
4024
+ "kind": "release",
4025
+ "surface_version": null,
4026
+ "changelog_section": "1.46.0+agent.11",
4027
+ "changelog_sha256": "28896ab18e07b78d21437c9b397dee67adb8bbc7791bb53ed1507113d638c5e1",
4028
+ "agent_impact": "Doctor's script syntax gate gives one built_output.script_syntax.missing_script warning per resolved URL, not per raw src spelling. A <script> left unclosed at end of file is no longer parsed by doctor or QA and cannot block; doctor warns under built_output.script_syntax.unclosed_script. A script symlink resolving outside the site root is not read; doctor warns under built_output.script_syntax.symlink_outside_site and lists it in scripts_outside_site[]. Symlinks inside the site root are read as before.",
4029
+ "compatibility": "additive",
4030
+ "migration": "none",
4031
+ "changes": [
4032
+ {
4033
+ "class": "named_surface",
4034
+ "path": "docs/build-packet.md",
4035
+ "surface_entry": "docs/build-packet.md",
4036
+ "summary": "The script syntax gate section records resolved-URL grouping of missing-script warnings, the unclosed-script rule and warning, and the symlink rule: followed inside the site root, warned about and not read outside it."
4037
+ }
4038
+ ],
4039
+ "entry_sha256": "9320297f8b6ea5866e56063b8dc4a364025e15d8963207d43e72396bd7eaf13b"
4040
+ },
4041
+ {
4042
+ "id": "RL-0198",
4043
+ "sequence": 190,
4044
+ "date": "2026-10-01",
4045
+ "kind": "release",
4046
+ "surface_version": "1.47.0",
4047
+ "changelog_section": "1.47.0",
4048
+ "changelog_sha256": "e8ea6bd06d018dd35b8f154c05e395034be3fb6a5476ab17ae00e511fabe16b8",
4049
+ "agent_impact": "Surface 1.47.0. The effects contract's --force notes on start, prepare-build and build now name the regeneration of a stale Design Source Package the intake synthesized itself (writes unchanged). compatibility.json names the package version, guarded by a test. Bundled skills carry revision 1.47.0+skills.1 with unchanged text: an agent that loaded 1.46.0+skills.2 should start a fresh session. Install the 1.47.0 package with the documented local setup command.",
4050
+ "compatibility": "compatible",
4051
+ "migration": "none",
4052
+ "changes": [
4053
+ {
4054
+ "class": "compatibility_policy",
4055
+ "path": "compatibility.json",
4056
+ "surface_entry": null,
4057
+ "summary": "Name the package version (1.47.0) instead of the stale 1.34.0."
4058
+ },
4059
+ {
4060
+ "class": "compatibility_policy",
4061
+ "path": "contracts/effects.v1.json",
4062
+ "surface_entry": "contracts/effects.v1.json",
4063
+ "summary": "Name the regeneration of a stale self-synthesized Design Source Package in the --force notes of start, prepare-build and build."
4064
+ },
4065
+ {
4066
+ "class": "compatibility_policy",
4067
+ "path": "contracts/supported-surface.json",
4068
+ "surface_entry": "contracts/supported-surface.json",
4069
+ "summary": "Advance the supported surface to 1.47.0 and refresh the effects contract hash."
4070
+ },
4071
+ {
4072
+ "class": "named_surface",
4073
+ "path": "docs/local-setup.md",
4074
+ "surface_entry": "docs/local-setup.md",
4075
+ "summary": "Pin the local setup install command to the 1.47.0 package."
4076
+ },
4077
+ {
4078
+ "class": "documentation",
4079
+ "path": "docs/orientation-contract-reference.md",
4080
+ "surface_entry": "docs/orientation-contract-reference.md",
4081
+ "summary": "Regenerate the orientation reference for surface 1.47.0."
4082
+ },
4083
+ {
4084
+ "class": "named_surface",
4085
+ "path": "docs/runtime-readiness.md",
4086
+ "surface_entry": "docs/runtime-readiness.md",
4087
+ "summary": "Regenerate the runtime readiness reference for surface 1.47.0."
4088
+ },
4089
+ {
4090
+ "class": "named_surface",
4091
+ "path": "docs/skills-revision.md",
4092
+ "surface_entry": "docs/skills-revision.md",
4093
+ "summary": "Use the current 1.47.0+skills.1 bundle in executable examples."
4094
+ },
4095
+ {
4096
+ "class": "generated_runtime",
4097
+ "path": "package-lock.json",
4098
+ "surface_entry": null,
4099
+ "summary": "Update release metadata for 1.47.0."
4100
+ },
4101
+ {
4102
+ "class": "package_export",
4103
+ "path": "package.json",
4104
+ "surface_entry": null,
4105
+ "summary": "Release 1.47.0."
4106
+ },
4107
+ {
4108
+ "class": "skill",
4109
+ "path": "skills.json",
4110
+ "surface_entry": "skills.json",
4111
+ "summary": "Advance the bundled skill revision to 1.47.0+skills.1 and each skill version one patch."
4112
+ },
4113
+ {
4114
+ "class": "skill",
4115
+ "path": "skills/campaign-lifecycle-orientation/SKILL.md",
4116
+ "surface_entry": null,
4117
+ "summary": "Publish the 1.47.0+skills.1 bundle revision."
4118
+ },
4119
+ {
4120
+ "class": "skill",
4121
+ "path": "skills/campaign-readback-classification/SKILL.md",
4122
+ "surface_entry": null,
4123
+ "summary": "Publish the 1.47.0+skills.1 bundle revision."
4124
+ },
4125
+ {
4126
+ "class": "skill",
4127
+ "path": "skills/campaign-run-evidence/SKILL.md",
4128
+ "surface_entry": null,
4129
+ "summary": "Publish the 1.47.0+skills.1 bundle revision."
4130
+ },
4131
+ {
4132
+ "class": "skill",
4133
+ "path": "skills/contribution-intake/SKILL.md",
4134
+ "surface_entry": null,
4135
+ "summary": "Publish the 1.47.0+skills.1 bundle revision."
4136
+ },
4137
+ {
4138
+ "class": "skill",
4139
+ "path": "skills/next-campaigns-build/SKILL.md",
4140
+ "surface_entry": null,
4141
+ "summary": "Publish the 1.47.0+skills.1 bundle revision."
4142
+ },
4143
+ {
4144
+ "class": "skill",
4145
+ "path": "skills/next-campaigns-os-setup/SKILL.md",
4146
+ "surface_entry": null,
4147
+ "summary": "Publish the 1.47.0+skills.1 bundle revision."
4148
+ },
4149
+ {
4150
+ "class": "skill",
4151
+ "path": "skills/next-campaigns-os/SKILL.md",
4152
+ "surface_entry": null,
4153
+ "summary": "Publish the 1.47.0+skills.1 bundle revision."
4154
+ },
4155
+ {
4156
+ "class": "skill",
4157
+ "path": "skills/next-campaigns-polish/SKILL.md",
4158
+ "surface_entry": null,
4159
+ "summary": "Publish the 1.47.0+skills.1 bundle revision."
4160
+ },
4161
+ {
4162
+ "class": "skill",
4163
+ "path": "skills/next-campaigns-qa/SKILL.md",
4164
+ "surface_entry": null,
4165
+ "summary": "Publish the 1.47.0+skills.1 bundle revision."
4166
+ }
4167
+ ],
4168
+ "entry_sha256": "cc03e502869034a55209958ac02cce0c2b637b12e3e678646c93cfc7aa3a4eeb"
3831
4169
  }
3832
4170
  ]
3833
4171
  }
@@ -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.46.0",
3
+ "surface_version": "1.47.0",
4
4
  "package_exports": [
5
5
  "./commercial-journey",
6
6
  "./commercial-parity",
@@ -120,7 +120,7 @@
120
120
  "sha256": "dfc9abed38d456969e47a21f606d308a03bdf036f3466f7d6e47a602747dacf4"
121
121
  },
122
122
  "contracts/effects.v1.json": {
123
- "sha256": "794fe51952685e5afe6eee1988ce0230aae7a15a64fa1efdb350624158796f9c"
123
+ "sha256": "5f8c6f2f88fc9e0c11c09e265640071d3c1d81fd96c6e05283ed5cfed21d1d59"
124
124
  },
125
125
  "schemas/campaigns-os-effects.v1.schema.json": {
126
126
  "sha256": "3eadd22169ab98bc7c2f682af2751267605170581182158d96be035b0cfe44dc"
@@ -55,12 +55,12 @@
55
55
  },
56
56
  "asset_pin": {
57
57
  "repo": "NextCommerceCo/campaign-cart-starter-templates",
58
- "sha": "11352c30c596db258679fd3a552b906086b11bb9",
58
+ "sha": "3793b1de3b77cc31dffe9176f47e8e99ea6134aa",
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
  },
62
- "rule": "Remove unless the method is listed in CampaignSpec available_payment_methods / available_express_payment_methods. Template chrome outside the SDK payment-method include counts as implied payment-method residue.",
63
- "note": "paypal intentionally has two selectors. QA fails if any selector for an unsupported method remains visible."
62
+ "rule": "Remove unless the method is listed in CampaignSpec available_payment_methods / available_express_payment_methods. Template chrome outside the SDK payment-method include counts as implied payment-method residue. The starter payment-logos.html row renders one <img data-payment-logo> per method and hides the ones the campaign does not offer, so it needs no removal; when one of its logos is flagged, set payment_flags.show_<method>: false in the page frontmatter instead of deleting markup.",
63
+ "note": "paypal intentionally has two selectors. QA fails if any selector for an unsupported method remains visible. images/upsell-payment-logos.svg stays listed: the starter still renders it ungated under payment_flags.style: flat and on the olympus-mv-two-step select page."
64
64
  }
65
65
  },
66
66
  "demo_assets": {
@@ -858,15 +858,35 @@ file. A referenced local script that is not in the built output is a warning,
858
858
  not a blocker, under `built_output.script_syntax.missing_script`, one warning
859
859
  per src naming the pages that load it: the browser gets a 404 for it and
860
860
  nothing it would define runs, but whether the page needs it is not known here.
861
+ Missing scripts are grouped by the URL the browser resolves, not the raw src,
862
+ so `check&#9;out.js` and `checkout.js` (the URL parser removes the tab) are one
863
+ warning; the src shown is the first spelling met.
861
864
  The src is also listed in `scripts_unresolved[]`. While a parse failure blocks
862
865
  the gate, the missing scripts stay on the gate's `warned[]` rather than also
863
866
  surfacing as warnings.
864
867
 
868
+ A `<script>` the page ends inside, with no `</script>` before end of file, is
869
+ not parsed: the browser never runs a script element whose end tag never
870
+ arrives. Doctor warns about it under `built_output.script_syntax.unclosed_script`,
871
+ one warning per page, because a page that ends mid-script is usually truncated
872
+ output. QA neither fetches nor parses such a script.
873
+
874
+ **Symlinks under `_site`.** A script that is a symlink, or sits under a
875
+ symlinked directory, is read the way a static server serves it, by following
876
+ the link, as long as its real path stays inside the site root (`_site/`). A
877
+ parse failure in it is reported under the path the page loads. A script whose
878
+ real path resolves outside the site root is not read. Doctor warns, and does
879
+ not block, under `built_output.script_syntax.symlink_outside_site`, naming the
880
+ link (never its target) and saying the target is outside the site root. The
881
+ link is listed in `scripts_outside_site[]` with `file`, `src` and `pages`, not
882
+ in `scripts_unresolved[]`.
883
+
865
884
  The gate's evidence lands beside the other checkpoint gates at
866
885
  `derived.checkpoint_gates[]` (`id: built_output.script_syntax`, status `pass` |
867
886
  `blocked` | `not_applicable`, `findings[]` with `file`, `line`, `column`,
868
- `source_type` and `pages`, `warned[]` with `src` and `pages`,
869
- `scripts_scanned`, `scripts_unresolved[]`, `pages_scanned`). Fixtures: `fixtures/script-syntax/{good,bad}`. It passes, parsing every
887
+ `source_type` and `pages`, `warned[]` with `code`, `src` and `pages` (and
888
+ `file` for a symlink outside the site root), `scripts_scanned`,
889
+ `scripts_unresolved[]`, `scripts_outside_site[]`, `pages_scanned`). Fixtures: `fixtures/script-syntax/{good,bad}`. It passes, parsing every
870
890
  local script the pages load and with no missing-script warning, on the
871
891
  canonical rendered output of every certified starter family
872
892
  (`fixtures/certified-families/`). QA applies the same rule to the page scripts
@@ -3,18 +3,21 @@
3
3
  For a new campaign, choose its working folder and run this from that folder:
4
4
 
5
5
  ```sh
6
- npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.46.0 next-campaign-page-kit@0.2.0 && npx --no-install campaigns-os tooling setup --target . --platform claude
6
+ npm install --save-exact next-campaign-page-kit@0.2.0 && npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.47.0 && npx --no-install campaigns-os tooling setup --target . --platform claude
7
7
  ```
8
8
 
9
9
  Review the release source/provenance before installation as described in
10
10
  `AGENTS.md`. npm installs the dependencies first; `--no-install` then runs only
11
- the project's installed CLI. Keep `package.json` and `package-lock.json` in
12
- Git. For an existing project, preserve its reviewed pin: run `npm ci`, then
11
+ the project's installed CLI. Page-kit stays a runtime dependency and the
12
+ toolkit a dev dependency: installing page-kit with `--save-dev` would move it
13
+ out of `dependencies` and break builds that run `npm ci --omit=dev`. Keep
14
+ `package.json` and `package-lock.json` in Git. For an existing project, preserve its reviewed pin: run `npm ci`, then
13
15
  `npx --no-install campaigns-os tooling setup --target . --platform claude`
14
16
  on a release that supports setup. Changing the pin is a separate update.
15
17
 
16
18
  Setup checks the exact toolkit pin, its lockfile version and the installed
17
- page-kit dependency before it changes files. It composes the existing
19
+ page-kit dependency before it changes files, and warns when page-kit is
20
+ declared only in `devDependencies`. It composes the existing
18
21
  installers to:
19
22
 
20
23
  1. Install the QA browser through this toolkit's own Playwright package.
@@ -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.46.0`
29
+ Supported surface at generation time: `1.47.0`
30
30
 
31
31
  ## Forward compatibility
32
32
 
@@ -1004,6 +1004,22 @@ the baseline, and when no candidate page is captured the leg emits
1004
1004
  `no_in_scope_page_captured` (skipped) or `no_capture_page_answered`
1005
1005
  (`FAIL`/`BLOCKER`).
1006
1006
 
1007
+ The automatic candidate is never a receipt page: the campaign root is not one,
1008
+ and a built entry counts as one only when its topology `page_type` is `receipt`
1009
+ or `thankyou`. The leg does not substitute the topology's receipt step, because
1010
+ a receipt loaded without an order fires no Purchase. So when the automatic
1011
+ candidate is not a receipt and fires no Purchase, `purchase-present` is
1012
+ `MANUAL_REVIEW`/`WARN` instead of a blocker, and its `evidence.page_mismatch`
1013
+ names the mismatch: `reason` (`receipt_baseline_non_receipt_candidate` when the
1014
+ baseline fired a Purchase, else `candidate_not_receipt`),
1015
+ `baseline_fired_purchase`, `candidate_receipt: false`, `candidate_source`
1016
+ (`campaign_root` or `built_entry`) and `candidate_page_type`. To compare
1017
+ Purchase, pass the candidate receipt with `--analytics-candidate`. An explicit
1018
+ candidate, or an automatic one whose page type is a receipt, still blocks on a
1019
+ missing Purchase, and a non-receipt candidate that does fire a Purchase gets the
1020
+ full set of Purchase checks, with its page recorded in `evidence.candidate_page`
1021
+ on `purchase-present`.
1022
+
1007
1023
  | Flag | Meaning |
1008
1024
  |---|---|
1009
1025
  | `--analytics-baseline <url>` | Legacy funnel URL to capture as the parity baseline (enables the leg) |
@@ -1019,7 +1035,9 @@ Point both at the **thank-you / receipt page** for the highest-value `dl_purchas
1019
1035
  check, or drive the same offer through each funnel so client-fired values line up.
1020
1036
 
1021
1037
  What the diff asserts (BLOCKER unless noted):
1022
- - `purchase-present` — candidate fires a purchase event.
1038
+ - `purchase-present` — candidate fires a purchase event (`MANUAL_REVIEW`
1039
+ naming the page mismatch when the automatic candidate is not a receipt and
1040
+ fires none; see above).
1023
1041
  - `purchase-value` / `purchase-currency` — match the baseline's **client-fired**
1024
1042
  value (compared client-vs-client; never vs a backend total, since tax is
1025
1043
  computed backend and is not in the client value on headless checkouts).
@@ -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.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.46.0`.
11
+ Recipe kind `campaigns-os-node-v1`, revision `1.0.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.47.0`.
12
12
 
13
13
  ## What this is
14
14
 
@@ -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. Targets outside the manifest's supported SDK range report unknown.
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.
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. 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,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.46.0+skills.1"
19
+ "bundle_revision": "1.47.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.46.0+skills.1` is therefore ahead of `1.40.0+skills.7`.
28
+ with the prefix**. `1.47.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.46.0+skills.1
40
+ Bundle revision: 1.47.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.46.0+skills.1
51
+ npx --no-install campaigns-os tooling status --skills-revision 1.47.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.46.0+skills.1",
93
+ "requested": "1.47.0+skills.1",
94
94
  "spelling": "bundle",
95
- "on_disk": "1.46.0+skills.1",
95
+ "on_disk": "1.47.0+skills.1",
96
96
  "on_disk_skill": null,
97
- "message": "match (1.46.0+skills.1)"
97
+ "message": "match (1.47.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.46.0+skills.1)
105
- Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.46.0+skills.1 — start a fresh session
106
- Skills revision: unchecked (on disk 1.46.0+skills.1)
104
+ Skills revision: match (1.47.0+skills.1)
105
+ Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.47.0+skills.1 — start a fresh session
106
+ Skills revision: unchecked (on disk 1.47.0+skills.1)
107
107
  ```
108
108
 
109
109
  `unchecked` is the state when the flag is absent. It is not an error — an
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextcommerce/campaigns-os",
3
- "version": "1.46.0",
3
+ "version": "1.47.0",
4
4
  "description": "Toolkit for agent-assisted NEXT campaign builds.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: campaign-lifecycle-orientation
3
- version: 1.0.17
3
+ version: 1.0.19
4
4
  description: Orient a reader to the Campaigns OS lifecycle artifacts a run has already emitted, without advancing any stage or changing any state.
5
5
  ---
6
6
 
7
- Bundle revision: 1.46.0+skills.1
8
- Run `npx --no-install campaigns-os tooling status --skills-revision 1.46.0+skills.1`
7
+ Bundle revision: 1.47.0+skills.1
8
+ Run `npx --no-install campaigns-os tooling status --skills-revision 1.47.0+skills.1`
9
9
  from the campaign's Page Kit folder, where it runs the project's pinned copy and
10
10
  never installs one, at the start of each task. Start a fresh session if it
11
11
  reports `mismatch`: this text is already in your context and is never re-read