@nextcommerce/campaigns-os 1.33.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 (310) hide show
  1. package/AGENTS.md +204 -0
  2. package/CHANGELOG.md +5002 -0
  3. package/CONTEXT.md +685 -0
  4. package/LICENSE +202 -0
  5. package/NOTICE +4 -0
  6. package/README.md +368 -0
  7. package/agents/claude/CLAUDE.md +32 -0
  8. package/agents/codex/AGENTS.md +27 -0
  9. package/agents/copilot/copilot-instructions.md +14 -0
  10. package/agents/cursor/campaigns-os.mdc +13 -0
  11. package/bin/campaigns-os.mjs +38 -0
  12. package/campaign-spec/README.md +138 -0
  13. package/campaign-spec/dist/analytics-vocabulary.d.ts +47 -0
  14. package/campaign-spec/dist/analytics-vocabulary.js +74 -0
  15. package/campaign-spec/dist/index.d.ts +40 -0
  16. package/campaign-spec/dist/index.js +77 -0
  17. package/campaign-spec/dist/normalize.d.ts +22 -0
  18. package/campaign-spec/dist/normalize.js +41 -0
  19. package/campaign-spec/dist/routing.d.ts +190 -0
  20. package/campaign-spec/dist/routing.js +263 -0
  21. package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +42 -0
  22. package/campaign-spec/dist/rules/analytics-contract-shape.js +303 -0
  23. package/campaign-spec/dist/rules/assembly-hints-shape.d.ts +42 -0
  24. package/campaign-spec/dist/rules/assembly-hints-shape.js +191 -0
  25. package/campaign-spec/dist/rules/campaign-metadata.d.ts +14 -0
  26. package/campaign-spec/dist/rules/campaign-metadata.js +40 -0
  27. package/campaign-spec/dist/rules/checkout-has-success-url.d.ts +31 -0
  28. package/campaign-spec/dist/rules/checkout-has-success-url.js +66 -0
  29. package/campaign-spec/dist/rules/cycle-detection.d.ts +12 -0
  30. package/campaign-spec/dist/rules/cycle-detection.js +141 -0
  31. package/campaign-spec/dist/rules/design-source-shape.d.ts +29 -0
  32. package/campaign-spec/dist/rules/design-source-shape.js +142 -0
  33. package/campaign-spec/dist/rules/downsell-without-upsell.d.ts +11 -0
  34. package/campaign-spec/dist/rules/downsell-without-upsell.js +40 -0
  35. package/campaign-spec/dist/rules/exit-intent-validation.d.ts +23 -0
  36. package/campaign-spec/dist/rules/exit-intent-validation.js +147 -0
  37. package/campaign-spec/dist/rules/funnel-count.d.ts +9 -0
  38. package/campaign-spec/dist/rules/funnel-count.js +37 -0
  39. package/campaign-spec/dist/rules/funnel-hypothesis-length.d.ts +22 -0
  40. package/campaign-spec/dist/rules/funnel-hypothesis-length.js +60 -0
  41. package/campaign-spec/dist/rules/funnel-identity.d.ts +15 -0
  42. package/campaign-spec/dist/rules/funnel-identity.js +57 -0
  43. package/campaign-spec/dist/rules/funnel-weight-sum.d.ts +20 -0
  44. package/campaign-spec/dist/rules/funnel-weight-sum.js +66 -0
  45. package/campaign-spec/dist/rules/index.d.ts +69 -0
  46. package/campaign-spec/dist/rules/index.js +131 -0
  47. package/campaign-spec/dist/rules/offer-ref-integrity.d.ts +13 -0
  48. package/campaign-spec/dist/rules/offer-ref-integrity.js +59 -0
  49. package/campaign-spec/dist/rules/package-pricing-sanity.d.ts +12 -0
  50. package/campaign-spec/dist/rules/package-pricing-sanity.js +42 -0
  51. package/campaign-spec/dist/rules/page-count.d.ts +11 -0
  52. package/campaign-spec/dist/rules/page-count.js +31 -0
  53. package/campaign-spec/dist/rules/page-id-uniqueness.d.ts +14 -0
  54. package/campaign-spec/dist/rules/page-id-uniqueness.js +47 -0
  55. package/campaign-spec/dist/rules/promo-code-input-validation.d.ts +8 -0
  56. package/campaign-spec/dist/rules/promo-code-input-validation.js +126 -0
  57. package/campaign-spec/dist/rules/promo-codes-shape.d.ts +30 -0
  58. package/campaign-spec/dist/rules/promo-codes-shape.js +187 -0
  59. package/campaign-spec/dist/rules/route-field-ignored-for-page-type.d.ts +33 -0
  60. package/campaign-spec/dist/rules/route-field-ignored-for-page-type.js +81 -0
  61. package/campaign-spec/dist/rules/route-target-resolves.d.ts +32 -0
  62. package/campaign-spec/dist/rules/route-target-resolves.js +112 -0
  63. package/campaign-spec/dist/rules/schema-version.d.ts +21 -0
  64. package/campaign-spec/dist/rules/schema-version.js +53 -0
  65. package/campaign-spec/dist/rules/sdk-version.d.ts +26 -0
  66. package/campaign-spec/dist/rules/sdk-version.js +96 -0
  67. package/campaign-spec/dist/rules/shipping-countries-shape.d.ts +10 -0
  68. package/campaign-spec/dist/rules/shipping-countries-shape.js +30 -0
  69. package/campaign-spec/dist/rules/shipping-methods-present.d.ts +10 -0
  70. package/campaign-spec/dist/rules/shipping-methods-present.js +26 -0
  71. package/campaign-spec/dist/rules/store-profile-shape.d.ts +30 -0
  72. package/campaign-spec/dist/rules/store-profile-shape.js +127 -0
  73. package/campaign-spec/dist/rules/thank-you-requirement.d.ts +16 -0
  74. package/campaign-spec/dist/rules/thank-you-requirement.js +50 -0
  75. package/campaign-spec/dist/rules/unknown-top-level-fields.d.ts +28 -0
  76. package/campaign-spec/dist/rules/unknown-top-level-fields.js +114 -0
  77. package/campaign-spec/dist/rules/upsell-has-packages.d.ts +9 -0
  78. package/campaign-spec/dist/rules/upsell-has-packages.js +35 -0
  79. package/campaign-spec/dist/rules/upsell-routing-complete.d.ts +10 -0
  80. package/campaign-spec/dist/rules/upsell-routing-complete.js +45 -0
  81. package/campaign-spec/dist/rules/upsell-without-checkout.d.ts +11 -0
  82. package/campaign-spec/dist/rules/upsell-without-checkout.js +44 -0
  83. package/campaign-spec/dist/rules/variant-labels-shape.d.ts +28 -0
  84. package/campaign-spec/dist/rules/variant-labels-shape.js +86 -0
  85. package/campaign-spec/dist/sdk-version-parse.d.ts +43 -0
  86. package/campaign-spec/dist/sdk-version-parse.js +62 -0
  87. package/campaign-spec/dist/types.d.ts +674 -0
  88. package/campaign-spec/dist/types.js +40 -0
  89. package/campaign-spec/package.json +12 -0
  90. package/compatibility.json +25 -0
  91. package/contracts/agent-relevant-change-policy.v1.json +111 -0
  92. package/contracts/brand-theme-source-defaults.figma-sections-export.v0.json +32 -0
  93. package/contracts/brand-theme-target-tokens.next-core.v0.json +65 -0
  94. package/contracts/campaign-cart-checkout-field-contract.v0.json +45 -0
  95. package/contracts/campaign-cart-sdk-support-policy.v0.json +11 -0
  96. package/contracts/commerce-surface-catalog.json +2452 -0
  97. package/contracts/fixtures/orientation/canonicalization/v1.json +34 -0
  98. package/contracts/fixtures/orientation/envelope/current.json +95 -0
  99. package/contracts/fixtures/orientation/envelope/freshness_unknown.json +97 -0
  100. package/contracts/fixtures/orientation/envelope/legacy_baseline.json +95 -0
  101. package/contracts/fixtures/orientation/envelope/orientation_available.json +136 -0
  102. package/contracts/fixtures/orientation/envelope/recovered_interrupted_update.json +136 -0
  103. package/contracts/fixtures/orientation/envelope/refused.json +100 -0
  104. package/contracts/fixtures/orientation/envelope/restart_required.json +137 -0
  105. package/contracts/fixtures/orientation/envelope/updated.json +135 -0
  106. package/contracts/fixtures/orientation/hostile-target/README.md +61 -0
  107. package/contracts/fixtures/orientation/hostile-target/manifest.json +82 -0
  108. package/contracts/fixtures/orientation/hostile-target/repo/CHANGELOG.md +18 -0
  109. package/contracts/fixtures/orientation/hostile-target/repo/bin/intended.mjs +12 -0
  110. package/contracts/fixtures/orientation/hostile-target/repo/bin/tripwire.mjs +15 -0
  111. package/contracts/fixtures/orientation/hostile-target/repo/contracts/release-ledger.json +48 -0
  112. package/contracts/fixtures/orientation/hostile-target/repo/contracts/supported-surface.json +17 -0
  113. package/contracts/fixtures/orientation/hostile-target/repo/docs/example-contract.md +13 -0
  114. package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-checkout +5 -0
  115. package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-merge +5 -0
  116. package/contracts/fixtures/orientation/hostile-target/repo/hooks/pre-commit +5 -0
  117. package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/package.json +15 -0
  118. package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/tripwire.mjs +9 -0
  119. package/contracts/fixtures/orientation/hostile-target/repo/package.json +20 -0
  120. package/contracts/fixtures/orientation/hostile-target/repo/schemas/example.v0.schema.json +15 -0
  121. package/contracts/fixtures/orientation/release-gate/cases.json +1073 -0
  122. package/contracts/fixtures/runtime-recipe/accept/current.json +299 -0
  123. package/contracts/fixtures/runtime-recipe/accept/minimal.json +294 -0
  124. package/contracts/fixtures/runtime-recipe/dist-states.json +51 -0
  125. package/contracts/fixtures/runtime-recipe/manifest.json +85 -0
  126. package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +299 -0
  127. package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +297 -0
  128. package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +299 -0
  129. package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +299 -0
  130. package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +299 -0
  131. package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +251 -0
  132. package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +299 -0
  133. package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +299 -0
  134. package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +310 -0
  135. package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +299 -0
  136. package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +299 -0
  137. package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +299 -0
  138. package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +299 -0
  139. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/assembly-report.json +180 -0
  140. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/build-context.json +115 -0
  141. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/doctor-output.json +29 -0
  142. package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/qa-verdict.json +27 -0
  143. package/contracts/fixtures/sidecar-bundle/production-shaped/campaign-runtime.build.json +141 -0
  144. package/contracts/migration-sidecar-bundle.v0.json +149 -0
  145. package/contracts/orientation-limits.v1.json +41 -0
  146. package/contracts/orientation-reason-codes.v1.json +196 -0
  147. package/contracts/private-template-sources.json +8 -0
  148. package/contracts/release-ledger.json +4598 -0
  149. package/contracts/reserved-skill-names.json +13 -0
  150. package/contracts/runtime-recipe.campaigns-os-node-v1.json +299 -0
  151. package/contracts/supported-surface.json +180 -0
  152. package/contracts/template-brand-contract.apollo-mv-single-step.v0.json +27 -0
  153. package/contracts/template-brand-contract.apollo.v0.json +27 -0
  154. package/contracts/template-brand-contract.demeter.v0.json +27 -0
  155. package/contracts/template-brand-contract.olympus-mv-single-step.v0.json +27 -0
  156. package/contracts/template-brand-contract.olympus-mv-two-step.v0.json +28 -0
  157. package/contracts/template-brand-contract.olympus.v0.json +27 -0
  158. package/contracts/template-brand-contract.shared-commerce.v0.json +190 -0
  159. package/contracts/template-brand-contract.shop-single-step.v0.json +27 -0
  160. package/contracts/template-brand-contract.shop-three-step.v0.json +29 -0
  161. package/contracts/template-slot-manifest.apollo-mv-single-step.v0.json +14 -0
  162. package/contracts/template-slot-manifest.apollo.v0.json +12 -0
  163. package/contracts/template-slot-manifest.demeter.v0.json +30 -0
  164. package/contracts/template-slot-manifest.olympus-mv-single-step.v0.json +14 -0
  165. package/contracts/template-slot-manifest.olympus-mv-two-step.v0.json +15 -0
  166. package/contracts/template-slot-manifest.olympus.v0.json +12 -0
  167. package/contracts/template-slot-manifest.shared-content-core.v0.json +4254 -0
  168. package/contracts/template-slot-manifest.shop-single-step.v0.json +47 -0
  169. package/contracts/template-slot-manifest.shop-three-step.v0.json +33 -0
  170. package/docs/brand-theme-bridge.md +159 -0
  171. package/docs/build-packet.md +1300 -0
  172. package/docs/campaign-build-brief.md +145 -0
  173. package/docs/campaign-standardization-report.md +329 -0
  174. package/docs/campaigns-os-build-flow.md +117 -0
  175. package/docs/design-source-package.md +784 -0
  176. package/docs/legacy-migration.md +58 -0
  177. package/docs/migration-sidecar-bundle.md +139 -0
  178. package/docs/orientation-contract-reference.md +1220 -0
  179. package/docs/polish-evidence.md +502 -0
  180. package/docs/qa-and-test-orders.md +1691 -0
  181. package/docs/release-ledger-authoring-guide.md +274 -0
  182. package/docs/runtime-readiness.md +211 -0
  183. package/docs/supported-surface.md +83 -0
  184. package/docs/versioning.md +55 -0
  185. package/docs/workflow-findings-sidecar.md +588 -0
  186. package/package.json +135 -0
  187. package/prompts/first-build.md +27 -0
  188. package/prompts/friction-log.md +30 -0
  189. package/schemas/campaign-build-brief.v1.schema.json +149 -0
  190. package/schemas/campaign-design-source-package.v0.schema.json +697 -0
  191. package/schemas/campaign-runtime-assembly-report.v0.schema.json +390 -0
  192. package/schemas/campaign-runtime-build-context.v0.schema.json +339 -0
  193. package/schemas/campaign-runtime-build-packet.v0.schema.json +452 -0
  194. package/schemas/campaign-spec.v4.schema.json +582 -0
  195. package/schemas/campaigns-os-doctor-output.v0.schema.json +43 -0
  196. package/schemas/campaigns-os-legacy-migration-inventory.v0.schema.json +112 -0
  197. package/schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json +38 -0
  198. package/schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json +57 -0
  199. package/schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json +150 -0
  200. package/schemas/campaigns-os-qa-verdict.v0.schema.json +438 -0
  201. package/schemas/campaigns-os-release-ledger.v1.schema.json +162 -0
  202. package/schemas/campaigns-os-run-record.v0.schema.json +343 -0
  203. package/schemas/campaigns-os-runtime-recipe.v1.schema.json +313 -0
  204. package/schemas/campaigns-os-sidecar-bundle-conformance.v0.schema.json +78 -0
  205. package/schemas/campaigns-os-tooling-orientation.v1.schema.json +381 -0
  206. package/schemas/campaigns-os-workflow-finding.v0.schema.json +134 -0
  207. package/schemas/source-html-manifest.v0.schema.json +252 -0
  208. package/skills/next-campaigns-build/SKILL.md +73 -0
  209. package/skills/next-campaigns-os/SKILL.md +101 -0
  210. package/skills/next-campaigns-os/references/session-intake.md +160 -0
  211. package/skills/next-campaigns-os-setup/SKILL.md +22 -0
  212. package/skills/next-campaigns-polish/SKILL.md +145 -0
  213. package/skills/next-campaigns-qa/SKILL.md +92 -0
  214. package/skills.json +56 -0
  215. package/skills.sh +64 -0
  216. package/src/adapter-decision-contract.mjs +333 -0
  217. package/src/brand-theme.mjs +1151 -0
  218. package/src/browser-launch.mjs +79 -0
  219. package/src/build-brief.mjs +781 -0
  220. package/src/built-site-scope.mjs +312 -0
  221. package/src/campaign-ecosystem.mjs +734 -0
  222. package/src/campaign-identity.mjs +405 -0
  223. package/src/campaign-workspace.mjs +127 -0
  224. package/src/checkpoint-waiver.mjs +302 -0
  225. package/src/cli.mjs +13442 -0
  226. package/src/commercial-journey.mjs +1119 -0
  227. package/src/commercial-parity.mjs +965 -0
  228. package/src/consent.mjs +347 -0
  229. package/src/content-residue.mjs +322 -0
  230. package/src/deadline.mjs +81 -0
  231. package/src/design-source-package.mjs +2604 -0
  232. package/src/deviation.mjs +107 -0
  233. package/src/doctor-check-registry.mjs +49 -0
  234. package/src/doctor-sidecar.mjs +106 -0
  235. package/src/finding-cause.mjs +557 -0
  236. package/src/findings.mjs +326 -0
  237. package/src/fs-identity.mjs +68 -0
  238. package/src/gate-actions.mjs +105 -0
  239. package/src/html-scan.mjs +53 -0
  240. package/src/install-mode.mjs +272 -0
  241. package/src/legacy-migration.d.ts +128 -0
  242. package/src/legacy-migration.mjs +510 -0
  243. package/src/lifecycle.mjs +338 -0
  244. package/src/local-proof.mjs +401 -0
  245. package/src/map-pin-writeback.mjs +210 -0
  246. package/src/orchestration-stage-contract.mjs +81 -0
  247. package/src/package-install-fixture.mjs +33 -0
  248. package/src/page-kit-build-summary.mjs +175 -0
  249. package/src/page-kit-campaign-config.mjs +57 -0
  250. package/src/page-kit-sdk-version.mjs +392 -0
  251. package/src/page-kit-store-profile.mjs +369 -0
  252. package/src/page-kit-sync.mjs +162 -0
  253. package/src/polish-browser.mjs +867 -0
  254. package/src/polish-capture.mjs +1094 -0
  255. package/src/polish-deadline.mjs +51 -0
  256. package/src/polish-gate.mjs +739 -0
  257. package/src/polish-node.mjs +639 -0
  258. package/src/polish-page-load.mjs +1100 -0
  259. package/src/private-template-source.mjs +237 -0
  260. package/src/proof-policy.mjs +82 -0
  261. package/src/qa-analytics-correctness.mjs +307 -0
  262. package/src/qa-analytics-errors.mjs +38 -0
  263. package/src/qa-analytics-parity.mjs +699 -0
  264. package/src/qa-binding-evidence.mjs +140 -0
  265. package/src/qa-browser.mjs +6608 -0
  266. package/src/qa-cart-entry.mjs +406 -0
  267. package/src/qa-commercial-parity.mjs +641 -0
  268. package/src/qa-node.mjs +3620 -0
  269. package/src/qa-order-bump.mjs +381 -0
  270. package/src/qa-parity-capture.mjs +428 -0
  271. package/src/qa-parity-fixture.mjs +359 -0
  272. package/src/qa-publish.mjs +362 -0
  273. package/src/qa-purchase-data-layer.mjs +263 -0
  274. package/src/qa-route-probe.mjs +272 -0
  275. package/src/qa-sidecar.mjs +188 -0
  276. package/src/qa-test-order-topology.mjs +207 -0
  277. package/src/qa-url-privacy.mjs +13 -0
  278. package/src/qa-verdict-discovery.mjs +192 -0
  279. package/src/qa-verdict-publish.mjs +105 -0
  280. package/src/qa-verdict.mjs +287 -0
  281. package/src/remit.mjs +388 -0
  282. package/src/repo-scan.mjs +83 -0
  283. package/src/route-identity.mjs +133 -0
  284. package/src/run-record-closeout.mjs +229 -0
  285. package/src/run-record.mjs +839 -0
  286. package/src/run-session.mjs +226 -0
  287. package/src/runtime-state-ignore.mjs +113 -0
  288. package/src/sdk-attribute-index.mjs +212 -0
  289. package/src/sdk-markup.mjs +358 -0
  290. package/src/sdk-meta-tags.mjs +52 -0
  291. package/src/shell-token.mjs +7 -0
  292. package/src/sidecar-bundle.mjs +399 -0
  293. package/src/source-asset-crawl.mjs +469 -0
  294. package/src/source-html-intake.mjs +627 -0
  295. package/src/source-html-manifest.mjs +276 -0
  296. package/src/source-prep.mjs +284 -0
  297. package/src/spec-derive-store.mjs +431 -0
  298. package/src/spec-derive.mjs +514 -0
  299. package/src/spec-fetch.mjs +66 -0
  300. package/src/spec-hash.mjs +48 -0
  301. package/src/spec-identity.mjs +27 -0
  302. package/src/stage-ledger.mjs +509 -0
  303. package/src/standardization-report.mjs +1297 -0
  304. package/src/template-brand-contract.mjs +473 -0
  305. package/src/template-freshness.mjs +196 -0
  306. package/src/template-reference.mjs +81 -0
  307. package/src/template-slot-manifest.mjs +150 -0
  308. package/src/text-safety.mjs +62 -0
  309. package/src/theme-gate.mjs +185 -0
  310. package/src/upsell-selector-scope.mjs +299 -0
