@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,438 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://nextcommerce.com/schemas/campaigns-os-qa-verdict.v0.schema.json",
4
+ "title": "Campaigns OS QA Verdict v0",
5
+ "description": "The full QA verdict `campaigns-os qa run` writes under qa-output/<map-id>/<run-id>.json and publishes to the QA portal receiver. Contract identity is campaigns-os-qa-verdict/v0; the emitted schema_version FIELD is the literal \"1.0\" (it predates the slash-versioned naming convention, and the receiving Worker validates that same literal — changing the emitted value is a breaking shape change). Additions to v0 are expected; consumers must tolerate unknown fields, so additionalProperties stays true. Full verdicts carry live storefront URLs, request evidence, and order references and are gitignored — the committed form is the allowlist projection described by campaigns-os-qa-verdict-sidecar.v0.schema.json. Trust semantics: the CLI never emits trusted/trust_level/verified_at; the QA verdict receiver stamps them server-side at ingest, so they appear only on records read back from the portal. Schema-validity is NOT trust — a forged record can be shape-valid. Every downstream consumer MUST filter or segregate records carrying trusted: false. See docs/qa-and-test-orders.md (Verdict schema and trust semantics).",
6
+ "type": "object",
7
+ "additionalProperties": true,
8
+ "required": [
9
+ "schema_version",
10
+ "run_id",
11
+ "campaign_slug",
12
+ "spec_version",
13
+ "spec_hash",
14
+ "started_at",
15
+ "completed_at",
16
+ "runtime",
17
+ "disposition",
18
+ "assertions",
19
+ "test_orders",
20
+ "exceptions"
21
+ ],
22
+ "properties": {
23
+ "schema_version": {
24
+ "const": "1.0",
25
+ "description": "The emitted schema-version literal. \"1.0\" is the campaigns-os-qa-verdict/v0 shape; both src/qa-verdict.mjs validateVerdict and the portal receiver reject any other value."
26
+ },
27
+ "run_id": {
28
+ "type": "string",
29
+ "minLength": 1,
30
+ "description": "The QA run identity, also the verdict filename under qa-output/<map-id>/. The portal receiver additionally restricts it to [A-Za-z0-9_-]{1,64}."
31
+ },
32
+ "campaign_slug": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "description": "Carries the Campaign Map ID for schema back-compat; the true public route slug rides alongside as public_route_slug so consumers stop conflating the two."
36
+ },
37
+ "public_route_slug": {
38
+ "type": ["string", "null"],
39
+ "description": "The campaign's public route slug. null (or absent on verdicts emitted before the field existed) when no slug was resolved."
40
+ },
41
+ "campaign_ref_id": {
42
+ "type": ["integer", "null"],
43
+ "description": "The platform campaign ref id from the CampaignSpec, when available."
44
+ },
45
+ "spec_version": {
46
+ "type": "string",
47
+ "minLength": 1,
48
+ "description": "CampaignSpec schema version the run validated against (e.g. \"4.3\"), or a runner-scoped marker such as \"parity-fixture-<version>\" for fixture-driven parity runs."
49
+ },
50
+ "spec_hash": {
51
+ "type": "string",
52
+ "minLength": 1,
53
+ "description": "Hash of the CampaignSpec the run used, prefixed with the algorithm (e.g. \"sha256:<hex>\")."
54
+ },
55
+ "started_at": {
56
+ "type": "string",
57
+ "minLength": 1,
58
+ "description": "ISO 8601 timestamp the QA run started."
59
+ },
60
+ "completed_at": {
61
+ "type": "string",
62
+ "minLength": 1,
63
+ "description": "ISO 8601 timestamp the verdict was finalized."
64
+ },
65
+ "runtime": {
66
+ "type": "string",
67
+ "minLength": 1,
68
+ "description": "The producing runtime identity, e.g. \"campaigns-os-node-qa@0.1.0-alpha.0\"."
69
+ },
70
+ "operator": {
71
+ "type": "string",
72
+ "description": "Best-effort local operator identity (e.g. \"<user>@local\"). Empty string when unresolvable. NOT verified attribution — see the trust fields."
73
+ },
74
+ "base_url": {
75
+ "type": "string",
76
+ "description": "The --base-url or packet deploy URL the run resolved pages against. Omitted when the run had none. URL-bearing: emptied from the committed sidecar projection."
77
+ },
78
+ "entry_urls": {
79
+ "type": "array",
80
+ "items": { "$ref": "#/$defs/pageUrlRef" },
81
+ "description": "One entry-page reference per funnel topology. URL-bearing: emptied ([]) in the committed sidecar projection."
82
+ },
83
+ "page_urls": {
84
+ "type": "array",
85
+ "items": { "$ref": "#/$defs/pageUrlRef" },
86
+ "description": "Every resolved page URL across the run's funnel topologies, deduplicated by URL. URL-bearing: emptied ([]) in the committed sidecar projection."
87
+ },
88
+ "tested_urls": {
89
+ "type": "array",
90
+ "items": { "$ref": "#/$defs/pageUrlRef" },
91
+ "description": "Pages that produced an http:<page_id> assertion, joined back to the known page references. URL-bearing: emptied ([]) in the committed sidecar projection."
92
+ },
93
+ "disposition": {
94
+ "type": "string",
95
+ "enum": ["ready", "ready_with_exceptions", "blocked"],
96
+ "description": "Derived from the assertions (and the commercial section's status): any unwaived failing blocker => blocked; any warn/manual_review/waived assertion or a proven commercial mismatch => ready_with_exceptions; otherwise ready. Waived is never clean. ready_with_waivers is the doctor/next checkpoint term, never a verdict disposition."
97
+ },
98
+ "assertions": {
99
+ "type": "array",
100
+ "items": { "$ref": "#/$defs/assertion" },
101
+ "description": "The flat assertion list every QA leg serializes into. The portal receiver caps validation at 500 entries."
102
+ },
103
+ "test_orders": {
104
+ "type": "array",
105
+ "items": { "$ref": "#/$defs/testOrder" },
106
+ "description": "One entry per typed-card browser test order the run drove, with persisted-order verification and sanitized request evidence."
107
+ },
108
+ "exceptions": {
109
+ "type": "array",
110
+ "items": { "$ref": "#/$defs/exception" },
111
+ "description": "Derived projection of every assertion that failed, warned, needs manual review, or carries warn/blocker severity — the quick exception read without re-scanning assertions."
112
+ },
113
+ "commercial": {
114
+ "$ref": "#/$defs/commercial",
115
+ "description": "The commercial parity section, present on runs where calculate scenarios were planned (or explicitly recorded as unavailable). Absent on verdicts emitted before the leg existed."
116
+ },
117
+ "trusted": {
118
+ "type": "boolean",
119
+ "description": "SERVER-STAMPED, never CLI-emitted: the QA verdict receiver classifies each submission at ingest and stamps the stored record. false means the record arrived without the ingest credential (an anonymous submission) — shape-valid but unattributed. Present only on records read back from the receiver. Every downstream consumer MUST filter or segregate records with trusted: false; campaigns-os itself refuses to promote such a record into the committed sidecar and excludes it from run-record verdict inference."
120
+ },
121
+ "trust_level": {
122
+ "type": "string",
123
+ "enum": ["anonymous", "shared_secret"],
124
+ "description": "SERVER-STAMPED companion to trusted: how the submission authenticated. anonymous pairs with trusted: false; shared_secret pairs with trusted: true."
125
+ },
126
+ "verified_at": {
127
+ "type": ["string", "null"],
128
+ "description": "SERVER-STAMPED companion to trusted: ISO 8601 instant the receiver verified the ingest credential. null on untrusted records."
129
+ }
130
+ },
131
+ "$defs": {
132
+ "pageBinding": {
133
+ "type": "object",
134
+ "additionalProperties": false,
135
+ "required": [
136
+ "schema_version",
137
+ "observation",
138
+ "outcome",
139
+ "reason",
140
+ "source_kinds",
141
+ "identity"
142
+ ],
143
+ "properties": {
144
+ "schema_version": {
145
+ "const": "campaigns-os-page-binding/v0"
146
+ },
147
+ "observation": {
148
+ "const": "static_declaration"
149
+ },
150
+ "outcome": {
151
+ "enum": [
152
+ "match",
153
+ "mismatch",
154
+ "unknown"
155
+ ]
156
+ },
157
+ "reason": {
158
+ "enum": [
159
+ "credential_comparison",
160
+ "page_unavailable",
161
+ "conflicting_declarations",
162
+ "conflicting_expected",
163
+ "expected_unavailable",
164
+ "script_unavailable_or_limit",
165
+ "dynamic_unresolved",
166
+ "no_source"
167
+ ]
168
+ },
169
+ "source_kinds": {
170
+ "type": "array",
171
+ "uniqueItems": true,
172
+ "items": {
173
+ "enum": [
174
+ "meta",
175
+ "inline",
176
+ "config_script"
177
+ ]
178
+ }
179
+ },
180
+ "identity": {
181
+ "const": "not_verified"
182
+ }
183
+ },
184
+ "description": "Credential-free static declaration comparison, not observed execution or Campaign App identity. No keys, hashes, fragments, source URLs or inferred resource IDs."
185
+ },
186
+ "pageUrlRef": {
187
+ "type": "object",
188
+ "additionalProperties": true,
189
+ "required": ["url"],
190
+ "properties": {
191
+ "funnel_id": { "type": ["string", "null"] },
192
+ "funnel_name": { "type": ["string", "null"] },
193
+ "page_id": { "type": ["string", "null"] },
194
+ "page_type": { "type": ["string", "null"] },
195
+ "label": { "type": ["string", "null"] },
196
+ "url": { "type": "string", "minLength": 1 }
197
+ },
198
+ "description": "A funnel-topology page reference: identity fields plus the resolved live URL."
199
+ },
200
+ "assertion": {
201
+ "allOf": [
202
+ {
203
+ "if": {
204
+ "properties": {
205
+ "id": {
206
+ "type": "string",
207
+ "pattern": "^page-binding:"
208
+ }
209
+ },
210
+ "required": [
211
+ "id"
212
+ ]
213
+ },
214
+ "then": {
215
+ "properties": {
216
+ "family": {
217
+ "const": "api-metadata"
218
+ },
219
+ "evidence": {
220
+ "$ref": "#/$defs/pageBinding"
221
+ }
222
+ },
223
+ "required": [
224
+ "evidence"
225
+ ]
226
+ }
227
+ }
228
+ ],
229
+ "type": "object",
230
+ "additionalProperties": true,
231
+ "required": ["id", "family", "page", "status"],
232
+ "properties": {
233
+ "id": {
234
+ "type": "string",
235
+ "minLength": 1,
236
+ "description": "Stable assertion identity, e.g. \"http:checkout\", \"theme_gate.nothing_generatable\", \"analytics-correctness:purchase-fires\"."
237
+ },
238
+ "family": {
239
+ "type": "string",
240
+ "enum": [
241
+ "theme_gate",
242
+ "polish_gate",
243
+ "funnel-flow",
244
+ "meta-tags",
245
+ "api-metadata",
246
+ "browser-runtime",
247
+ "template_residue",
248
+ "pricing",
249
+ "browser-test-order",
250
+ "browser-receipt-rendering",
251
+ "analytics-correctness",
252
+ "analytics-parity",
253
+ "parity-capture"
254
+ ],
255
+ "description": "The canonical assertion-family vocabulary (QA_ASSERTION_FAMILY_VOCABULARY in src/qa-verdict.mjs; a drift test asserts set equality against the emitters). Append-only: a new emitter family is an additive schema change."
256
+ },
257
+ "page": {
258
+ "type": "string",
259
+ "minLength": 1,
260
+ "description": "The page_id or label the assertion is about, or the reserved value \"campaign\" for campaign-level assertions."
261
+ },
262
+ "url": {
263
+ "type": "string",
264
+ "description": "The live page URL the assertion ran against, when page-scoped. URL-bearing: stripped from the committed sidecar projection."
265
+ },
266
+ "status": {
267
+ "type": "string",
268
+ "enum": ["pass", "fail", "warn", "skipped", "manual_review"]
269
+ },
270
+ "severity": {
271
+ "type": "string",
272
+ "enum": ["info", "warn", "blocker"],
273
+ "description": "Present on issue-bearing assertions. The portal receiver requires it when status is fail or warn."
274
+ },
275
+ "expected": { "type": "string" },
276
+ "actual": { "type": "string" },
277
+ "evidence": {
278
+ "type": "object",
279
+ "description": "Free-form structured evidence for this assertion (gate codes, capture summaries, blocked_by references, step ladders). Shape varies by family and is not individually contracted here. Stripped from the committed sidecar projection."
280
+ },
281
+ "waiver": { "$ref": "#/$defs/waiver" }
282
+ }
283
+ },
284
+ "waiver": {
285
+ "type": "object",
286
+ "additionalProperties": true,
287
+ "required": ["reason", "waived_by", "waived_at"],
288
+ "properties": {
289
+ "reason": { "type": "string", "minLength": 1 },
290
+ "waived_by": { "type": "string", "minLength": 1 },
291
+ "waived_at": { "type": "string", "minLength": 1 }
292
+ },
293
+ "description": "A bounded named-human waiver riding an assertion. Any assertion carrying one keeps the disposition at ready_with_exceptions — waived is never clean."
294
+ },
295
+ "exception": {
296
+ "type": "object",
297
+ "additionalProperties": true,
298
+ "required": ["id", "family", "page", "status", "severity"],
299
+ "properties": {
300
+ "id": { "type": ["string", "null"] },
301
+ "family": { "type": ["string", "null"] },
302
+ "page": { "type": ["string", "null"] },
303
+ "url": {
304
+ "type": ["string", "null"],
305
+ "description": "URL-bearing: null/absent in the committed sidecar projection."
306
+ },
307
+ "status": { "type": ["string", "null"] },
308
+ "severity": { "type": ["string", "null"] },
309
+ "expected": { "type": "string" },
310
+ "actual": { "type": "string" }
311
+ },
312
+ "description": "deriveExceptions output: the source assertion's identity fields. Values are nullable because the projection copies whatever the assertion carried."
313
+ },
314
+ "testOrder": {
315
+ "type": "object",
316
+ "additionalProperties": true,
317
+ "required": ["path", "ok", "verification", "evidence"],
318
+ "properties": {
319
+ "path": {
320
+ "type": "string",
321
+ "minLength": 1,
322
+ "description": "The planned order path, e.g. \"checkout\", \"accept\", \"accept-decline-accept\", or a tier/coupon plan label such as \"checkout@tier:8\"."
323
+ },
324
+ "ok": { "type": "boolean" },
325
+ "plan_id": { "type": "string" },
326
+ "plan": {
327
+ "type": "object",
328
+ "description": "Summary of a spec-derived tier/coupon plan (--test-order tiers), when the order ran under one."
329
+ },
330
+ "next_order_id": { "type": ["string", "null"] },
331
+ "ref_id": { "type": ["string", "null"] },
332
+ "qa_email": {
333
+ "type": ["string", "null"],
334
+ "description": "Always the redaction marker (\"[redacted-qa-email]\") — the generated QA email itself never enters the verdict."
335
+ },
336
+ "is_test": { "type": ["boolean", "null"] },
337
+ "payment_method": { "type": ["string", "null"] },
338
+ "card": {
339
+ "type": "object",
340
+ "additionalProperties": true,
341
+ "properties": { "last4": { "type": "string" } },
342
+ "description": "Card last4 only; full test-card numbers never enter the verdict."
343
+ },
344
+ "checkout_url": { "type": "string" },
345
+ "final_url": { "type": ["string", "null"] },
346
+ "cart_state": { "type": ["object", "null"] },
347
+ "receipt_line_items": { "type": "array" },
348
+ "final_receipt_line_items": { "type": "array" },
349
+ "vouchers": { "type": "array" },
350
+ "discount_total": { "type": ["number", "null"] },
351
+ "upsell": { "type": "object" },
352
+ "upsell_steps": { "type": "array" },
353
+ "receipt_rendering": { "type": "object" },
354
+ "data_layer": {
355
+ "type": "object",
356
+ "additionalProperties": true,
357
+ "description": "The order's SDK data layer across every page the path visited after checkout (campaigns-os#325): exactly one dl_purchase in window.NextDataLayer whose transaction_id is this order's number or ref id. The SDK raises dl_purchase on the first ref_id page that fetches the order back (the upsell page on a funnel with one, the receipt otherwise) and dedupes it on every later page, so the reading is the whole post-checkout journey, never a snapshot of the terminal document. Present only when the path placed an order; the analytics-correctness:data-layer-purchase:<path> assertion is its judgment. Counts are per the SDK's own array, never the mirrored window.dataLayer, so a GTM adapter re-push is not a duplicate; dl_upsell_purchase is a different event and is never counted. An unhooked or unreadable layer is recorded as measured: false with null counts, not as a zero reading.",
358
+ "required": ["layer", "event", "measured", "outcome", "ok", "reason"],
359
+ "properties": {
360
+ "layer": { "const": "NextDataLayer" },
361
+ "event": { "const": "dl_purchase" },
362
+ "required": { "type": "boolean" },
363
+ "measured": { "type": "boolean", "description": "false when the array could not be read; every count below is then null." },
364
+ "count": { "type": ["integer", "null"], "minimum": 0, "description": "dl_purchase pushes to the layer across every page after the order." },
365
+ "expected_order_refs": { "type": "array", "items": { "type": "string" }, "description": "The placed order's number and ref id, either of which the SDK may report as transaction_id." },
366
+ "observed_transaction_ids": { "type": ["array", "null"], "items": { "type": ["string", "null"] }, "description": "transaction_id of each dl_purchase, in push order." },
367
+ "order_ref_match": { "type": ["boolean", "null"], "description": "Whether a dl_purchase named the placed order; null when nothing could be matched." },
368
+ "outcome": { "type": "string", "enum": ["pass", "absent", "duplicate", "mismatch", "order_ref_unknown", "unmeasured"], "description": "pass = one event naming the order; absent = none; duplicate = more than one (the #302 fail); mismatch = one event naming another order or none; order_ref_unknown = one event but the run recorded no order reference (manual review); unmeasured = the layer could not be hooked or read (blocker)." },
369
+ "ok": { "type": ["boolean", "null"] },
370
+ "reason": { "type": "string" }
371
+ }
372
+ },
373
+ "verification": {
374
+ "type": "object",
375
+ "additionalProperties": true,
376
+ "properties": {
377
+ "verified": { "type": "boolean" },
378
+ "order_create_status": { "type": ["integer", "null"] },
379
+ "order_read_status": { "type": ["integer", "null"] },
380
+ "total_incl_tax": { "type": ["string", "null"] },
381
+ "currency": { "type": ["string", "null"] },
382
+ "order_create_observation": { "type": "string" },
383
+ "error": { "type": ["string", "null"] }
384
+ },
385
+ "description": "Persisted-order read-back verification — the authoritative proof path."
386
+ },
387
+ "evidence": {
388
+ "type": "object",
389
+ "additionalProperties": true,
390
+ "properties": {
391
+ "steps": {
392
+ "type": "array",
393
+ "description": "The step-ladder: ordered, individually timed named steps ({step, status, started_at, duration_ms, detail?, evidence?, error?}), appended as each finishes so a crash still leaves the ladder up to the failure point."
394
+ },
395
+ "events": {
396
+ "type": "object",
397
+ "description": "Sanitized network/console event capture. Checkout request bodies and generated QA emails are redacted before serialization."
398
+ },
399
+ "order_request_seen": { "type": "boolean" },
400
+ "spreedly_tokenized": { "type": "boolean" }
401
+ }
402
+ }
403
+ }
404
+ },
405
+ "commercial": {
406
+ "type": "object",
407
+ "additionalProperties": true,
408
+ "required": ["schema_version", "status"],
409
+ "properties": {
410
+ "schema_version": { "const": "campaigns-os-commercial-parity/v0" },
411
+ "status": {
412
+ "type": "string",
413
+ "enum": ["pass", "mismatch", "incomplete", "not_applicable", "not_run"],
414
+ "description": "mismatch keeps the verdict at ready_with_exceptions; incomplete preserves the disposition derived from ordinary assertions and never invents an exception."
415
+ },
416
+ "coverage_complete": { "type": "boolean" },
417
+ "findings": {
418
+ "type": "array",
419
+ "description": "Proven contract-governed claim mismatches, serialized deterministically into the flat assertions array as warn-severity pricing assertions."
420
+ },
421
+ "issues": {
422
+ "type": "array",
423
+ "items": {
424
+ "type": "object",
425
+ "additionalProperties": true,
426
+ "required": ["code"],
427
+ "properties": {
428
+ "code": { "type": "string", "minLength": 1 },
429
+ "count": { "type": "integer" }
430
+ }
431
+ },
432
+ "description": "Sanitized capture/proxy/limit issue codes with counts. An issue records incomplete evidence; it never invents a mismatch."
433
+ }
434
+ },
435
+ "description": "The compact commercial-parity report: counters over checked pages, scenarios, and claims, plus findings and issues. Counter fields are additive and not individually contracted here."
436
+ }
437
+ }
438
+ }
@@ -0,0 +1,162 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://nextcommerce.com/schemas/campaigns-os-release-ledger.v1.schema.json",
4
+ "title": "Campaigns OS Release Ledger v1",
5
+ "description": "The append-only, agent-relevant release ledger. One ordered entry per accepted release or reviewed amendment; one change item per agent-relevant semantic change. An entry deliberately carries NO commit identifier: an entry cannot name the commit that contains it without being rewritten after the fact, so a consumer derives the introducing commit from Git history at the immutable target OID instead. Historical entries are never rewritten; a correction ships as a new amendment entry. A consumer that recognizes v1 accepts and preserves unknown additive properties without interpreting them; required fields, known types, and safety-critical enums remain strict.",
6
+ "type": "object",
7
+ "additionalProperties": true,
8
+ "required": ["schema_version", "entries"],
9
+ "properties": {
10
+ "schema_version": {
11
+ "const": "campaigns-os-release-ledger/v1"
12
+ },
13
+ "_note": {
14
+ "type": "string",
15
+ "description": "Free-text authoring note. Not consumed."
16
+ },
17
+ "entries": {
18
+ "type": "array",
19
+ "description": "Ordered oldest to newest. sequence is strictly increasing by 1 from 1, and date is non-decreasing.",
20
+ "items": { "$ref": "#/$defs/entry" }
21
+ }
22
+ },
23
+ "$defs": {
24
+ "entry": {
25
+ "type": "object",
26
+ "additionalProperties": true,
27
+ "required": [
28
+ "id",
29
+ "sequence",
30
+ "date",
31
+ "kind",
32
+ "surface_version",
33
+ "changelog_section",
34
+ "changelog_sha256",
35
+ "agent_impact",
36
+ "compatibility",
37
+ "migration",
38
+ "changes",
39
+ "entry_sha256"
40
+ ],
41
+ "properties": {
42
+ "id": {
43
+ "type": "string",
44
+ "pattern": "^RL-[0-9]{4}$",
45
+ "description": "Stable, immutable entry identity. Never reused, never renumbered."
46
+ },
47
+ "sequence": {
48
+ "type": "integer",
49
+ "minimum": 1,
50
+ "description": "Position in the append-only order. Strictly increasing by 1."
51
+ },
52
+ "date": {
53
+ "type": "string",
54
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
55
+ "description": "The acceptance date. Non-decreasing across entries."
56
+ },
57
+ "kind": {
58
+ "type": "string",
59
+ "enum": ["release", "amendment"],
60
+ "description": "release: a change to the repository. amendment: a reviewed correction to an earlier entry's meaning. An amendment is the ONLY entry kind permitted to carry change items that map to no changed path in its own commit range."
61
+ },
62
+ "amends": {
63
+ "type": "string",
64
+ "pattern": "^RL-[0-9]{4}$",
65
+ "description": "Required on kind: amendment; forbidden on kind: release. The earlier entry this corrects. The earlier entry's bytes are not touched."
66
+ },
67
+ "amendment_reason": {
68
+ "type": "string",
69
+ "minLength": 1,
70
+ "description": "Required on kind: amendment. Why the earlier entry was wrong or incomplete."
71
+ },
72
+ "surface_version": {
73
+ "description": "The supported-surface version this entry accompanies, or null for a same-surface change. A surface_version change owes exactly one entry with that version.",
74
+ "oneOf": [
75
+ { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
76
+ { "type": "null" }
77
+ ]
78
+ },
79
+ "changelog_section": {
80
+ "type": "string",
81
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(\\+agent\\.[0-9]+)?$",
82
+ "description": "The exact CHANGELOG.md section identifier this entry links to, as it appears in the heading '## [<id>] - <date>'. A surface release uses the bare version. A same-surface change uses '<current-version>+agent.<n>', which sorts at the same version and never claims a release that did not happen. Exactly one section may carry a given identifier."
83
+ },
84
+ "changelog_sha256": {
85
+ "type": "string",
86
+ "pattern": "^[0-9a-f]{64}$",
87
+ "description": "SHA-256 over the exact UTF-8 bytes of the linked changelog section body, heading line included, trailing whitespace trimmed. A body edit without a hash update fails the correspondence gate."
88
+ },
89
+ "agent_impact": {
90
+ "type": "string",
91
+ "minLength": 1,
92
+ "description": "What a downstream agent must do differently, in plain language. 'None' is a valid assertion but must be written, not omitted."
93
+ },
94
+ "compatibility": {
95
+ "type": "string",
96
+ "enum": ["compatible", "additive", "breaking"],
97
+ "description": "compatible: no consumer change. additive: new surface a consumer may adopt. breaking: an existing consumer stops working without action."
98
+ },
99
+ "migration": {
100
+ "type": "string",
101
+ "minLength": 1,
102
+ "description": "The exact operator or consumer action, or the literal string 'none'. A breaking entry may not say 'none'."
103
+ },
104
+ "changes": {
105
+ "type": "array",
106
+ "minItems": 1,
107
+ "description": "One item per agent-relevant semantic change. Change identity is (class, path, surface_entry) and must be unique within this entry; later releases may record a new change to the same path.",
108
+ "items": { "$ref": "#/$defs/change" }
109
+ },
110
+ "entry_sha256": {
111
+ "type": "string",
112
+ "pattern": "^[0-9a-f]{64}$",
113
+ "description": "SHA-256 over the canonical JSON of this entry with entry_sha256 removed. Makes an in-place edit of a historical entry detectable even in a squashed history."
114
+ },
115
+ "introducing_commit": false,
116
+ "containing_commit": false
117
+ }
118
+ },
119
+ "change": {
120
+ "type": "object",
121
+ "additionalProperties": true,
122
+ "required": ["class", "path", "surface_entry", "summary"],
123
+ "properties": {
124
+ "class": {
125
+ "type": "string",
126
+ "enum": [
127
+ "schema",
128
+ "hashed_surface",
129
+ "named_surface",
130
+ "cli_surface",
131
+ "skill",
132
+ "package_export",
133
+ "compatibility_policy",
134
+ "documentation",
135
+ "workflow",
136
+ "generated_runtime"
137
+ ],
138
+ "description": "The semantic class, defined once in contracts/agent-relevant-change-policy.v1.json. The enum here is checked against that file."
139
+ },
140
+ "path": {
141
+ "description": "The repository-relative path this change covers, or null when the class has no single path (a CLI flag, for example). A non-null path must classify to this item's class under the change policy.",
142
+ "oneOf": [
143
+ { "type": "string", "minLength": 1 },
144
+ { "type": "null" }
145
+ ]
146
+ },
147
+ "surface_entry": {
148
+ "description": "The contracts/supported-surface.json entry this change affects (a hashed path, a named path, a cli_commands name, a package_exports subpath, or a bin name), or null when the change touches no supported entry.",
149
+ "oneOf": [
150
+ { "type": "string", "minLength": 1 },
151
+ { "type": "null" }
152
+ ]
153
+ },
154
+ "summary": {
155
+ "type": "string",
156
+ "minLength": 1,
157
+ "description": "One sentence naming what changed, written for a consumer, not for the author."
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }