@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,1073 @@
1
+ {
2
+ "_note": "The two-way release-gate matrix, as data. scripts/check-release-ledger.test.mjs drives every case through the REAL classifier and the REAL change policy (contracts/agent-relevant-change-policy.v1.json) — only the supported surface and the ledger contents are fixture-supplied. Adding a case here adds a test; nothing is asserted in prose. These cases are this repository's own test data and are deliberately NOT declared supported surface: a consumer depends on contracts/fixtures/orientation/envelope/** and .../hostile-target/**, not on this file.",
3
+ "schema": "campaigns-os-release-gate-cases/v1",
4
+ "hash_note": "Unless a case sets \"hash_mode\": \"as_written\", the harness computes entry_sha256 and changelog_sha256 for every entry before validating, so a case can be authored by hand and still exercise the hash gates. A case that WANTS a bad hash sets as_written and writes the wrong one.",
5
+ "default_surface": {
6
+ "surface_version": "1.1.0",
7
+ "package_exports": [],
8
+ "bin": [],
9
+ "cli_commands": ["build"],
10
+ "hashed": {
11
+ "schemas/fixture-example.v0.schema.json": { "sha256": "0000000000000000000000000000000000000000000000000000000000000000" }
12
+ },
13
+ "named": ["CHANGELOG.md", "contracts/release-ledger.json", "docs/build-packet.md", "skills.json"]
14
+ },
15
+ "default_base_surface_version": "1.0.0",
16
+ "default_changelog": "# Changelog\n\n## [1.1.0] - 2026-02-02\n\n### Added\n\n- A fixture release section.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
17
+ "cases": [
18
+ {
19
+ "id": "surface-bump-with-matching-entry",
20
+ "description": "A hashed schema changed, the surface version advanced, and exactly one ledger entry claims it.",
21
+ "expect": "pass",
22
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
23
+ "head_entries": [
24
+ {
25
+ "id": "RL-0001",
26
+ "sequence": 1,
27
+ "date": "2026-02-02",
28
+ "kind": "release",
29
+ "surface_version": "1.1.0",
30
+ "changelog_section": "1.1.0",
31
+ "agent_impact": "Revalidate artifacts against the new schema revision.",
32
+ "compatibility": "additive",
33
+ "migration": "none",
34
+ "changes": [
35
+ {
36
+ "class": "schema",
37
+ "path": "schemas/fixture-example.v0.schema.json",
38
+ "surface_entry": "schemas/fixture-example.v0.schema.json",
39
+ "summary": "An optional field was added to the fixture schema."
40
+ }
41
+ ]
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ "id": "same-surface-named-doc-change",
47
+ "description": "A named contract doc changed with no surface bump. The entry carries surface_version null and links a +agent changelog section.",
48
+ "expect": "pass",
49
+ "surface_version_unchanged": true,
50
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Changed\n\n- A contract doc gained a section a consumer reads.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
51
+ "surface": { "surface_version": "1.0.0" },
52
+ "changed_paths": ["docs/build-packet.md"],
53
+ "head_entries": [
54
+ {
55
+ "id": "RL-0001",
56
+ "sequence": 1,
57
+ "date": "2026-02-03",
58
+ "kind": "release",
59
+ "surface_version": null,
60
+ "changelog_section": "1.0.0+agent.1",
61
+ "agent_impact": "Re-read the build packet doc; the packet shape did not change but its documented meaning did.",
62
+ "compatibility": "compatible",
63
+ "migration": "none",
64
+ "changes": [
65
+ {
66
+ "class": "named_surface",
67
+ "path": "docs/build-packet.md",
68
+ "surface_entry": "docs/build-packet.md",
69
+ "summary": "The packet doc now states the skip-reason rule explicitly."
70
+ }
71
+ ]
72
+ }
73
+ ]
74
+ },
75
+ {
76
+ "id": "same-surface-cli-flag-change",
77
+ "description": "A CLI flag changed. The bytes moved in src/cli.mjs; the ledger records a path-less cli_surface item because the flag has no file of its own.",
78
+ "expect": "pass",
79
+ "surface_version_unchanged": true,
80
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Changed\n\n- A supported command gained a flag.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
81
+ "surface": { "surface_version": "1.0.0" },
82
+ "changed_paths": ["src/cli.mjs"],
83
+ "head_entries": [
84
+ {
85
+ "id": "RL-0001",
86
+ "sequence": 1,
87
+ "date": "2026-02-03",
88
+ "kind": "release",
89
+ "surface_version": null,
90
+ "changelog_section": "1.0.0+agent.1",
91
+ "agent_impact": "The build command accepts a new flag; existing invocations are unaffected.",
92
+ "compatibility": "additive",
93
+ "migration": "none",
94
+ "changes": [
95
+ { "class": "cli_surface", "path": null, "surface_entry": "build", "summary": "campaigns-os build gained a flag." },
96
+ { "class": "cli_surface", "path": "src/cli.mjs", "surface_entry": "build", "summary": "The dispatch table learned the new flag." }
97
+ ]
98
+ }
99
+ ]
100
+ },
101
+ {
102
+ "id": "same-surface-skill-change",
103
+ "description": "A skill package changed with no surface bump.",
104
+ "expect": "pass",
105
+ "surface_version_unchanged": true,
106
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Changed\n\n- A skill changed what it tells an agent to do.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
107
+ "surface": { "surface_version": "1.0.0" },
108
+ "changed_paths": ["skills/example-skill/SKILL.md", "skills.json"],
109
+ "head_entries": [
110
+ {
111
+ "id": "RL-0001",
112
+ "sequence": 1,
113
+ "date": "2026-02-03",
114
+ "kind": "release",
115
+ "surface_version": null,
116
+ "changelog_section": "1.0.0+agent.1",
117
+ "agent_impact": "Reinstall skills; the example skill's instructions changed.",
118
+ "compatibility": "compatible",
119
+ "migration": "Run the skills installer again.",
120
+ "changes": [
121
+ { "class": "skill", "path": "skills/example-skill/SKILL.md", "surface_entry": null, "summary": "The example skill's instructions changed." },
122
+ { "class": "skill", "path": "skills.json", "surface_entry": "skills.json", "summary": "The example skill's version advanced." }
123
+ ]
124
+ }
125
+ ]
126
+ },
127
+ {
128
+ "id": "same-surface-workflow-change",
129
+ "description": "A repository workflow that produces a supported artifact changed.",
130
+ "expect": "pass",
131
+ "surface_version_unchanged": true,
132
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Changed\n\n- The catalog refresh workflow changed its serialization.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
133
+ "surface": { "surface_version": "1.0.0" },
134
+ "changed_paths": [".github/workflows/refresh-example.yml"],
135
+ "head_entries": [
136
+ {
137
+ "id": "RL-0001",
138
+ "sequence": 1,
139
+ "date": "2026-02-03",
140
+ "kind": "release",
141
+ "surface_version": null,
142
+ "changelog_section": "1.0.0+agent.1",
143
+ "agent_impact": "None for a consumer reading artifacts; the producing workflow now serializes writers.",
144
+ "compatibility": "compatible",
145
+ "migration": "none",
146
+ "changes": [
147
+ { "class": "workflow", "path": ".github/workflows/refresh-example.yml", "surface_entry": null, "summary": "The refresh workflow serializes concurrent writers." }
148
+ ]
149
+ }
150
+ ]
151
+ },
152
+ {
153
+ "id": "same-surface-compatibility-policy-change",
154
+ "description": "The published compatibility statement changed with no surface bump.",
155
+ "expect": "pass",
156
+ "surface_version_unchanged": true,
157
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Changed\n\n- The compatibility statement widened a supported range.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
158
+ "surface": { "surface_version": "1.0.0" },
159
+ "changed_paths": ["compatibility.json"],
160
+ "head_entries": [
161
+ {
162
+ "id": "RL-0001",
163
+ "sequence": 1,
164
+ "date": "2026-02-03",
165
+ "kind": "release",
166
+ "surface_version": null,
167
+ "changelog_section": "1.0.0+agent.1",
168
+ "agent_impact": "A wider CampaignSpec range is accepted; re-read the compatibility statement before pinning.",
169
+ "compatibility": "additive",
170
+ "migration": "none",
171
+ "changes": [
172
+ { "class": "compatibility_policy", "path": "compatibility.json", "surface_entry": null, "summary": "The accepted CampaignSpec range widened." }
173
+ ]
174
+ }
175
+ ]
176
+ },
177
+ {
178
+ "id": "multiple-changes-in-one-entry",
179
+ "description": "Several agent-relevant changes ship together. One entry, distinct change items, each path covered exactly once.",
180
+ "expect": "pass",
181
+ "changed_paths": ["schemas/fixture-example.v0.schema.json", "docs/build-packet.md", "src/cli.mjs"],
182
+ "head_entries": [
183
+ {
184
+ "id": "RL-0001",
185
+ "sequence": 1,
186
+ "date": "2026-02-02",
187
+ "kind": "release",
188
+ "surface_version": "1.1.0",
189
+ "changelog_section": "1.1.0",
190
+ "agent_impact": "Revalidate artifacts and re-read the packet doc.",
191
+ "compatibility": "additive",
192
+ "migration": "none",
193
+ "changes": [
194
+ { "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." },
195
+ { "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The doc describes the new field." },
196
+ { "class": "cli_surface", "path": "src/cli.mjs", "surface_entry": "build", "summary": "build emits the new field." }
197
+ ]
198
+ }
199
+ ]
200
+ },
201
+ {
202
+ "id": "agent-relevant-path-with-no-ledger-item",
203
+ "description": "A named contract doc changed and nothing recorded it. This is the failure the whole gate exists for.",
204
+ "expect": "fail",
205
+ "expect_error_matching": "docs/build-packet\\.md is an agent-relevant change \\(class named_surface\\) with no release-ledger change item",
206
+ "changed_paths": ["schemas/fixture-example.v0.schema.json", "docs/build-packet.md"],
207
+ "head_entries": [
208
+ {
209
+ "id": "RL-0001",
210
+ "sequence": 1,
211
+ "date": "2026-02-02",
212
+ "kind": "release",
213
+ "surface_version": "1.1.0",
214
+ "changelog_section": "1.1.0",
215
+ "agent_impact": "Revalidate artifacts against the new schema revision.",
216
+ "compatibility": "additive",
217
+ "migration": "none",
218
+ "changes": [
219
+ { "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }
220
+ ]
221
+ }
222
+ ]
223
+ },
224
+ {
225
+ "id": "ledger-item-with-no-classified-path",
226
+ "description": "A ledger item claims a path that did not change in this range, and the entry is not an amendment.",
227
+ "expect": "fail",
228
+ "expect_error_matching": "change item names docs/build-packet\\.md, which did not change in this range",
229
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
230
+ "head_entries": [
231
+ {
232
+ "id": "RL-0001",
233
+ "sequence": 1,
234
+ "date": "2026-02-02",
235
+ "kind": "release",
236
+ "surface_version": "1.1.0",
237
+ "changelog_section": "1.1.0",
238
+ "agent_impact": "Revalidate artifacts against the new schema revision.",
239
+ "compatibility": "additive",
240
+ "migration": "none",
241
+ "changes": [
242
+ { "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." },
243
+ { "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "Claims a doc change that did not happen." }
244
+ ]
245
+ }
246
+ ]
247
+ },
248
+ {
249
+ "id": "reviewed-amendment-needs-no-classified-path",
250
+ "description": "An amendment corrects an earlier entry's meaning. It legitimately maps to no changed path, and rewriting the earlier entry is not an option.",
251
+ "expect": "pass",
252
+ "surface_version_unchanged": true,
253
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Fixed\n\n- The 1.0.0 entry understated its compatibility impact.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
254
+ "surface": { "surface_version": "1.0.0" },
255
+ "changed_paths": [],
256
+ "base_entries": [
257
+ {
258
+ "id": "RL-0001",
259
+ "sequence": 1,
260
+ "date": "2026-02-01",
261
+ "kind": "release",
262
+ "surface_version": "1.0.0",
263
+ "changelog_section": "1.0.0",
264
+ "agent_impact": "None.",
265
+ "compatibility": "compatible",
266
+ "migration": "none",
267
+ "changes": [
268
+ { "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The initial doc." }
269
+ ]
270
+ }
271
+ ],
272
+ "head_entries": [
273
+ "@base",
274
+ {
275
+ "id": "RL-0002",
276
+ "sequence": 2,
277
+ "date": "2026-02-03",
278
+ "kind": "amendment",
279
+ "amends": "RL-0001",
280
+ "amendment_reason": "RL-0001 was recorded as compatible; the doc change it described actually removed a documented guarantee.",
281
+ "surface_version": null,
282
+ "changelog_section": "1.0.0+agent.1",
283
+ "agent_impact": "Treat the 1.0.0 packet doc change as breaking, not compatible.",
284
+ "compatibility": "breaking",
285
+ "migration": "Re-read docs/build-packet.md and stop relying on the removed guarantee.",
286
+ "changes": [
287
+ { "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "Corrects the compatibility classification of RL-0001." }
288
+ ]
289
+ }
290
+ ]
291
+ },
292
+ {
293
+ "id": "duplicate-entry-id",
294
+ "expect": "fail",
295
+ "expect_error_matching": "duplicate entry id",
296
+ "changed_paths": ["schemas/fixture-example.v0.schema.json", "docs/build-packet.md"],
297
+ "head_entries": [
298
+ {
299
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
300
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
301
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
302
+ },
303
+ {
304
+ "id": "RL-0001", "sequence": 2, "date": "2026-02-02", "kind": "release", "surface_version": null,
305
+ "changelog_section": "1.0.0", "agent_impact": "Re-read the doc.", "compatibility": "compatible", "migration": "none",
306
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The doc changed." }]
307
+ }
308
+ ]
309
+ },
310
+ {
311
+ "id": "duplicate-change-identity",
312
+ "expect": "fail",
313
+ "expect_error_matching": "change identity schema\\|schemas/fixture-example\\.v0\\.schema\\.json\\|schemas/fixture-example\\.v0\\.schema\\.json is already recorded",
314
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
315
+ "head_entries": [
316
+ {
317
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
318
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
319
+ "changes": [
320
+ { "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." },
321
+ { "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "The same change, recorded twice." }
322
+ ]
323
+ }
324
+ ]
325
+ },
326
+ {
327
+ "id": "unordered-sequence",
328
+ "expect": "fail",
329
+ "expect_error_matching": "sequence must be 2 \\(append-only order\\), got 7",
330
+ "changed_paths": ["schemas/fixture-example.v0.schema.json", "docs/build-packet.md"],
331
+ "head_entries": [
332
+ {
333
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
334
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
335
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
336
+ },
337
+ {
338
+ "id": "RL-0002", "sequence": 7, "date": "2026-02-02", "kind": "release", "surface_version": null,
339
+ "changelog_section": "1.0.0", "agent_impact": "Re-read the doc.", "compatibility": "compatible", "migration": "none",
340
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The doc changed." }]
341
+ }
342
+ ]
343
+ },
344
+ {
345
+ "id": "unordered-date",
346
+ "expect": "fail",
347
+ "expect_error_matching": "date 2026-01-01 is earlier than the previous entry's 2026-02-02",
348
+ "changed_paths": ["schemas/fixture-example.v0.schema.json", "docs/build-packet.md"],
349
+ "head_entries": [
350
+ {
351
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
352
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
353
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
354
+ },
355
+ {
356
+ "id": "RL-0002", "sequence": 2, "date": "2026-01-01", "kind": "release", "surface_version": null,
357
+ "changelog_section": "1.0.0", "agent_impact": "Re-read the doc.", "compatibility": "compatible", "migration": "none",
358
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The doc changed." }]
359
+ }
360
+ ]
361
+ },
362
+ {
363
+ "id": "breaking-entry-with-no-migration",
364
+ "expect": "fail",
365
+ "expect_error_matching": "a breaking entry must state a migration action",
366
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
367
+ "head_entries": [
368
+ {
369
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
370
+ "changelog_section": "1.1.0", "agent_impact": "A required field was removed.", "compatibility": "breaking", "migration": "none",
371
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A required field was removed." }]
372
+ }
373
+ ]
374
+ },
375
+ {
376
+ "id": "missing-agent-impact",
377
+ "expect": "fail",
378
+ "expect_error_matching": "agent_impact is required",
379
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
380
+ "head_entries": [
381
+ {
382
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
383
+ "changelog_section": "1.1.0", "agent_impact": " ", "compatibility": "additive", "migration": "none",
384
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
385
+ }
386
+ ]
387
+ },
388
+ {
389
+ "id": "missing-changelog-section",
390
+ "expect": "fail",
391
+ "expect_error_matching": "changelog_section \"9\\.9\\.9\" has no matching section",
392
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
393
+ "head_entries": [
394
+ {
395
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
396
+ "changelog_section": "9.9.9", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
397
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
398
+ }
399
+ ]
400
+ },
401
+ {
402
+ "id": "changelog-body-hash-mismatch",
403
+ "description": "The changelog section was edited after the ledger recorded its hash.",
404
+ "expect": "fail",
405
+ "expect_error_matching": "changelog_sha256 does not match the body of CHANGELOG\\.md section \"1\\.1\\.0\"",
406
+ "hash_mode": "as_written",
407
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
408
+ "head_entries": [
409
+ {
410
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
411
+ "changelog_section": "1.1.0",
412
+ "changelog_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
413
+ "entry_sha256": "@computed",
414
+ "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
415
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
416
+ }
417
+ ]
418
+ },
419
+ {
420
+ "id": "entry-hash-mismatch",
421
+ "description": "A historical entry's body was edited in place without updating its content hash.",
422
+ "expect": "fail",
423
+ "expect_error_matching": "entry_sha256 does not match the entry contents",
424
+ "hash_mode": "as_written",
425
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
426
+ "head_entries": [
427
+ {
428
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
429
+ "changelog_section": "1.1.0",
430
+ "changelog_sha256": "@computed",
431
+ "entry_sha256": "2222222222222222222222222222222222222222222222222222222222222222",
432
+ "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
433
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
434
+ }
435
+ ]
436
+ },
437
+ {
438
+ "id": "duplicate-changelog-section",
439
+ "expect": "fail",
440
+ "expect_error_matching": "matches 2 sections in CHANGELOG\\.md",
441
+ "changelog": "# Changelog\n\n## [1.1.0] - 2026-02-02\n\n### Added\n\n- A fixture release section.\n\n## [1.1.0] - 2026-02-02\n\n### Added\n\n- The same identifier a second time.\n",
442
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
443
+ "head_entries": [
444
+ {
445
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
446
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
447
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
448
+ }
449
+ ]
450
+ },
451
+ {
452
+ "id": "entry-storing-a-containing-commit-oid",
453
+ "description": "An entry that names the commit containing it. The whole point of deriving the introducing commit from history is that this value cannot exist honestly.",
454
+ "expect": "fail",
455
+ "expect_error_matching": "looks like a commit identifier",
456
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
457
+ "head_entries": [
458
+ {
459
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
460
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
461
+ "introducing_commit": "abcdefabcdefabcdefabcdefabcdefabcdefabcd",
462
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
463
+ }
464
+ ]
465
+ },
466
+ {
467
+ "id": "ledger-item-claiming-an-excluded-path",
468
+ "description": "A change item names an implementation path the policy excludes. The ledger may not launder an unsupported path into the agent-relevant set.",
469
+ "expect": "fail",
470
+ "expect_error_matching": "classifies as not agent-relevant",
471
+ "changed_paths": ["schemas/fixture-example.v0.schema.json", "src/doctor-check-registry.mjs"],
472
+ "head_entries": [
473
+ {
474
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
475
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
476
+ "changes": [
477
+ { "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." },
478
+ { "class": "named_surface", "path": "src/doctor-check-registry.mjs", "surface_entry": null, "summary": "Claims an implementation file is supported surface." }
479
+ ]
480
+ }
481
+ ]
482
+ },
483
+ {
484
+ "id": "unclassified-changed-path",
485
+ "description": "A path the policy has never seen. The classifier fails closed rather than assuming no agent impact.",
486
+ "expect": "fail",
487
+ "expect_error_matching": "top-level-mystery\\.txt matches no rule, no supported-surface entry, and no ignore",
488
+ "changed_paths": ["schemas/fixture-example.v0.schema.json", "top-level-mystery.txt"],
489
+ "head_entries": [
490
+ {
491
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
492
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
493
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
494
+ }
495
+ ]
496
+ },
497
+ {
498
+ "id": "surface-bump-claimed-by-two-entries",
499
+ "expect": "fail",
500
+ "expect_error_matching": "surface_version 1\\.1\\.0 is already claimed by RL-0001",
501
+ "changed_paths": ["schemas/fixture-example.v0.schema.json", "docs/build-packet.md"],
502
+ "head_entries": [
503
+ {
504
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
505
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
506
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
507
+ },
508
+ {
509
+ "id": "RL-0002", "sequence": 2, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
510
+ "changelog_section": "1.0.0", "agent_impact": "Re-read the doc.", "compatibility": "compatible", "migration": "none",
511
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The doc changed." }]
512
+ }
513
+ ]
514
+ },
515
+ {
516
+ "id": "append-only-historical-entry-rewritten",
517
+ "expect": "fail",
518
+ "expect_error_matching": "historical entry was rewritten",
519
+ "changed_paths": [],
520
+ "surface_version_unchanged": true,
521
+ "surface": { "surface_version": "1.0.0" },
522
+ "base_entries": [
523
+ {
524
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-01", "kind": "release", "surface_version": "1.0.0",
525
+ "changelog_section": "1.0.0", "agent_impact": "None.", "compatibility": "compatible", "migration": "none",
526
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The initial doc." }]
527
+ }
528
+ ],
529
+ "head_entries": [
530
+ {
531
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-01", "kind": "release", "surface_version": "1.0.0",
532
+ "changelog_section": "1.0.0", "agent_impact": "None.", "compatibility": "breaking", "migration": "Rewritten after the fact.",
533
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The initial doc." }]
534
+ }
535
+ ]
536
+ },
537
+ {
538
+ "id": "surface-bump-with-no-claiming-entry",
539
+ "description": "The supported surface advanced and no new entry claims the new version. This is the headline case the two-way gate exists for and it had no fixture until now: the release happened, the ledger says nothing about it.",
540
+ "expect": "fail",
541
+ "expect_error_matching": "supported_surface moved to 1\\.1\\.0 but 0 new ledger entries claim it",
542
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
543
+ "head_entries": [
544
+ {
545
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": null,
546
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
547
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added, but nothing claims the surface bump that shipped with it." }]
548
+ }
549
+ ]
550
+ },
551
+ {
552
+ "id": "path-covered-by-two-change-items",
553
+ "description": "Two entries in the same range both record the same changed path. Coverage is exactly once, not at least once: two records of one change make the history ambiguous about what happened.",
554
+ "expect": "fail",
555
+ "expect_error_matching": "docs/build-packet\\.md is covered by 2 ledger change items",
556
+ "surface_version_unchanged": true,
557
+ "surface": { "surface_version": "1.0.0" },
558
+ "changelog": "# Changelog\n\n## [1.0.0+agent.2] - 2026-02-04\n\n### Changed\n\n- The same doc, recorded a second time in the same range.\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Changed\n\n- A contract doc changed.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
559
+ "changed_paths": ["docs/build-packet.md"],
560
+ "head_entries": [
561
+ {
562
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-03", "kind": "release", "surface_version": null,
563
+ "changelog_section": "1.0.0+agent.1", "agent_impact": "Re-read the doc.", "compatibility": "compatible", "migration": "none",
564
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The doc changed." }]
565
+ },
566
+ {
567
+ "id": "RL-0002", "sequence": 2, "date": "2026-02-04", "kind": "release", "surface_version": null,
568
+ "changelog_section": "1.0.0+agent.2", "agent_impact": "Re-read the doc.", "compatibility": "compatible", "migration": "none",
569
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The same doc change, recorded again." }]
570
+ }
571
+ ]
572
+ },
573
+ {
574
+ "id": "path-less-item-with-no-classified-change-of-its-class",
575
+ "description": "A path-less CLI item with nothing of that class in the range. A flag change still moves bytes somewhere; an item that maps to no moved byte is a claim about work that did not ship.",
576
+ "expect": "fail",
577
+ "expect_error_matching": "path-less change item of class cli_surface has no classified change of that class",
578
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
579
+ "head_entries": [
580
+ {
581
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
582
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
583
+ "changes": [
584
+ { "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." },
585
+ { "class": "cli_surface", "path": null, "surface_entry": "build", "summary": "Claims a flag change with no CLI bytes in the range." }
586
+ ]
587
+ }
588
+ ]
589
+ },
590
+ {
591
+ "id": "amendment-with-no-amends",
592
+ "expect": "fail",
593
+ "expect_error_matching": "an amendment must name the entry it amends",
594
+ "changed_paths": [],
595
+ "surface_version_unchanged": true,
596
+ "surface": { "surface_version": "1.0.0" },
597
+ "head_entries": [
598
+ {
599
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-03", "kind": "amendment",
600
+ "amendment_reason": "Corrects an earlier entry that this fixture never names.",
601
+ "surface_version": null, "changelog_section": "1.0.0", "agent_impact": "None.", "compatibility": "compatible", "migration": "none",
602
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "A correction with no subject." }]
603
+ }
604
+ ]
605
+ },
606
+ {
607
+ "id": "amendment-amending-an-unknown-entry",
608
+ "expect": "fail",
609
+ "expect_error_matching": "amends RL-9999, which is not an earlier entry",
610
+ "changed_paths": [],
611
+ "surface_version_unchanged": true,
612
+ "surface": { "surface_version": "1.0.0" },
613
+ "head_entries": [
614
+ {
615
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-03", "kind": "amendment",
616
+ "amends": "RL-9999",
617
+ "amendment_reason": "Corrects an entry that does not exist.",
618
+ "surface_version": null, "changelog_section": "1.0.0", "agent_impact": "None.", "compatibility": "compatible", "migration": "none",
619
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "A correction to nothing." }]
620
+ }
621
+ ]
622
+ },
623
+ {
624
+ "id": "amendment-with-no-amendment-reason",
625
+ "description": "An amendment is the only way to correct append-only history, so the reason it was needed is the whole record. Omitting it leaves a silent rewrite wearing an amendment's clothes.",
626
+ "expect": "fail",
627
+ "expect_error_matching": "an amendment must carry amendment_reason",
628
+ "changed_paths": [],
629
+ "surface_version_unchanged": true,
630
+ "surface": { "surface_version": "1.0.0" },
631
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Fixed\n\n- A correction.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
632
+ "base_entries": [
633
+ {
634
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-01", "kind": "release", "surface_version": "1.0.0",
635
+ "changelog_section": "1.0.0", "agent_impact": "None.", "compatibility": "compatible", "migration": "none",
636
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The initial doc." }]
637
+ }
638
+ ],
639
+ "head_entries": [
640
+ "@base",
641
+ {
642
+ "id": "RL-0002", "sequence": 2, "date": "2026-02-03", "kind": "amendment",
643
+ "amends": "RL-0001",
644
+ "surface_version": null, "changelog_section": "1.0.0+agent.1", "agent_impact": "Treat RL-0001 as breaking.", "compatibility": "breaking", "migration": "Stop relying on the removed guarantee.",
645
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "Corrects RL-0001." }]
646
+ }
647
+ ]
648
+ },
649
+ {
650
+ "id": "release-entry-carrying-amends",
651
+ "description": "A release that quietly wears amendment fields. The amendment exemption from the backward gate is a reviewed escape hatch; a release may not help itself to it.",
652
+ "expect": "fail",
653
+ "expect_error_matching": "amends/amendment_reason are only valid on kind \"amendment\"",
654
+ "changed_paths": ["schemas/fixture-example.v0.schema.json"],
655
+ "head_entries": [
656
+ {
657
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-02", "kind": "release", "surface_version": "1.1.0",
658
+ "amends": "RL-0000",
659
+ "amendment_reason": "A release pretending to be a correction.",
660
+ "changelog_section": "1.1.0", "agent_impact": "Revalidate.", "compatibility": "additive", "migration": "none",
661
+ "changes": [{ "class": "schema", "path": "schemas/fixture-example.v0.schema.json", "surface_entry": "schemas/fixture-example.v0.schema.json", "summary": "A field was added." }]
662
+ }
663
+ ]
664
+ },
665
+ {
666
+ "id": "same-path-recorded-again-by-a-later-release",
667
+ "description": "A path recorded by RL-0001 changes again and RL-0002 records it. Change identity is unique within an entry, not across the ledger — a repository touches the same path many times over its life, and the second time must be recordable.",
668
+ "expect": "pass",
669
+ "surface_version_unchanged": true,
670
+ "surface": { "surface_version": "1.0.0" },
671
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Changed\n\n- The same contract doc changed a second time.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
672
+ "changed_paths": ["docs/build-packet.md"],
673
+ "base_entries": [
674
+ {
675
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-01", "kind": "release", "surface_version": "1.0.0",
676
+ "changelog_section": "1.0.0", "agent_impact": "None.", "compatibility": "compatible", "migration": "none",
677
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The initial doc." }]
678
+ }
679
+ ],
680
+ "head_entries": [
681
+ "@base",
682
+ {
683
+ "id": "RL-0002", "sequence": 2, "date": "2026-02-03", "kind": "release", "surface_version": null,
684
+ "changelog_section": "1.0.0+agent.1", "agent_impact": "Re-read the packet doc; it changed again.", "compatibility": "compatible", "migration": "none",
685
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The same doc, changed again in a later release." }]
686
+ }
687
+ ]
688
+ },
689
+ {
690
+ "id": "policy-tightening-leaves-historical-entries-intact",
691
+ "description": "The supported surface no longer names docs/build-packet.md, so a path RL-0001 legitimately recorded would not classify today. RL-0001 is historical and append-only forbids editing it, so only entries new in the range are classified against the current policy.",
692
+ "expect": "pass",
693
+ "surface_version_unchanged": true,
694
+ "surface": { "surface_version": "1.0.0", "named": ["CHANGELOG.md", "contracts/release-ledger.json", "skills.json"] },
695
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Changed\n\n- A skill changed after the surface narrowed.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
696
+ "changed_paths": ["skills.json"],
697
+ "base_entries": [
698
+ {
699
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-01", "kind": "release", "surface_version": "1.0.0",
700
+ "changelog_section": "1.0.0", "agent_impact": "None.", "compatibility": "compatible", "migration": "none",
701
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "A doc that was named surface when this entry was written." }]
702
+ }
703
+ ],
704
+ "head_entries": [
705
+ "@base",
706
+ {
707
+ "id": "RL-0002", "sequence": 2, "date": "2026-02-03", "kind": "release", "surface_version": null,
708
+ "changelog_section": "1.0.0+agent.1", "agent_impact": "Reinstall skills.", "compatibility": "compatible", "migration": "Run the skills installer again.",
709
+ "changes": [{ "class": "skill", "path": "skills.json", "surface_entry": "skills.json", "summary": "The skill version advanced." }]
710
+ }
711
+ ]
712
+ },
713
+ {
714
+ "id": "append-only-historical-entry-deleted",
715
+ "expect": "fail",
716
+ "expect_error_matching": "historical entry was deleted",
717
+ "changed_paths": [],
718
+ "surface_version_unchanged": true,
719
+ "surface": { "surface_version": "1.0.0" },
720
+ "base_entries": [
721
+ {
722
+ "id": "RL-0001", "sequence": 1, "date": "2026-02-01", "kind": "release", "surface_version": "1.0.0",
723
+ "changelog_section": "1.0.0", "agent_impact": "None.", "compatibility": "compatible", "migration": "none",
724
+ "changes": [{ "class": "named_surface", "path": "docs/build-packet.md", "surface_entry": "docs/build-packet.md", "summary": "The initial doc." }]
725
+ }
726
+ ],
727
+ "head_entries": []
728
+ },
729
+ {
730
+ "id": "amendment-rehashes-the-section-it-corrects",
731
+ "description": "The 1.0.0 section's bytes changed after RL-0001 hashed them. RL-0001 cannot be edited; an amendment linking the same section with the current hash supersedes the stale one.",
732
+ "expect": "pass",
733
+ "hash_mode": "as_written",
734
+ "changed_paths": [],
735
+ "surface_version_unchanged": true,
736
+ "surface": {
737
+ "surface_version": "1.0.0"
738
+ },
739
+ "base_entries": [
740
+ {
741
+ "id": "RL-0001",
742
+ "sequence": 1,
743
+ "date": "2026-02-01",
744
+ "kind": "release",
745
+ "surface_version": "1.0.0",
746
+ "changelog_section": "1.0.0",
747
+ "changelog_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
748
+ "entry_sha256": "@computed",
749
+ "agent_impact": "None.",
750
+ "compatibility": "compatible",
751
+ "migration": "none",
752
+ "changes": [
753
+ {
754
+ "class": "named_surface",
755
+ "path": "docs/build-packet.md",
756
+ "surface_entry": "docs/build-packet.md",
757
+ "summary": "The initial doc."
758
+ }
759
+ ]
760
+ }
761
+ ],
762
+ "head_entries": [
763
+ "@base",
764
+ {
765
+ "id": "RL-0002",
766
+ "sequence": 2,
767
+ "date": "2026-02-03",
768
+ "kind": "amendment",
769
+ "amends": "RL-0001",
770
+ "amendment_reason": "A merge-conflict marker line committed at the tail of the 1.0.0 section was deleted; the section's bytes changed, so this entry carries its current hash.",
771
+ "surface_version": null,
772
+ "changelog_section": "1.0.0",
773
+ "changelog_sha256": "@computed",
774
+ "entry_sha256": "@computed",
775
+ "agent_impact": "None; the 1.0.0 section reads as it was meant to.",
776
+ "compatibility": "compatible",
777
+ "migration": "none",
778
+ "changes": [
779
+ {
780
+ "class": "named_surface",
781
+ "path": "docs/build-packet.md",
782
+ "surface_entry": "docs/build-packet.md",
783
+ "summary": "Re-hashes the 1.0.0 section after the marker line was removed."
784
+ }
785
+ ]
786
+ }
787
+ ]
788
+ },
789
+ {
790
+ "id": "amendment-elsewhere-leaves-the-stale-hash",
791
+ "description": "An amendment that links a different section corrects meaning only; it does not excuse the amended entry's stale changelog hash.",
792
+ "expect": "fail",
793
+ "expect_error_matching": "RL-0001: changelog_sha256 does not match the body of CHANGELOG\\.md section \"1\\.0\\.0\"",
794
+ "hash_mode": "as_written",
795
+ "changelog": "# Changelog\n\n## [1.0.0+agent.1] - 2026-02-03\n\n### Fixed\n\n- A stray marker line was removed from the 1.0.0 section.\n\n## [1.0.0] - 2026-02-01\n\n### Added\n\n- The initial fixture surface.\n",
796
+ "changed_paths": [],
797
+ "surface_version_unchanged": true,
798
+ "surface": {
799
+ "surface_version": "1.0.0"
800
+ },
801
+ "base_entries": [
802
+ {
803
+ "id": "RL-0001",
804
+ "sequence": 1,
805
+ "date": "2026-02-01",
806
+ "kind": "release",
807
+ "surface_version": "1.0.0",
808
+ "changelog_section": "1.0.0",
809
+ "changelog_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
810
+ "entry_sha256": "@computed",
811
+ "agent_impact": "None.",
812
+ "compatibility": "compatible",
813
+ "migration": "none",
814
+ "changes": [
815
+ {
816
+ "class": "named_surface",
817
+ "path": "docs/build-packet.md",
818
+ "surface_entry": "docs/build-packet.md",
819
+ "summary": "The initial doc."
820
+ }
821
+ ]
822
+ }
823
+ ],
824
+ "head_entries": [
825
+ "@base",
826
+ {
827
+ "id": "RL-0002",
828
+ "sequence": 2,
829
+ "date": "2026-02-03",
830
+ "kind": "amendment",
831
+ "amends": "RL-0001",
832
+ "amendment_reason": "A merge-conflict marker line committed at the tail of the 1.0.0 section was deleted; the section's bytes changed, so this entry carries its current hash.",
833
+ "surface_version": null,
834
+ "changelog_section": "1.0.0+agent.1",
835
+ "changelog_sha256": "@computed",
836
+ "entry_sha256": "@computed",
837
+ "agent_impact": "None; the 1.0.0 section reads as it was meant to.",
838
+ "compatibility": "compatible",
839
+ "migration": "none",
840
+ "changes": [
841
+ {
842
+ "class": "named_surface",
843
+ "path": "docs/build-packet.md",
844
+ "surface_entry": "docs/build-packet.md",
845
+ "summary": "Re-hashes the 1.0.0 section after the marker line was removed."
846
+ }
847
+ ]
848
+ }
849
+ ]
850
+ },
851
+ {
852
+ "id": "release-entry-relinking-a-section",
853
+ "description": "Only an amendment of the entry holding the link may link a section a second time; a release entry re-linking it fails the one-to-one rule.",
854
+ "expect": "fail",
855
+ "expect_error_matching": "RL-0002: changelog section \"1\\.0\\.0\" is already linked from RL-0001; the link is one-to-one",
856
+ "changed_paths": [],
857
+ "surface_version_unchanged": true,
858
+ "surface": {
859
+ "surface_version": "1.0.0"
860
+ },
861
+ "base_entries": [
862
+ {
863
+ "id": "RL-0001",
864
+ "sequence": 1,
865
+ "date": "2026-02-01",
866
+ "kind": "release",
867
+ "surface_version": "1.0.0",
868
+ "changelog_section": "1.0.0",
869
+ "agent_impact": "None.",
870
+ "compatibility": "compatible",
871
+ "migration": "none",
872
+ "changes": [
873
+ {
874
+ "class": "named_surface",
875
+ "path": "docs/build-packet.md",
876
+ "surface_entry": "docs/build-packet.md",
877
+ "summary": "The initial doc."
878
+ }
879
+ ]
880
+ }
881
+ ],
882
+ "head_entries": [
883
+ "@base",
884
+ {
885
+ "id": "RL-0002",
886
+ "sequence": 2,
887
+ "date": "2026-02-03",
888
+ "kind": "release",
889
+ "surface_version": null,
890
+ "changelog_section": "1.0.0",
891
+ "agent_impact": "None.",
892
+ "compatibility": "compatible",
893
+ "migration": "none",
894
+ "changes": [
895
+ {
896
+ "class": "named_surface",
897
+ "path": "docs/build-packet.md",
898
+ "surface_entry": "docs/build-packet.md",
899
+ "summary": "Claims the 1.0.0 section again."
900
+ }
901
+ ]
902
+ }
903
+ ]
904
+ },
905
+ {
906
+ "id": "amendment-chain-rehashes-the-section-twice",
907
+ "description": "The 1.0.0 section's bytes changed twice. RL-0002 amends RL-0001 with the section's then-current hash; RL-0003 amends RL-0002 with the newest one. Each amendment supersedes only the entry it amends, so both stale hashes are excused and the newest is the one checked.",
908
+ "expect": "pass",
909
+ "hash_mode": "as_written",
910
+ "changed_paths": [],
911
+ "surface_version_unchanged": true,
912
+ "surface": {
913
+ "surface_version": "1.0.0"
914
+ },
915
+ "base_entries": [
916
+ {
917
+ "id": "RL-0001",
918
+ "sequence": 1,
919
+ "date": "2026-02-01",
920
+ "kind": "release",
921
+ "surface_version": "1.0.0",
922
+ "changelog_section": "1.0.0",
923
+ "changelog_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
924
+ "entry_sha256": "@computed",
925
+ "agent_impact": "None.",
926
+ "compatibility": "compatible",
927
+ "migration": "none",
928
+ "changes": [
929
+ {
930
+ "class": "named_surface",
931
+ "path": "docs/build-packet.md",
932
+ "surface_entry": "docs/build-packet.md",
933
+ "summary": "The initial doc."
934
+ }
935
+ ]
936
+ }
937
+ ],
938
+ "head_entries": [
939
+ "@base",
940
+ {
941
+ "id": "RL-0002",
942
+ "sequence": 2,
943
+ "date": "2026-02-03",
944
+ "kind": "amendment",
945
+ "amends": "RL-0001",
946
+ "amendment_reason": "A merge-conflict marker line at the tail of the 1.0.0 section was deleted; this entry carried the section's hash at the time.",
947
+ "surface_version": null,
948
+ "changelog_section": "1.0.0",
949
+ "changelog_sha256": "2222222222222222222222222222222222222222222222222222222222222222",
950
+ "entry_sha256": "@computed",
951
+ "agent_impact": "None; the 1.0.0 section reads as it was meant to.",
952
+ "compatibility": "compatible",
953
+ "migration": "none",
954
+ "changes": [
955
+ {
956
+ "class": "named_surface",
957
+ "path": "docs/build-packet.md",
958
+ "surface_entry": "docs/build-packet.md",
959
+ "summary": "Re-hashes the 1.0.0 section after the first correction."
960
+ }
961
+ ]
962
+ },
963
+ {
964
+ "id": "RL-0003",
965
+ "sequence": 3,
966
+ "date": "2026-02-03",
967
+ "kind": "amendment",
968
+ "amends": "RL-0002",
969
+ "amendment_reason": "A second stray line in the 1.0.0 section was deleted; this entry carries the section's current hash.",
970
+ "surface_version": null,
971
+ "changelog_section": "1.0.0",
972
+ "changelog_sha256": "@computed",
973
+ "entry_sha256": "@computed",
974
+ "agent_impact": "None; the 1.0.0 section reads as it was meant to.",
975
+ "compatibility": "compatible",
976
+ "migration": "none",
977
+ "changes": [
978
+ {
979
+ "class": "named_surface",
980
+ "path": "docs/build-packet.md",
981
+ "surface_entry": "docs/build-packet.md",
982
+ "summary": "Re-hashes the 1.0.0 section after the second correction."
983
+ }
984
+ ]
985
+ }
986
+ ]
987
+ },
988
+ {
989
+ "id": "two-amendments-of-one-entry-relinking-its-section",
990
+ "description": "Two amendments of the same entry both linking its section are not a chain: the second link is held by RL-0002, not RL-0001, so RL-0003 fails the one-to-one rule. The amendment that re-hashes a section must name the entry currently holding the link.",
991
+ "expect": "fail",
992
+ "expect_error_matching": "RL-0003: changelog section \"1\\.0\\.0\" is already linked from RL-0002; the link is one-to-one",
993
+ "hash_mode": "as_written",
994
+ "changed_paths": [],
995
+ "surface_version_unchanged": true,
996
+ "surface": {
997
+ "surface_version": "1.0.0"
998
+ },
999
+ "base_entries": [
1000
+ {
1001
+ "id": "RL-0001",
1002
+ "sequence": 1,
1003
+ "date": "2026-02-01",
1004
+ "kind": "release",
1005
+ "surface_version": "1.0.0",
1006
+ "changelog_section": "1.0.0",
1007
+ "changelog_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
1008
+ "entry_sha256": "@computed",
1009
+ "agent_impact": "None.",
1010
+ "compatibility": "compatible",
1011
+ "migration": "none",
1012
+ "changes": [
1013
+ {
1014
+ "class": "named_surface",
1015
+ "path": "docs/build-packet.md",
1016
+ "surface_entry": "docs/build-packet.md",
1017
+ "summary": "The initial doc."
1018
+ }
1019
+ ]
1020
+ }
1021
+ ],
1022
+ "head_entries": [
1023
+ "@base",
1024
+ {
1025
+ "id": "RL-0002",
1026
+ "sequence": 2,
1027
+ "date": "2026-02-03",
1028
+ "kind": "amendment",
1029
+ "amends": "RL-0001",
1030
+ "amendment_reason": "A merge-conflict marker line at the tail of the 1.0.0 section was deleted; this entry carries the section's current hash.",
1031
+ "surface_version": null,
1032
+ "changelog_section": "1.0.0",
1033
+ "changelog_sha256": "@computed",
1034
+ "entry_sha256": "@computed",
1035
+ "agent_impact": "None; the 1.0.0 section reads as it was meant to.",
1036
+ "compatibility": "compatible",
1037
+ "migration": "none",
1038
+ "changes": [
1039
+ {
1040
+ "class": "named_surface",
1041
+ "path": "docs/build-packet.md",
1042
+ "surface_entry": "docs/build-packet.md",
1043
+ "summary": "Re-hashes the 1.0.0 section after the marker line was removed."
1044
+ }
1045
+ ]
1046
+ },
1047
+ {
1048
+ "id": "RL-0003",
1049
+ "sequence": 3,
1050
+ "date": "2026-02-03",
1051
+ "kind": "amendment",
1052
+ "amends": "RL-0001",
1053
+ "amendment_reason": "Claims to re-hash the 1.0.0 section again, but amends RL-0001 instead of RL-0002, which holds the link.",
1054
+ "surface_version": null,
1055
+ "changelog_section": "1.0.0",
1056
+ "changelog_sha256": "@computed",
1057
+ "entry_sha256": "@computed",
1058
+ "agent_impact": "None; the 1.0.0 section reads as it was meant to.",
1059
+ "compatibility": "compatible",
1060
+ "migration": "none",
1061
+ "changes": [
1062
+ {
1063
+ "class": "named_surface",
1064
+ "path": "docs/build-packet.md",
1065
+ "surface_entry": "docs/build-packet.md",
1066
+ "summary": "Re-hashes the 1.0.0 section a second time."
1067
+ }
1068
+ ]
1069
+ }
1070
+ ]
1071
+ }
1072
+ ]
1073
+ }