@@ -0,0 +1,784 @@
1
+ # Design Source Package v0
2
+
3
+ The Design Source Package is the durable, normalized creative/provenance input
4
+ that Build and Polish consume. It does not replace CampaignSpec commerce truth,
5
+ the Campaign Build Brief, Template Reference runtime proof, or Polish Evidence.
6
+ Those artifacts stay separate and join through stable Surface Identity and
7
+ fingerprints.
8
+
9
+ This guide documents the v0 implementation shipped by this repository. The
10
+ normative JSON shape is
11
+ [`schemas/campaign-design-source-package.v0.schema.json`](../schemas/campaign-design-source-package.v0.schema.json),
12
+ and the runtime validator and producer enforce additional cross-record and
13
+ current-input checks.
14
+
15
+ ## Contract identity and compatibility
16
+
17
+ - Schema identity: `campaign-design-source-package/v0`.
18
+ - Schema file: `schemas/campaign-design-source-package.v0.schema.json`.
19
+ - Default target-repo path:
20
+ `.campaign-runtime/input/design-source-package.json`.
21
+ - Current `prepare-build` producer: `source_kind: "html_funnel"`.
22
+ - Current source adapter remains visible in `packet.source_html`. During v0,
23
+ `prepare-build` emits that compatibility block alongside the normalized
24
+ package; the source-html manifest and page mappings seed the package but do
25
+ not become it.
26
+
27
+ The package is strict at its top level. It contains `schema_version`,
28
+ `package_id`, `source_kind`, `generated_at`, `material_fingerprint`,
29
+ `surface_identity`, `contributions`, `source_gaps`, `source_todos`, `waivers`,
30
+ `divergences`, `proposed_exceptions`, `notes`, `readiness`, and `readback`.
31
+ Unknown top-level fields are rejected.
32
+
33
+ ## Contributions and coverage
34
+
35
+ Each `contributions[]` record identifies one source contribution and records:
36
+
37
+ - `id`, `kind`, `trust`, and whether it is `renderable`;
38
+ - structured `provenance` and `presentation_intent`;
39
+ - `source_refs`, source-side `screenshot_refs`, and comparison-side
40
+ `reference_refs`;
41
+ - optional `template_reference` proof; and
42
+ - `mappings` that join the contribution to Surface Identity.
43
+
44
+ The v0 contribution kinds are `html_funnel`, `figma_frames`, `figma_sections`,
45
+ `page_kit`, `static_source`, `template_stock`, `agency_source`, and `other`.
46
+ Trust is `native`, `structured`, `rendered`, or `opaque`.
47
+
48
+ Every contribution mapping uses exactly one `coverage_role`:
49
+
50
+ - `primary_design`
51
+ - `partial_design`
52
+ - `brand_tokens`
53
+ - `asset_source`
54
+ - `copy_source`
55
+ - `template_baseline`
56
+ - `reference_only`
57
+ - `fallback_legacy`
58
+
59
+ Mapping `confidence` is `high`, `medium`, `low`, or `unknown`. It describes
60
+ confidence in the source-to-surface relationship, not design quality or
61
+ approval. A page-level `primary_design` claim must be `high` or `medium` to
62
+ qualify for readiness.
63
+
64
+ ## Surface Identity
65
+
66
+ `surface_identity[]` is the campaign-facing join catalog. Exactly one entry
67
+ must reserve this identity pair:
68
+
69
+ ```json
70
+ {
71
+ "id": "campaign",
72
+ "kind": "campaign"
73
+ }
74
+ ```
75
+
76
+ The `campaign` ID and `campaign` kind are reserved for each other; the other
77
+ fields are not required to be empty. Current synthesis sets `label: "Campaign"`,
78
+ adds the current campaign Map ID and route slug to `aliases` when present, and
79
+ records those values as `mappings.campaign_map_id` and
80
+ `mappings.campaign_slug`. Current-input validation refuses an existing package
81
+ that omits or changes either mapping when the corresponding current value is
82
+ available. Campaign scope is used for legitimate campaign-wide gaps, TODOs,
83
+ waivers, divergences, and exceptions.
84
+
85
+ Current synthesis also creates a `kind: "page"` Surface Identity for every
86
+ active CampaignSpec page or mapped source page. Stable, human-semantic
87
+ CampaignSpec page IDs are preferred; otherwise the producer derives an ID from
88
+ the normalized page role and order. Section and runtime-surface entries are
89
+ allowed, but `prepare-build` does not invent them.
90
+
91
+ Page Surface Identity keeps these namespaces distinct in `mappings`:
92
+
93
+ - `campaign_spec_page_id`
94
+ - `map_builder_label`
95
+ - `map_builder_custom_name`
96
+ - `public_route`
97
+ - `producer_page_type`
98
+ - `source_page_id`
99
+ - `source_path`
100
+ - `page_kit`
101
+
102
+ The `page_kit` projection contains `target_path`, `output_path`,
103
+ `public_route`, `page_type`, and optional `spec_route` and
104
+ `permalink_required`. These fields, public routes, producer IDs, and DOM or
105
+ runtime names are mappings or aliases; none replaces Surface Identity.
106
+
107
+ ## Source and visual references
108
+
109
+ Source references have a stable `id`, a `kind`, and at least one of `path` or
110
+ `url`. Their kinds are `html_file`, `manifest`, `asset`, `url`, `export`,
111
+ `document`, and `other`. They may also carry a byte hash, role, media type, and
112
+ notes.
113
+
114
+ Visual references use shared viewport keys: `desktop`, `mobile`, and optional
115
+ `tablet`. Their kinds are:
116
+
117
+ - `source_screenshot`
118
+ - `template_reference_screenshot`
119
+ - `render_reference`
120
+ - `export_reference`
121
+ - `unavailable_render`
122
+
123
+ An available visual requires a path or URL. An unavailable visual requires an
124
+ `unavailable_reason`. Available source and Template Reference screenshots must
125
+ also link to a known source record with `source_ref_id`. Width, height, device
126
+ profile, scale factor, browser, and capture time are metadata; they do not
127
+ create additional viewport names.
128
+
129
+ A coverage mapping cites reference IDs through `source_refs`,
130
+ `screenshot_refs`, and `reference_refs`. Readiness counts only correctly typed,
131
+ linked proof. A render reference, a generic image asset, or an image without an
132
+ explicit viewport is not silently promoted to source-screenshot proof. An
133
+ `unavailable_render` documents an absence but does not satisfy an available
134
+ desktop/mobile proof requirement.
135
+
136
+ ### Source screenshot behavior
137
+
138
+ Primary-design coverage is evaluated existentially per page, not independently
139
+ for every claim. To satisfy the page through `primary_design`, at least one
140
+ `high` or `medium` claim must qualify. A non-renderable contribution needs no
141
+ screenshot proof; a renderable one qualifies only with available, linked
142
+ `source_screenshot` proof for both `desktop` and `mobile`. Tablet is optional in
143
+ v0. Once one qualifying claim has the required proof, another incomplete claim
144
+ for the same page does not create a screenshot blocker or TODO.
145
+
146
+ If the page has one or more high/medium claims but none has the required proof,
147
+ synthesis selects the claim with the fewest missing standard viewports (then by
148
+ stable contribution/mapping ID order) and produces one blocking
149
+ `missing_source_screenshot` Source TODO per viewport missing from that claim.
150
+ An accepted screenshot-coverage Source Gap or active approved waiver supersedes
151
+ those TODOs.
152
+
153
+ Low or unknown primary coverage produces a blocking
154
+ `low_confidence_primary_design` TODO only when the page has no high/medium
155
+ primary claim and no accepted primary-coverage Source Gap or active approved
156
+ waiver. If there is no primary claim or coverage exception, an incomplete
157
+ selected Template Reference produces the specific TODO described below; a
158
+ complete proven `template_baseline` satisfies coverage, and otherwise the page
159
+ receives a blocking `missing_primary_design` TODO.
160
+
161
+ ### Template Reference behavior
162
+
163
+ A template family name alone is not proof. `template_baseline` coverage is
164
+ emitted only when the contribution has a Template Reference with:
165
+
166
+ - an `id`, `family`, and `version`;
167
+ - a `contract_path` or `artifact_path`; and
168
+ - available, linked `template_reference_screenshot` records for the standard
169
+ `desktop` and `mobile` viewports.
170
+
171
+ If family selection lacks the version/location linkage, synthesis emits a
172
+ blocking `missing_template_reference` TODO. If the Template Reference is linked
173
+ but a standard viewport is absent, it emits a blocking
174
+ `missing_template_viewport` TODO for that viewport. Until proof is complete,
175
+ the template contribution carries no `template_baseline` mapping; the producer
176
+ does not invent a reference or viewport capture.
177
+
178
+ ## Gaps, TODOs, waivers, and readiness
179
+
180
+ The detailed records are authoritative. `readiness` and `readback` are generated
181
+ summaries and must agree with them.
182
+
183
+ - Source Gap kinds are `coverage_absence`, `screenshot_absence`,
184
+ `reference_absence`, `source_limitation`, and `other`; statuses are
185
+ `proposed`, `accepted`, and `resolved`. A proposed gap blocks. An accepted
186
+ gap with the matching kind, scope, and `applies_to` target may satisfy the
187
+ relevant absence and yield `ready_with_gaps`.
188
+ - Source TODOs are `pending`, `blocked`, `completed`, or `skipped`. Any
189
+ non-completed TODO blocks unless an active approved waiver matches its record
190
+ or affected surface. A skipped TODO without that waiver is invalid as well
191
+ as blocking.
192
+ - Source TODO kinds are `missing_source_screenshot`,
193
+ `missing_template_reference`, `missing_template_viewport`,
194
+ `missing_primary_design`, `low_confidence_primary_design`,
195
+ `unreadable_reference`, and `other`.
196
+ - Waivers are `proposed`, `approved`, `revoked`, or `expired`. An effective
197
+ waiver must be approved, attributed, and bounded by a future `expires_at` or
198
+ a non-empty `review_condition`.
199
+ - A proposed readiness-affecting divergence or proposed exception blocks.
200
+ Non-readiness-affecting records remain visible without creating that blocker.
201
+
202
+ Design Source Package readiness uses only:
203
+
204
+ - `pending`
205
+ - `blocked`
206
+ - `ready`
207
+ - `ready_with_gaps`
208
+ - `ready_with_waivers`
209
+
210
+ There is no `ready_with_warnings` source-readiness state. Warning-like source
211
+ conditions must be represented as a concrete gap, TODO, divergence, proposed
212
+ exception, note, or waiver. Notes never clear a blocker.
213
+
214
+ The shared checkpoint vocabulary additionally includes `completed`,
215
+ `completed_with_warnings`, and `skipped`, but those are not valid values for
216
+ `design_source_package.readiness.status`.
217
+
218
+ The generated `readiness` record also carries `blocking_reasons`, total
219
+ `gap_count`, `todo_count`, and `waiver_count`, plus `generated_at`. The generated
220
+ `readback` has the fixed buckets `summary`, `included_sources`, `handled`,
221
+ `blockers`, `gaps`, `todos`, `waivers`, and `next_actions`. Validation rejects a
222
+ summary or readback that contradicts the authoritative records.
223
+
224
+ ## Artifact references and fingerprints
225
+
226
+ The Build Packet, Build Context, and Assembly Report emitted by
227
+ `prepare-build` each carry the same four-field reference, never an embedded
228
+ package:
229
+
230
+ ```json
231
+ {
232
+ "path": ".campaign-runtime/input/design-source-package.json",
233
+ "schema_version": "campaign-design-source-package/v0",
234
+ "sha256": "sha256:<64 lowercase hex>",
235
+ "material_fingerprint": "sha256:<64 lowercase hex>"
236
+ }
237
+ ```
238
+
239
+ `path` is relative to the artifact containing the reference. With default
240
+ locations, the packet uses
241
+ `.campaign-runtime/input/design-source-package.json`, while the context and
242
+ report use `input/design-source-package.json`. A custom nested report receives
243
+ the corresponding artifact-relative path. Consumers must resolve each path
244
+ from its owning artifact instead of comparing the path strings directly.
245
+
246
+ The two hashes have different jobs:
247
+
248
+ - `sha256` hashes the exact package bytes on disk. Whitespace, key order,
249
+ timestamps, notes, and every other serialized byte affect it. When an
250
+ existing package is reused, the reference hashes those original bytes rather
251
+ than a reserialization.
252
+ - `material_fingerprint` hashes canonical JSON for the explicit v0 material
253
+ projection. It drives Build/Polish freshness and is not a whole-JSON hash.
254
+
255
+ ### Exact v0 material projection
256
+
257
+ The material projection contains:
258
+
259
+ - top-level `schema_version` and `source_kind`;
260
+ - every Surface Identity's `id`, `kind`, `label`, aliases, and mappings,
261
+ including the Page Kit projection;
262
+ - every contribution's identity, kind, trust, renderability, provenance,
263
+ presentation intent, source references, source screenshot/comparison
264
+ references, Template Reference, and coverage mappings;
265
+ - all Source Gaps and Source TODOs;
266
+ - approved waivers only; and
267
+ - divergences and proposed exceptions whose `readiness_affecting` value is
268
+ `true`.
269
+
270
+ The projected fields are explicit:
271
+
272
+ | Record | Fields in the v0 projection |
273
+ | --- | --- |
274
+ | Surface Identity | `id`, `kind`, `label`, `aliases`, and `mappings`: `campaign_map_id`, `campaign_slug`, `campaign_spec_page_id`, `map_builder_label`, `map_builder_custom_name`, `public_route`, `producer_page_type`, `source_page_id`, `source_path`, `page_kit`, `parent_surface_id`, `dom_selector`, `runtime_surface`. |
275
+ | Contribution core | `id`, `kind`, `trust`, `renderable`. |
276
+ | Provenance | `source_type`, `adapter`, `producer`, `generator`, `generator_version`, `source_root`, `manifest_schema_version`, `manifest_path`, `manifest_sha256`, `producer_material_fingerprint`, `asset_crawl_schema_version`. |
277
+ | Presentation intent | `summary`, `composition`, `content_hierarchy`, `imagery`, `copy`, `brand`, `responsive_behavior`. |
278
+ | Source reference | `id`, `kind`, `path`, `url`, `sha256`, `role`, `media_type`. |
279
+ | Visual reference | `id`, `kind`, `viewport`, `availability`, `url`, `path`, `sha256`, `width`, `height`, `device_profile`, `scale_factor`, `browser`, `source_ref_id`, `unavailable_reason`. |
280
+ | Template Reference | `id`, `family`, `version`, `contract_path`, `artifact_path`, `sha256`, `standard_viewport_refs` using the visual projection above. |
281
+ | Coverage mapping | `id`, `surface_id`, `coverage_role`, `confidence`, `source_refs`, `screenshot_refs`, `reference_refs`, `template_reference_id`. |
282
+ | Source Gap | `id`, `kind`, `scope`, `applies_to`, `reason`, `status`, `attributed_by`, `attributed_at`, `evidence_refs`. |
283
+ | Source TODO | `id`, `kind`, `scope`, `applies_to`, `description`, `status`, `owner`, `required_viewports`, `source_ref_ids`. |
284
+ | Approved waiver | `id`, `scope`, `applies_to`, `reason`, `status`, `waived_by`, `waived_at`, `expires_at`, `review_condition`, `evidence_refs`. |
285
+ | Readiness-affecting divergence | `id`, `scope`, `applies_to`, `summary`, `status`, `recorded_stage`, `readiness_affecting`, `attributed_by`, `evidence_refs`. |
286
+ | Readiness-affecting exception | `id`, `scope`, `applies_to`, `reason`, `status`, `readiness_affecting`, `proposed_by`, `evidence_refs`. |
287
+
288
+ Record collections and set-like ID lists are normalized deterministically
289
+ before hashing, and nested SHA-256 values have one material identity whether
290
+ written bare or with the `sha256:` prefix.
291
+
292
+ Administrative examples that are not projected include:
293
+
294
+ - top-level `package_id` and `generated_at`;
295
+ - generated `readiness` and `readback` fields;
296
+ - top-level, contribution, mapping, source-reference, and visual-reference
297
+ notes;
298
+ - visual `captured_at` alone; and
299
+ - JSON formatting, object-key order, record-collection order, and set-like
300
+ string-list order where the projection sorts by ID/value.
301
+
302
+ Non-approved waivers and non-readiness-affecting divergences/exceptions are also
303
+ outside the v0 projection. Even when a derived field is non-material, stale or
304
+ invented `readiness` or `readback` still fails validation. A changed
305
+ administrative byte therefore requires a refreshed full `sha256` reference but
306
+ does not, by itself, make Build or Polish stale.
307
+
308
+ ## `prepare-build`: emit, validate, or refuse
309
+
310
+ `prepare-build` treats the default package path as an ownership boundary.
311
+
312
+ When the package is missing, it synthesizes the current `html_funnel` package,
313
+ validates it, serializes it, and reports mode `emitted`. Synthesis uses current
314
+ active pages and page mappings, campaign Map ID and route slug, current source
315
+ file hashes, the source-html manifest and its exact byte hash when present, the
316
+ source asset crawl, and the source-side template-family input. A coherent but
317
+ blocked package is still emitted so its TODOs and blockers are durable.
318
+
319
+ When the package already exists, `prepare-build`:
320
+
321
+ 1. reads and retains its exact bytes;
322
+ 2. parses and validates its strict shape, material fingerprint, generated
323
+ readiness, and generated readback;
324
+ 3. validates current campaign and page identity/mappings;
325
+ 4. validates current HTML source/provenance, required source refs and byte
326
+ hashes, coverage mappings, and current template material; and
327
+ 5. requires `source_kind: "html_funnel"`.
328
+
329
+ If all checks pass, the mode is `reused`: the package bytes and modification
330
+ time are untouched, and all three artifact references use the full hash of
331
+ those exact bytes. Harmless reformatting and administrative notes can therefore
332
+ be reused when the material fingerprint and derived summaries remain valid.
333
+
334
+ If any current campaign, active/mapped page, source material, manifest or crawl
335
+ provenance, coverage, template family/reference, material fingerprint,
336
+ readiness, or readback check fails, `prepare-build` refuses. It leaves the
337
+ existing package and packet/context/report/brief sidecars byte-identical. It
338
+ does not silently regenerate or overwrite the package; source preparation must
339
+ reconcile the package and its references explicitly before retrying.
340
+
341
+ Before writing any output, `prepare-build` also requires distinct paths for the
342
+ Build Packet, Build Context, Assembly Report, Doctor output, normalized Build
343
+ Brief, and fixed Design Source Package. Equal paths and filesystem aliases are
344
+ rejected, including symlinks, hard links, dangling leaf symlinks, and symlinked
345
+ parent directories.
346
+
347
+ This behavior is the implemented v0 compatibility boundary. It does not promise
348
+ that a separate future workflow command will generate, repair, approve, or
349
+ silently refresh the package.
350
+
351
+ ## Clearing `DESIGN_SOURCE_PACKAGE_NOT_READY`
352
+
353
+ `prepare-build` and `start` block at intake when a renderable page has no
354
+ qualifying primary-design claim with linked `desktop` and `mobile`
355
+ `source_screenshot` proof. Doctor reports one
356
+ `DESIGN_SOURCE_PACKAGE_NOT_READY` error per blocking reason; the blocked
357
+ `capture-<surface>-<viewport>` Source TODOs are themselves blocking reasons, so a
358
+ four-page funnel with no proof reports twelve. `next` routes back to
359
+ `prepare-build`, and doctor's `next` block says the same. Nothing is wrong with the run: the package is coherent and
360
+ durable, and it is telling you that the source material arrived without visual
361
+ proof.
362
+
363
+ The input channel that supplies that proof is the source-html manifest at
364
+ `<source-root>/.campaigns-os/source-html-manifest.json` — or, when the source
365
+ root is not yours to write, a manifest of the same schema anywhere else, named
366
+ with `--design-manifest <path>` on `start`, `prepare-build`, or `build` (see
367
+ [A read-only source root](#a-read-only-source-root)). Each `pages[]` entry may
368
+ carry a `screenshots[]` array; `prepare-build` reads it, alongside the
369
+ equivalent `screenshot_refs` and `source_screenshot_refs` keys, and normalizes
370
+ each record into the html_funnel contribution's `screenshot_refs`. This is the
371
+ only operator-authored channel that seeds source-screenshot proof (a producer's
372
+ `section_exports[].images` is the other, producer-authored path); the package
373
+ itself is not hand-edited, and there is no capture command.
374
+
375
+ The manifest is read only when the whole file is a valid `source-html-manifest/v0`
376
+ document: `schema_version` set to `"source-html-manifest/v0"` and a `pages[]`
377
+ array whose entries carry the active CampaignSpec `page_id` and exactly one of
378
+ the source-root-relative `path` or a `skip_reason` for a page that has no source
379
+ HTML by design (schema: `schemas/source-html-manifest.v0.schema.json`;
380
+ consumer mechanics: [Source HTML Manifest Auto-Population](build-packet.md#source-html-manifest-auto-population)).
381
+ A manifest that fails validation is reported as a doctor *warning*, not an error:
382
+ `prepare-build` falls back to filesystem matching and never reads `screenshots[]`,
383
+ so the run blocks again with nothing else changed. `node scripts/reference-ai-producer.mjs`
384
+ emits a valid envelope (page ids, paths, `source_hash`) from a folder of HTML
385
+ files; add the `screenshots[]` records to its output, or start from the complete
386
+ example below. (`context.source.manifest_draft` in the build context is populated
387
+ only when filename matching was ambiguous, and is `null` otherwise.)
388
+
389
+ ### The record shape
390
+
391
+ One manifest page entry with its proof, inside the envelope the validator requires
392
+ (the mobile record is elided):
393
+
394
+ ```json
395
+ {
396
+ "schema_version": "source-html-manifest/v0",
397
+ "pages": [
398
+ {
399
+ "page_id": "landing",
400
+ "path": "landing.html",
401
+ "source_hash": "<64 lowercase hex>",
402
+ "screenshots": [
403
+ {
404
+ "id": "source-landing-desktop",
405
+ "kind": "source_screenshot",
406
+ "viewport": "desktop",
407
+ "availability": "available",
408
+ "path": ".campaigns-os/screenshots/landing-desktop.png",
409
+ "sha256": "<64 lowercase hex>",
410
+ "width": 1440,
411
+ "height": 900,
412
+ "device_profile": "desktop-1440x900",
413
+ "browser": "playwright-chromium/151.0.7922.34",
414
+ "captured_at": "2026-09-06T01:39:57.250Z"
415
+ },
416
+ { "viewport": "mobile", "path": ".campaigns-os/screenshots/landing-mobile.png" }
417
+ ]
418
+ }
419
+ ]
420
+ }
421
+ ```
422
+
423
+ Three fields decide whether a record counts toward the gate:
424
+
425
+ - `viewport` must be `desktop`, `mobile`, or `tablet`. A record without a
426
+ recognized viewport is an asset, not proof, and is dropped.
427
+ - `availability` must be `available`, which means the record carries a `path` or
428
+ a `url`. An `unavailable` record needs an `unavailable_reason`; it documents
429
+ an absence and never satisfies a viewport.
430
+ - `kind` must be `source_screenshot`, which is the default when the field is
431
+ omitted and the only kind that counts as proof. `unavailable_render` is
432
+ accepted by the channel and retained, but it records an absence and never
433
+ satisfies a viewport. Any other kind — `render_reference`, `export_reference` —
434
+ is dropped for this channel rather than promoted.
435
+
436
+ You need one qualifying `desktop` record and one qualifying `mobile` record per
437
+ renderable page. `tablet` is optional in v0. A record that fails any of the three
438
+ tests is still dropped rather than rejecting the manifest, but it no longer goes
439
+ unreported: `start`/`prepare-build` and `doctor` warn once per bad record, naming
440
+ the record (`pages[i].screenshots[j]`), its `page_id`, and the field that failed,
441
+ so a typo shows up as a warning instead of as a page that silently stays blocked.
442
+
443
+ Everything else in the record is metadata that is retained but not required:
444
+ `id` (otherwise derived from the page surface, viewport, and a content digest),
445
+ `sha256` (64 lowercase hex, bare or `sha256:`-prefixed), `width`/`height` (positive integers, or a nested `dimensions` object),
446
+ `device_profile`, `scale_factor`, `browser`, `captured_at`, and `notes`. The
447
+ `source_ref_id` link that readiness requires is back-filled for you from the
448
+ page's own HTML source reference; you do not write it in the manifest.
449
+
450
+ Paths follow the manifest's own convention: relative to the source root passed
451
+ to `prepare-build`, not to the `.campaigns-os` directory. Keeping the PNGs
452
+ inside the source root — `.campaigns-os/screenshots/` is a good home — keeps the
453
+ manifest portable.
454
+
455
+ Editing the manifest changes its byte hash, which is part of the package's
456
+ provenance. Expect the recovery below to be required whenever you add or change
457
+ `screenshots[]`.
458
+
459
+ ### What counts as source proof
460
+
461
+ A `source_screenshot` attests what the merchant's design actually looks like.
462
+ That means a **standalone HTML document**: a complete page that renders on its
463
+ own in a browser — its own `<html>`, its own stylesheets and assets, no build
464
+ step. Render it at a desktop and a mobile viewport and the capture is honest
465
+ evidence of the design you are asked to preserve.
466
+
467
+ A **prepared page-kit fragment** is not that. A file that carries page-kit
468
+ frontmatter and a bare body fragment (the shape of the quick-start fixtures
469
+ under `examples/source-html/`) has no standalone appearance; screenshotting it
470
+ captures frontmatter text and an unstyled fragment. It is a faithful capture of
471
+ a file and it is not proof of a design.
472
+
473
+ The gate cannot tell the two apart — see the negative controls below — so this
474
+ distinction is yours to hold. When the source is fragments, or when the design
475
+ exists only inside a tool you cannot render, the honest record is an
476
+ `unavailable_render` with an `unavailable_reason`, which documents the absence
477
+ in the package but **does not clear the gate**. What would clear it honestly is
478
+ an accepted screenshot-absence Source Gap or an approved waiver, described under
479
+ [Gaps, TODOs, waivers, and readiness](#gaps-todos-waivers-and-readiness); in
480
+ v0 neither has an operator-authored input channel (the manifest carries no gap
481
+ key, `checkpoint waive` registers no design-source gate, and the package is not
482
+ hand-edited). A source that cannot be captured honestly therefore stays blocked
483
+ at intake in v0. Hold there and escalate; do not attest a capture of something
484
+ that was never a page.
485
+
486
+ ### Template-stock pages: the family decides
487
+
488
+ A third case is neither a standalone design nor an uncapturable one: the page
489
+ has no bespoke design at all, because the design *is* the starter template
490
+ family. There is nothing of the merchant's to screenshot, so a
491
+ `source_screenshot` would be a capture of stock the toolkit already ships.
492
+
493
+ Declare such a page out of source scope and intake treats it as template
494
+ stock. Two ways to declare it, both first-class:
495
+
496
+ - a per-page `skip_reason` entry in the source-html manifest — a `pages[]` entry
497
+ carrying `page_id` and `skip_reason` and no `path` (a page entry takes exactly
498
+ one of the two);
499
+ - `build_scope.mode: "partial"` on the CampaignSpec, when the whole scope is
500
+ partial rather than a few named pages.
501
+
502
+ Either declaration records the page on the assembly report's
503
+ `dec_page_scope_<page>` decision with `template_stock: true` and
504
+ `template_family` set to the family the packet locks, lists it under
505
+ `stages.prepare_build.declared_out_of_scope`, and reaches
506
+ `stages.prepare_build.status: "completed_partial"`. Intake demands no design
507
+ source for the page: no `capture-*` TODO, no `link-*` TODO. The build stage
508
+ materialises it from the locked family's own page of that role — the `next
509
+ build` prompt names every template-stock page and the family to copy it from —
510
+ and the family decides only *how* the package records its coverage:
511
+
512
+ - **A family that publishes complete Template Reference proof — today `apollo`
513
+ alone** — covers the page with synthesized `template_baseline` coverage from
514
+ the catalog's Template Reference. `readiness.status` is `ready`.
515
+ `src/partial-source-build.test.mjs` covers both declarations end to end
516
+ against `apollo`, including a clean re-run.
517
+ - **Every other family** has no Template Reference to link, so the package
518
+ records an accepted `coverage_absence` Source Gap for the page instead
519
+ (`template-stock-<surface>`, scope `primary_design_coverage`,
520
+ `attributed_by: "prepare-build"`, reason naming the locked family).
521
+ `readiness.status` is `ready_with_gaps` — ready, and honest that the page's
522
+ design is stock. `src/template-stock-intake.test.mjs` covers this on the
523
+ two-step fixture family, whose `select` step is template stock by
524
+ construction.
525
+
526
+ Until the build stage has materialised the stock pages, that is a partial
527
+ build and it carries partial-build limits: the declared pages appear under
528
+ `declared_out_of_scope` (with `declared_by` recording which mechanism declared
529
+ them) and under `derived.scope.out_of_scope_pages` (each carrying
530
+ `template_stock: true` and `template_family`); doctor labels only the mapped
531
+ routes as previewable, warns `CampaignSpec page "<id>" is template stock and
532
+ not built yet`, and keeps checkout launch and test-order proof blocked while a
533
+ runtime page (`select`, `checkout`, `upsell`, `receipt`) is among them. You get
534
+ a terminal, honest intake — not a fully proven campaign.
535
+
536
+ The build stage lifts those limits page by page. `next-campaigns-build`
537
+ materialises each template-stock page from the locked family's own page of
538
+ that role (the pre-checkout `select` step first, because it seeds the cart the
539
+ runtime pages read). Once the page's built HTML exists at its route under
540
+ `_site/<slug>/`, doctor reads the `template_stock` marker on the scope decision
541
+ and counts the page as built: it moves into `derived.scope.built_pages` (with
542
+ `template_stock: true`, `template_family`, and no `source_path`), joins the
543
+ previewable routes, no longer blocks runtime QA, and the ready list says
544
+ `Template-stock page(s) materialised by the build stage: <id> (<family>)`. A
545
+ declared page the build leaves unbuilt stays out of scope exactly as before.
546
+
547
+ One limit stands in this version: `polish capture` plans its routes from the
548
+ packet's mapped pages (`source_html.pages[].page_kit`), and a template-stock
549
+ mapping carries no `page_kit`, so a materialised stock page is outside the
550
+ polish capture plan (`route_scope: "selected"`) and its hidden-eager-media
551
+ checkpoint — the checkpoint does not demand a capture it cannot plan. Browser
552
+ QA resolves its routes from the CampaignSpec topology and covers the page.
553
+
554
+ Do not attest a screenshot of stock template output as a design source to get
555
+ past intake. It is no longer the only route through for a non-apollo family,
556
+ and it never was honest: the toolkit records such a page as a high-confidence
557
+ design-source match with a `source_hash`, and nothing downstream knows the
558
+ page is stock. Declare it instead.
559
+
560
+ The template-stock TODO that still fires — `link-<page>-template-reference`,
561
+ for a page that is *undeclared* and has no source HTML on a family without
562
+ Template Reference proof — names the family the packet locks (`--template-family`
563
+ first, then the CampaignSpec `preferred_template_family` hint), the same
564
+ family the `template-baseline` contribution's `presentation_intent` names.
565
+
566
+ ### Recovery after a blocked first run
567
+
568
+ A blocked run still emits the package, and `prepare-build` never refreshes a
569
+ package it did not just create. So a first run that blocked leaves a package
570
+ whose provenance names the *old* manifest, and simply rerunning with a new
571
+ manifest fails closed:
572
+
573
+ ```
574
+ campaigns-os: Design Source Package at <target>/.campaign-runtime/input/design-source-package.json
575
+ is invalid, stale, or contradictory: [design_source_package.current_html_funnel_material_stale] … ;
576
+ [design_source_package.current_source_material_stale] … "…/source-html-manifest.json" is missing or
577
+ has stale kind, role, or byte hash. …
578
+ ```
579
+
580
+ That refusal is the explicit reconciliation the ownership boundary requires. The
581
+ sanctioned sequence, from the source-preparation side:
582
+
583
+ ```bash
584
+ # 1. capture the proof and write it into the manifest
585
+ # <source-root>/.campaigns-os/source-html-manifest.json → pages[].screenshots[]
586
+
587
+ # 2. remove the package the blocked run emitted, so prepare-build re-synthesizes it
588
+ rm <page-kit-repository>/.campaign-runtime/input/design-source-package.json
589
+
590
+ # 3. rerun intake, from the Campaigns OS checkout (npm run resolves package.json
591
+ # from the current directory; run from the source directory it fails with a
592
+ # bare npm ENOENT)
593
+ npm run campaigns-os -- start \
594
+ --spec <campaign-spec.json> \
595
+ --source <prepared-html-directory> \
596
+ --target <page-kit-repository> \
597
+ --template-family <family>
598
+ ```
599
+
600
+ Step 2 is only ever correct for a package emitted by a blocked run that no
601
+ downstream stage has consumed. Once Build or Polish has bound its evidence to a
602
+ package fingerprint, deleting it invalidates that evidence; reconcile through
603
+ the stage-freshness lanes instead.
604
+
605
+ After the rerun, `readiness.status` is `ready` (or `ready_with_gaps` /
606
+ `ready_with_waivers` when accepted gaps or active waivers exist), `blocking_reasons`
607
+ is empty, the `capture-*` TODOs are gone, and doctor advances to assembly.
608
+
609
+ ### Negative controls: what the gate does not check
610
+
611
+ The package producer reads no files. It never opens the PNG at `path` and never
612
+ recomputes `sha256`; `sha256` is only checked for its spelling (64 lowercase hex,
613
+ bare or `sha256:`-prefixed) when present. A record pointing at a file that does not exist, and a record whose
614
+ `sha256` does not match its file, both clear the gate exactly as a real capture
615
+ does.
616
+
617
+ `screenshots[]` is therefore an **attestation**, not a verified artifact. The
618
+ toolkit takes your word that the capture is real, current, and of the thing it
619
+ names. Treat a wrong or absent file as what it is — a false claim about the
620
+ merchant's design that will surface as a mismatch during Polish, when there is
621
+ no honest evidence to compare against.
622
+
623
+ ### A read-only source root
624
+
625
+ Intake needs the source root to be readable, not writable. Every access
626
+ `prepare-build`, `start`, and doctor make under the source root is a read — the
627
+ HTML files, the manifest, the asset crawl — and every artifact the run produces
628
+ is written under the target repository: the Design Source Package at
629
+ `.campaign-runtime/input/design-source-package.json`, plus the Build Packet,
630
+ Build Context, Assembly Report, and normalized Build Brief. So the gate clears
631
+ from a writable target repo against a source root nobody can write to, and an
632
+ operator does not need a work copy of the source in order to run intake.
633
+
634
+ The proof itself can also stay outside the source tree. A visual reference is
635
+ available when it carries either a `path` or a `url`, and the producer opens no
636
+ file (see the negative controls above), so `screenshots[]` records that give a
637
+ `url` for their desktop and mobile captures satisfy the requirement with no PNG
638
+ bytes anywhere under the source root. An accepted screenshot-absence Source Gap
639
+ or an active approved `source_screenshot`-scope waiver clears it the same way,
640
+ where one exists — v0 has no operator channel for authoring either.
641
+
642
+ Two mechanics to plan around. The manifest is read from
643
+ `<source-root>/.campaigns-os/source-html-manifest.json` by default; when nobody
644
+ can write there, pass `--design-manifest <path>` to `start`, `prepare-build`,
645
+ or `build` and the same `source-html-manifest/v0` document is read from that
646
+ file instead — `pages[].path` and `files[].path` stay relative to `--source`,
647
+ never to the manifest. The Design Source Package records the file it read
648
+ (`contributions[html-funnel].provenance.manifest_path`, relative to the
649
+ package), and doctor validates that same file on every later run. A bare flag,
650
+ a missing file, or a manifest that fails validation is an error before
651
+ anything is written, not the warning-and-filesystem-fallback the default path
652
+ gets: you named the file. And the packet stores `source_html.root` relative to
653
+ the packet file, so a packet resolves its source from the location it was
654
+ written at: replay a run from the same place, or expect doctor to report
655
+ `source_html.root` as missing.
656
+
657
+ ## Lifecycle ownership and freshness
658
+
659
+ Prepare owns source normalization and the three package references. It records
660
+ Design Source Package readiness blockers in both `report.blockers` and
661
+ `report.stages.prepare_build.blockers`, and sets the Prepare stage to
662
+ `completed` or `blocked`. Prepare leaves Assembly pending and does **not** write
663
+ `stages.assembly.source_package_material_fingerprint`.
664
+
665
+ Build owns consumption. Before Assembly is marked complete, Build records its
666
+ build fingerprint and copies the current
667
+ `report.design_source_package.material_fingerprint` to
668
+ `stages.assembly.source_package_material_fingerprint`.
669
+
670
+ Polish owns its distinct evidence. A current Polish record must match:
671
+
672
+ - the current Assembly build fingerprint through
673
+ `stages.polish.source_build_fingerprint`; and
674
+ - the current Design Source Package material fingerprint through
675
+ `stages.polish.source_package_material_fingerprint`.
676
+
677
+ If a current package exists but Assembly's source-package fingerprint is
678
+ missing or different, the Polish gate returns
679
+ `polish.assembly_source_package_fingerprint_missing` or
680
+ `polish.assembly_source_package_stale`, and `campaigns-os next` routes back to
681
+ Build. `campaigns-os validate-assembly-report` applies the same missing-
682
+ fingerprint condition and fails with
683
+ `stages.assembly.source_package_material_fingerprint`, and reports a waiver
684
+ whose `expires_at` does not parse as
685
+ `stages.assembly.waiver_expires_at_invalid`, so a hand-authored report cannot
686
+ pass the standalone validator and then block the ladder. If Assembly is current but Polish's build or source-package fingerprint
687
+ is missing or stale, the route is back to Polish. A legacy report with no
688
+ current Design Source Package material fingerprint keeps build-only Polish
689
+ freshness and emits a warning.
690
+
691
+ The exceptional Assembly Source Freshness waiver lane remains explicit and
692
+ attributed. It can let Polish proceed despite missing/stale Assembly source
693
+ consumption, but Polish must still bind its own evidence to the current package;
694
+ the waiver is not a substitute for Polish Evidence.
695
+
696
+ Prepare blockers are coherent across the report. A terminal-looking
697
+ `stages.prepare_build.status` does not override `report.status: "blocked"`,
698
+ retained stage blockers, or retained top-level
699
+ `DESIGN_SOURCE_PACKAGE_NOT_READY` blockers.
700
+
701
+ For custom report locations, Build Context's `report_path` is the durable
702
+ pointer that packet-only `next` follows. Before any downstream stage is
703
+ selected, `next` verifies the context-to-packet pointer, report-to-packet and
704
+ report-to-context pointers, campaign Map ID and route slug, and the DSP
705
+ schema/hash/material/path binding across packet, context, and report. A missing
706
+ or foreign report, or any mismatched binding, fails closed at Prepare instead
707
+ of bypassing the earliest gate.
708
+
709
+ See [Polish evidence](./polish-evidence.md) for the full stage-evidence and
710
+ freshness gate.
711
+
712
+ ## Inspect, validate, and test
713
+
714
+ This repository is a private package checkout. Invoke its CLI through the npm
715
+ script form: `npm run campaigns-os -- <cmd>`.
716
+
717
+ From the target repo root, inspect the default artifact and recompute its
718
+ exact-byte audit hash with Node:
719
+
720
+ ```bash
721
+ node --input-type=module -e '
722
+ import { createHash } from "node:crypto";
723
+ import { readFileSync } from "node:fs";
724
+ const path = ".campaign-runtime/input/design-source-package.json";
725
+ const bytes = readFileSync(path);
726
+ const value = JSON.parse(bytes);
727
+ console.log(JSON.stringify({
728
+ path,
729
+ schema_version: value.schema_version,
730
+ readiness: value.readiness,
731
+ sha256: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
732
+ material_fingerprint: value.material_fingerprint
733
+ }, null, 2));
734
+ '
735
+ ```
736
+
737
+ From this repository root, validate a target package's standalone runtime
738
+ contract:
739
+
740
+ ```bash
741
+ node --input-type=module -e '
742
+ import { readFileSync } from "node:fs";
743
+ import { validateDesignSourcePackage } from "./src/design-source-package.mjs";
744
+ const value = JSON.parse(readFileSync(process.argv[1], "utf8"));
745
+ const result = validateDesignSourcePackage(value);
746
+ console.log(JSON.stringify(result, null, 2));
747
+ if (!result.ok) process.exitCode = 1;
748
+ ' <page-kit-repository>/.campaign-runtime/input/design-source-package.json
749
+ ```
750
+
751
+ The standalone validator does not know the current source directory or
752
+ CampaignSpec. Before downstream stage evidence exists, rerun the real producer
753
+ with the same current inputs to exercise current campaign/page/source/template
754
+ validation and exact-byte reuse:
755
+
756
+ ```bash
757
+ npm run campaigns-os -- prepare-build \
758
+ --spec <campaign-spec.json> \
759
+ --source <prepared-html-directory> \
760
+ --target <page-kit-repository> \
761
+ --template-family <family> \
762
+ --no-run-session \
763
+ --json
764
+ ```
765
+
766
+ `prepare-build` protects an Assembly Report that already contains downstream
767
+ stage evidence; do not use a destructive override merely to perform a check.
768
+ Use Doctor and `next` to inspect the cross-artifact and lifecycle gates:
769
+
770
+ ```bash
771
+ npm run campaigns-os -- doctor --packet <page-kit-repository>/campaign-runtime.build.json
772
+ npm run campaigns-os -- next --packet <page-kit-repository>/campaign-runtime.build.json --json
773
+ ```
774
+
775
+ Run the focused contract and negative-control suite from this repository:
776
+
777
+ ```bash
778
+ node --test \
779
+ src/design-source-package.test.mjs \
780
+ src/design-source-package-prepare-build.test.mjs \
781
+ src/design-source-package-polish.integration.test.mjs \
782
+ src/polish-gate.test.mjs
783
+ npm run check
784
+ ```