@jasonbelmonti/markdown-engine 1.0.0 → 2.0.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 (308) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +75 -26
  3. package/SECURITY.md +5 -3
  4. package/dist/api/annotation-source-range.d.ts +7 -0
  5. package/dist/api/annotation-source-range.d.ts.map +1 -0
  6. package/dist/api/annotation-source-range.js +103 -0
  7. package/dist/api/annotation-source-range.js.map +1 -0
  8. package/dist/api/annotation-target-candidate.d.ts +10 -0
  9. package/dist/api/annotation-target-candidate.d.ts.map +1 -0
  10. package/dist/api/annotation-target-candidate.js +14 -0
  11. package/dist/api/annotation-target-candidate.js.map +1 -0
  12. package/dist/api/annotation-target-cloning.d.ts +5 -0
  13. package/dist/api/annotation-target-cloning.d.ts.map +1 -0
  14. package/dist/api/annotation-target-cloning.js +122 -0
  15. package/dist/api/annotation-target-cloning.js.map +1 -0
  16. package/dist/api/annotation-target-diagnostics.d.ts +8 -0
  17. package/dist/api/annotation-target-diagnostics.d.ts.map +1 -0
  18. package/dist/api/annotation-target-diagnostics.js +57 -0
  19. package/dist/api/annotation-target-diagnostics.js.map +1 -0
  20. package/dist/api/annotation-target-validation.d.ts +2 -2
  21. package/dist/api/annotation-target-validation.d.ts.map +1 -1
  22. package/dist/api/annotation-target-validation.js +5 -272
  23. package/dist/api/annotation-target-validation.js.map +1 -1
  24. package/dist/api/contracts.d.ts +2 -1
  25. package/dist/api/contracts.d.ts.map +1 -1
  26. package/dist/api/contracts.js +1 -0
  27. package/dist/api/contracts.js.map +1 -1
  28. package/dist/api/declarative-validation.d.ts +13 -0
  29. package/dist/api/declarative-validation.d.ts.map +1 -0
  30. package/dist/api/declarative-validation.js +63 -0
  31. package/dist/api/declarative-validation.js.map +1 -0
  32. package/dist/api/document-queries.d.ts.map +1 -1
  33. package/dist/api/document-queries.js +74 -3
  34. package/dist/api/document-queries.js.map +1 -1
  35. package/dist/api/document.d.ts +44 -0
  36. package/dist/api/document.d.ts.map +1 -1
  37. package/dist/api/engine-node-attributes.d.ts +15 -0
  38. package/dist/api/engine-node-attributes.d.ts.map +1 -0
  39. package/dist/api/engine-node-attributes.js +99 -0
  40. package/dist/api/engine-node-attributes.js.map +1 -0
  41. package/dist/api/normalize.d.ts.map +1 -1
  42. package/dist/api/normalize.js +22 -1
  43. package/dist/api/normalize.js.map +1 -1
  44. package/dist/api/serialize.js +2 -14
  45. package/dist/api/serialize.js.map +1 -1
  46. package/dist/cli/args.d.ts +6 -2
  47. package/dist/cli/args.d.ts.map +1 -1
  48. package/dist/cli/args.js +33 -25
  49. package/dist/cli/args.js.map +1 -1
  50. package/dist/cli/declarative-validation.d.ts +18 -0
  51. package/dist/cli/declarative-validation.d.ts.map +1 -0
  52. package/dist/cli/declarative-validation.js +131 -0
  53. package/dist/cli/declarative-validation.js.map +1 -0
  54. package/dist/cli/files.d.ts +1 -0
  55. package/dist/cli/files.d.ts.map +1 -1
  56. package/dist/cli/files.js +3 -0
  57. package/dist/cli/files.js.map +1 -1
  58. package/dist/cli/run.d.ts.map +1 -1
  59. package/dist/cli/run.js +18 -3
  60. package/dist/cli/run.js.map +1 -1
  61. package/dist/cli/validate-args.d.ts +18 -0
  62. package/dist/cli/validate-args.d.ts.map +1 -0
  63. package/dist/cli/validate-args.js +131 -0
  64. package/dist/cli/validate-args.js.map +1 -0
  65. package/dist/declarative-validation/assertions/context.d.ts +8 -0
  66. package/dist/declarative-validation/assertions/context.d.ts.map +1 -0
  67. package/dist/declarative-validation/assertions/context.js +2 -0
  68. package/dist/declarative-validation/assertions/context.js.map +1 -0
  69. package/dist/declarative-validation/assertions/diagnostics.d.ts +22 -0
  70. package/dist/declarative-validation/assertions/diagnostics.d.ts.map +1 -0
  71. package/dist/declarative-validation/assertions/diagnostics.js +71 -0
  72. package/dist/declarative-validation/assertions/diagnostics.js.map +1 -0
  73. package/dist/declarative-validation/assertions/document-text-offsets.d.ts +3 -0
  74. package/dist/declarative-validation/assertions/document-text-offsets.d.ts.map +1 -0
  75. package/dist/declarative-validation/assertions/document-text-offsets.js +32 -0
  76. package/dist/declarative-validation/assertions/document-text-offsets.js.map +1 -0
  77. package/dist/declarative-validation/assertions/evaluator.d.ts +5 -0
  78. package/dist/declarative-validation/assertions/evaluator.d.ts.map +1 -0
  79. package/dist/declarative-validation/assertions/evaluator.js +46 -0
  80. package/dist/declarative-validation/assertions/evaluator.js.map +1 -0
  81. package/dist/declarative-validation/assertions/exists.d.ts +4 -0
  82. package/dist/declarative-validation/assertions/exists.d.ts.map +1 -0
  83. package/dist/declarative-validation/assertions/exists.js +8 -0
  84. package/dist/declarative-validation/assertions/exists.js.map +1 -0
  85. package/dist/declarative-validation/assertions/frontmatter-required.d.ts +9 -0
  86. package/dist/declarative-validation/assertions/frontmatter-required.d.ts.map +1 -0
  87. package/dist/declarative-validation/assertions/frontmatter-required.js +15 -0
  88. package/dist/declarative-validation/assertions/frontmatter-required.js.map +1 -0
  89. package/dist/declarative-validation/assertions/id-targets.d.ts +29 -0
  90. package/dist/declarative-validation/assertions/id-targets.d.ts.map +1 -0
  91. package/dist/declarative-validation/assertions/id-targets.js +428 -0
  92. package/dist/declarative-validation/assertions/id-targets.js.map +1 -0
  93. package/dist/declarative-validation/assertions/id-tokens.d.ts +15 -0
  94. package/dist/declarative-validation/assertions/id-tokens.d.ts.map +1 -0
  95. package/dist/declarative-validation/assertions/id-tokens.js +27 -0
  96. package/dist/declarative-validation/assertions/id-tokens.js.map +1 -0
  97. package/dist/declarative-validation/assertions/ids.d.ts +9 -0
  98. package/dist/declarative-validation/assertions/ids.d.ts.map +1 -0
  99. package/dist/declarative-validation/assertions/ids.js +42 -0
  100. package/dist/declarative-validation/assertions/ids.js.map +1 -0
  101. package/dist/declarative-validation/assertions/index.d.ts +3 -0
  102. package/dist/declarative-validation/assertions/index.d.ts.map +1 -0
  103. package/dist/declarative-validation/assertions/index.js +3 -0
  104. package/dist/declarative-validation/assertions/index.js.map +1 -0
  105. package/dist/declarative-validation/assertions/literal-text.d.ts +2 -0
  106. package/dist/declarative-validation/assertions/literal-text.d.ts.map +1 -0
  107. package/dist/declarative-validation/assertions/literal-text.js +14 -0
  108. package/dist/declarative-validation/assertions/literal-text.js.map +1 -0
  109. package/dist/declarative-validation/assertions/normalized-source-ranges.d.ts +3 -0
  110. package/dist/declarative-validation/assertions/normalized-source-ranges.d.ts.map +1 -0
  111. package/dist/declarative-validation/assertions/normalized-source-ranges.js +105 -0
  112. package/dist/declarative-validation/assertions/normalized-source-ranges.js.map +1 -0
  113. package/dist/declarative-validation/assertions/ordering.d.ts +6 -0
  114. package/dist/declarative-validation/assertions/ordering.d.ts.map +1 -0
  115. package/dist/declarative-validation/assertions/ordering.js +71 -0
  116. package/dist/declarative-validation/assertions/ordering.js.map +1 -0
  117. package/dist/declarative-validation/assertions/references.d.ts +9 -0
  118. package/dist/declarative-validation/assertions/references.d.ts.map +1 -0
  119. package/dist/declarative-validation/assertions/references.js +354 -0
  120. package/dist/declarative-validation/assertions/references.js.map +1 -0
  121. package/dist/declarative-validation/assertions/sections-required.d.ts +9 -0
  122. package/dist/declarative-validation/assertions/sections-required.d.ts.map +1 -0
  123. package/dist/declarative-validation/assertions/sections-required.js +49 -0
  124. package/dist/declarative-validation/assertions/sections-required.js.map +1 -0
  125. package/dist/declarative-validation/assertions/table-columns-required.d.ts +9 -0
  126. package/dist/declarative-validation/assertions/table-columns-required.d.ts.map +1 -0
  127. package/dist/declarative-validation/assertions/table-columns-required.js +31 -0
  128. package/dist/declarative-validation/assertions/table-columns-required.js.map +1 -0
  129. package/dist/declarative-validation/assertions/text-length.d.ts +9 -0
  130. package/dist/declarative-validation/assertions/text-length.d.ts.map +1 -0
  131. package/dist/declarative-validation/assertions/text-length.js +40 -0
  132. package/dist/declarative-validation/assertions/text-length.js.map +1 -0
  133. package/dist/declarative-validation/assertions/text-occurrence-count.d.ts +9 -0
  134. package/dist/declarative-validation/assertions/text-occurrence-count.d.ts.map +1 -0
  135. package/dist/declarative-validation/assertions/text-occurrence-count.js +22 -0
  136. package/dist/declarative-validation/assertions/text-occurrence-count.js.map +1 -0
  137. package/dist/declarative-validation/assertions/text.d.ts +9 -0
  138. package/dist/declarative-validation/assertions/text.d.ts.map +1 -0
  139. package/dist/declarative-validation/assertions/text.js +32 -0
  140. package/dist/declarative-validation/assertions/text.js.map +1 -0
  141. package/dist/declarative-validation/compiler/assertion-builders.d.ts +6 -0
  142. package/dist/declarative-validation/compiler/assertion-builders.d.ts.map +1 -0
  143. package/dist/declarative-validation/compiler/assertion-builders.js +199 -0
  144. package/dist/declarative-validation/compiler/assertion-builders.js.map +1 -0
  145. package/dist/declarative-validation/compiler/assertion-shapes.d.ts +18 -0
  146. package/dist/declarative-validation/compiler/assertion-shapes.d.ts.map +1 -0
  147. package/dist/declarative-validation/compiler/assertion-shapes.js +129 -0
  148. package/dist/declarative-validation/compiler/assertion-shapes.js.map +1 -0
  149. package/dist/declarative-validation/compiler/assertions.d.ts +5 -0
  150. package/dist/declarative-validation/compiler/assertions.d.ts.map +1 -0
  151. package/dist/declarative-validation/compiler/assertions.js +25 -0
  152. package/dist/declarative-validation/compiler/assertions.js.map +1 -0
  153. package/dist/declarative-validation/compiler/compatibility.d.ts +4 -0
  154. package/dist/declarative-validation/compiler/compatibility.d.ts.map +1 -0
  155. package/dist/declarative-validation/compiler/compatibility.js +28 -0
  156. package/dist/declarative-validation/compiler/compatibility.js.map +1 -0
  157. package/dist/declarative-validation/compiler/diagnostics.d.ts +3 -0
  158. package/dist/declarative-validation/compiler/diagnostics.d.ts.map +1 -0
  159. package/dist/declarative-validation/compiler/diagnostics.js +9 -0
  160. package/dist/declarative-validation/compiler/diagnostics.js.map +1 -0
  161. package/dist/declarative-validation/compiler/index.d.ts +5 -0
  162. package/dist/declarative-validation/compiler/index.d.ts.map +1 -0
  163. package/dist/declarative-validation/compiler/index.js +164 -0
  164. package/dist/declarative-validation/compiler/index.js.map +1 -0
  165. package/dist/declarative-validation/compiler/plan.d.ts +58 -0
  166. package/dist/declarative-validation/compiler/plan.d.ts.map +1 -0
  167. package/dist/declarative-validation/compiler/plan.js +2 -0
  168. package/dist/declarative-validation/compiler/plan.js.map +1 -0
  169. package/dist/declarative-validation/diagnostics/index.d.ts +7 -0
  170. package/dist/declarative-validation/diagnostics/index.d.ts.map +1 -0
  171. package/dist/declarative-validation/diagnostics/index.js +2 -0
  172. package/dist/declarative-validation/diagnostics/index.js.map +1 -0
  173. package/dist/declarative-validation/diagnostics/profile-config-diagnostics.d.ts +11 -0
  174. package/dist/declarative-validation/diagnostics/profile-config-diagnostics.d.ts.map +1 -0
  175. package/dist/declarative-validation/diagnostics/profile-config-diagnostics.js +45 -0
  176. package/dist/declarative-validation/diagnostics/profile-config-diagnostics.js.map +1 -0
  177. package/dist/declarative-validation/evidence/index.d.ts +14 -0
  178. package/dist/declarative-validation/evidence/index.d.ts.map +1 -0
  179. package/dist/declarative-validation/evidence/index.js +38 -0
  180. package/dist/declarative-validation/evidence/index.js.map +1 -0
  181. package/dist/declarative-validation/profile/assertion-schema.d.ts +4 -0
  182. package/dist/declarative-validation/profile/assertion-schema.d.ts.map +1 -0
  183. package/dist/declarative-validation/profile/assertion-schema.js +285 -0
  184. package/dist/declarative-validation/profile/assertion-schema.js.map +1 -0
  185. package/dist/declarative-validation/profile/data-closure.d.ts +6 -0
  186. package/dist/declarative-validation/profile/data-closure.d.ts.map +1 -0
  187. package/dist/declarative-validation/profile/data-closure.js +167 -0
  188. package/dist/declarative-validation/profile/data-closure.js.map +1 -0
  189. package/dist/declarative-validation/profile/direct-profile-diagnostics.d.ts +5 -0
  190. package/dist/declarative-validation/profile/direct-profile-diagnostics.d.ts.map +1 -0
  191. package/dist/declarative-validation/profile/direct-profile-diagnostics.js +73 -0
  192. package/dist/declarative-validation/profile/direct-profile-diagnostics.js.map +1 -0
  193. package/dist/declarative-validation/profile/index.d.ts +113 -0
  194. package/dist/declarative-validation/profile/index.d.ts.map +1 -0
  195. package/dist/declarative-validation/profile/index.js +2 -0
  196. package/dist/declarative-validation/profile/index.js.map +1 -0
  197. package/dist/declarative-validation/profile/materialization.d.ts +9 -0
  198. package/dist/declarative-validation/profile/materialization.d.ts.map +1 -0
  199. package/dist/declarative-validation/profile/materialization.js +109 -0
  200. package/dist/declarative-validation/profile/materialization.js.map +1 -0
  201. package/dist/declarative-validation/profile/parse.d.ts +3 -0
  202. package/dist/declarative-validation/profile/parse.d.ts.map +1 -0
  203. package/dist/declarative-validation/profile/parse.js +89 -0
  204. package/dist/declarative-validation/profile/parse.js.map +1 -0
  205. package/dist/declarative-validation/profile/schema-values.d.ts +14 -0
  206. package/dist/declarative-validation/profile/schema-values.d.ts.map +1 -0
  207. package/dist/declarative-validation/profile/schema-values.js +81 -0
  208. package/dist/declarative-validation/profile/schema-values.js.map +1 -0
  209. package/dist/declarative-validation/profile/schema.d.ts +9 -0
  210. package/dist/declarative-validation/profile/schema.d.ts.map +1 -0
  211. package/dist/declarative-validation/profile/schema.js +98 -0
  212. package/dist/declarative-validation/profile/schema.js.map +1 -0
  213. package/dist/declarative-validation/profile/selector-schema.d.ts +4 -0
  214. package/dist/declarative-validation/profile/selector-schema.d.ts.map +1 -0
  215. package/dist/declarative-validation/profile/selector-schema.js +145 -0
  216. package/dist/declarative-validation/profile/selector-schema.js.map +1 -0
  217. package/dist/declarative-validation/results/index.d.ts +26 -0
  218. package/dist/declarative-validation/results/index.d.ts.map +1 -0
  219. package/dist/declarative-validation/results/index.js +2 -0
  220. package/dist/declarative-validation/results/index.js.map +1 -0
  221. package/dist/declarative-validation/selectors/index.d.ts +57 -0
  222. package/dist/declarative-validation/selectors/index.d.ts.map +1 -0
  223. package/dist/declarative-validation/selectors/index.js +149 -0
  224. package/dist/declarative-validation/selectors/index.js.map +1 -0
  225. package/dist/declarative-validation/selectors/source.d.ts +8 -0
  226. package/dist/declarative-validation/selectors/source.d.ts.map +1 -0
  227. package/dist/declarative-validation/selectors/source.js +57 -0
  228. package/dist/declarative-validation/selectors/source.js.map +1 -0
  229. package/dist/declarative-validation/selectors/table-targets.d.ts +13 -0
  230. package/dist/declarative-validation/selectors/table-targets.d.ts.map +1 -0
  231. package/dist/declarative-validation/selectors/table-targets.js +106 -0
  232. package/dist/declarative-validation/selectors/table-targets.js.map +1 -0
  233. package/dist/declarative-validation/selectors/table-text.d.ts +4 -0
  234. package/dist/declarative-validation/selectors/table-text.d.ts.map +1 -0
  235. package/dist/declarative-validation/selectors/table-text.js +16 -0
  236. package/dist/declarative-validation/selectors/table-text.js.map +1 -0
  237. package/dist/{ir → internal}/document-node-walk.d.ts +5 -0
  238. package/dist/internal/document-node-walk.d.ts.map +1 -0
  239. package/dist/internal/document-node-walk.js +36 -0
  240. package/dist/internal/document-node-walk.js.map +1 -0
  241. package/dist/internal/stable-json.d.ts +3 -0
  242. package/dist/internal/stable-json.d.ts.map +1 -0
  243. package/dist/internal/stable-json.js +17 -0
  244. package/dist/internal/stable-json.js.map +1 -0
  245. package/dist/ir/document-derived-views.d.ts.map +1 -1
  246. package/dist/ir/document-derived-views.js +2 -0
  247. package/dist/ir/document-derived-views.js.map +1 -1
  248. package/dist/ir/document-link-reference-views.d.ts +3 -0
  249. package/dist/ir/document-link-reference-views.d.ts.map +1 -0
  250. package/dist/ir/document-link-reference-views.js +150 -0
  251. package/dist/ir/document-link-reference-views.js.map +1 -0
  252. package/dist/ir/document-link-views.d.ts.map +1 -1
  253. package/dist/ir/document-link-views.js +7 -8
  254. package/dist/ir/document-link-views.js.map +1 -1
  255. package/dist/ir/document-list-views.d.ts.map +1 -1
  256. package/dist/ir/document-list-views.js +14 -13
  257. package/dist/ir/document-list-views.js.map +1 -1
  258. package/dist/ir/document-sections.d.ts.map +1 -1
  259. package/dist/ir/document-sections.js +2 -4
  260. package/dist/ir/document-sections.js.map +1 -1
  261. package/dist/ir/document-table-views.js +1 -1
  262. package/dist/ir/document-table-views.js.map +1 -1
  263. package/dist/ir/document-text-spans.js +1 -1
  264. package/dist/ir/document-text-spans.js.map +1 -1
  265. package/dist/ir/document.d.ts +2 -3
  266. package/dist/ir/document.d.ts.map +1 -1
  267. package/dist/ir/document.js +1 -1
  268. package/dist/ir/document.js.map +1 -1
  269. package/dist/ir/index.d.ts +1 -0
  270. package/dist/ir/index.d.ts.map +1 -1
  271. package/dist/ir/normalization-input.d.ts +12 -0
  272. package/dist/ir/normalization-input.d.ts.map +1 -0
  273. package/dist/ir/normalization-input.js +2 -0
  274. package/dist/ir/normalization-input.js.map +1 -0
  275. package/dist/rules/code-fence-languages.d.ts.map +1 -1
  276. package/dist/rules/code-fence-languages.js +4 -6
  277. package/dist/rules/code-fence-languages.js.map +1 -1
  278. package/dist/rules/document-query.d.ts +0 -2
  279. package/dist/rules/document-query.d.ts.map +1 -1
  280. package/dist/rules/document-query.js +1 -17
  281. package/dist/rules/document-query.js.map +1 -1
  282. package/dist/rules/index.d.ts +2 -6
  283. package/dist/rules/index.d.ts.map +1 -1
  284. package/dist/rules/index.js +3 -37
  285. package/dist/rules/index.js.map +1 -1
  286. package/dist/rules/links-allowed-schemes.d.ts.map +1 -1
  287. package/dist/rules/links-allowed-schemes.js +3 -2
  288. package/dist/rules/links-allowed-schemes.js.map +1 -1
  289. package/dist/rules/registry.d.ts +12 -0
  290. package/dist/rules/registry.d.ts.map +1 -0
  291. package/dist/rules/registry.js +61 -0
  292. package/dist/rules/registry.js.map +1 -0
  293. package/docs/contracts/api.md +661 -0
  294. package/docs/contracts/declarative-validation.md +559 -0
  295. package/docs/contracts/frontmatter.md +122 -0
  296. package/fixtures/declarative-validation/examples/operational-spec/fail.md +32 -0
  297. package/fixtures/declarative-validation/examples/operational-spec/pass.md +34 -0
  298. package/fixtures/declarative-validation/examples/operational-spec/profile.yaml +131 -0
  299. package/fixtures/declarative-validation/examples/release-checklist/fail.md +22 -0
  300. package/fixtures/declarative-validation/examples/release-checklist/pass.md +22 -0
  301. package/fixtures/declarative-validation/examples/release-checklist/profile.yaml +96 -0
  302. package/fixtures/declarative-validation/examples/requirements-traceability/fail.md +27 -0
  303. package/fixtures/declarative-validation/examples/requirements-traceability/pass.md +27 -0
  304. package/fixtures/declarative-validation/examples/requirements-traceability/profile.yaml +119 -0
  305. package/package.json +21 -6
  306. package/dist/ir/document-node-walk.d.ts.map +0 -1
  307. package/dist/ir/document-node-walk.js +0 -16
  308. package/dist/ir/document-node-walk.js.map +0 -1
@@ -0,0 +1,559 @@
1
+ # Declarative Validation Contract
2
+
3
+ Status: package 2.0.0, v1 profile syntax, document contract 1.0.0
4
+ Last updated: 2026-05-14
5
+
6
+ This document defines the public declarative validation contract for
7
+ `@jasonbelmonti/markdown-engine`. The stable surface is the package-root API,
8
+ the v1 profile syntax, the CLI validation command, diagnostic codes, serialized
9
+ result shapes, and evidence fields. Internal parser output, compiled rule-plan
10
+ records, selector target records, and evaluator implementation modules are not
11
+ public contracts.
12
+
13
+ Package 2.0 does not introduce `documentVersion: "2.0.0"` or
14
+ `markdown-engine.validation@v2`. Declarative validation continues to use the v1
15
+ profile syntax against the existing `documentVersion: "1.0.0"` rich IR
16
+ document contract.
17
+
18
+ ## 1.0 Contract
19
+
20
+ Declarative validation is a local, deterministic validation layer over a
21
+ normalized `EngineDocument`. It accepts inert YAML-compatible profile data,
22
+ compiles supported selectors and assertions into engine-owned rule plans, and
23
+ returns stable diagnostics, rule results, profile metadata, and optional
24
+ evidence.
25
+
26
+ The public API functions are:
27
+
28
+ ```ts
29
+ parseValidationProfile(
30
+ input: string | JsonSafeValue,
31
+ options?: DeclarativeProfileParseOptions,
32
+ ): DeclarativeProfileParseResult
33
+
34
+ validateWithProfile(
35
+ document: EngineDocument,
36
+ profile: ValidationProfile,
37
+ options?: DeclarativeValidationOptions,
38
+ ): DeclarativeValidationResult
39
+ ```
40
+
41
+ Compiled declarative validation plans are internal. They are not exported from
42
+ the package root, are not serialized in API or CLI results, and carry no semver
43
+ stability guarantee.
44
+
45
+ ## Syntax Versioning
46
+
47
+ The v1 syntax is selected with:
48
+
49
+ ```yaml
50
+ syntaxVersion: markdown-engine.validation@v1
51
+ ```
52
+
53
+ `syntaxVersion` is required. Missing or unsupported values emit
54
+ `profile.config.unsupportedSyntaxVersion`.
55
+
56
+ The v1 vocabulary is closed. Unknown profile keys, rule keys, selector keys,
57
+ known assertion keys, and nested assertion keys emit
58
+ `profile.config.unsupportedKey` unless the contract assigns a more specific
59
+ compile diagnostic for an unsupported selector target or unsupported assertion
60
+ member.
61
+
62
+ Regex-like keys are explicitly unsupported in v1:
63
+
64
+ - `matches`
65
+ - `pattern`
66
+ - `regex`
67
+ - `regexp`
68
+
69
+ Executable-like keys are also unsupported:
70
+
71
+ - `callback`
72
+ - `eval`
73
+ - `execute`
74
+ - `expression`
75
+ - `function`
76
+ - `import`
77
+ - `imports`
78
+ - `plugin`
79
+ - `script`
80
+
81
+ These keys are treated as data-only unsupported config. They are not executed,
82
+ imported, evaluated, or compiled.
83
+
84
+ ## Document-Version Behavior
85
+
86
+ Profiles may include:
87
+
88
+ ```yaml
89
+ documentVersion: 1.0.0
90
+ ```
91
+
92
+ Supported profile `documentVersion` values are `"0.0.0"` and `"1.0.0"`.
93
+ Omission is allowed. `parseValidationProfile` preserves omission and does not
94
+ inject a default into the parsed profile.
95
+
96
+ Direct object inputs to `parseValidationProfile` are closed as JSON-safe data
97
+ before schema traversal. Accessors, proxies, cyclic values, sparse arrays,
98
+ functions, non-finite numbers, explicit `undefined`, and `__proto__` data
99
+ properties are rejected with inert diagnostics rather than being executed or
100
+ compiled.
101
+
102
+ `validateWithProfile` resolves an omitted profile `documentVersion` to the
103
+ supplied `EngineDocument.version`. The returned
104
+ `DeclarativeValidationResult.profile.documentVersion` records that resolved
105
+ version.
106
+
107
+ If the resolved profile `documentVersion` differs from `document.version`,
108
+ validation emits `profile.config.documentVersionMismatch`, returns no rule
109
+ results, and does not evaluate rules.
110
+
111
+ ## Profile Shape
112
+
113
+ The top-level profile shape is:
114
+
115
+ ```ts
116
+ interface ValidationProfile {
117
+ syntaxVersion: "markdown-engine.validation@v1";
118
+ documentVersion?: EngineDocumentVersion;
119
+ rules: readonly DeclarativeValidationRule[];
120
+ }
121
+
122
+ interface DeclarativeValidationRule {
123
+ id: string;
124
+ severity?: "error" | "warning" | "info";
125
+ select: DeclarativeSelector;
126
+ assert: DeclarativeAssertion;
127
+ }
128
+ ```
129
+
130
+ Rule IDs must be non-empty strings and unique within one profile. Duplicate rule
131
+ IDs emit `profile.config.invalidShape` because diagnostics, rule results, and
132
+ evidence identify output by `ruleId`.
133
+
134
+ Rule `severity` defaults to `error` when omitted. Unsupported severity values
135
+ emit `profile.config.invalidShape`.
136
+
137
+ Profile values must be JSON-safe data properties after YAML materialization.
138
+ Functions, accessors, proxies, cyclic structures, sparse arrays, `undefined`
139
+ payloads in required positions, non-finite numbers, and `__proto__` properties
140
+ are rejected as invalid shape.
141
+
142
+ ## Selector Contract
143
+
144
+ Selectors resolve against public `EngineDocument` structure and query helper
145
+ semantics. Supported selector targets are:
146
+
147
+ ```ts
148
+ type DeclarativeSelector =
149
+ | { target: "document" }
150
+ | { target: "section"; title?: string; depth?: number }
151
+ | { target: "heading"; text?: string; depth?: number }
152
+ | { target: "table"; section?: string; header?: readonly string[] }
153
+ | {
154
+ target: "tableRow";
155
+ section?: string;
156
+ tableHeader?: readonly string[];
157
+ where?: { column: string; equals?: string; includes?: string };
158
+ }
159
+ | {
160
+ target: "tableCell";
161
+ section?: string;
162
+ tableHeader?: readonly string[];
163
+ column: string;
164
+ rowWhere?: { column: string; equals?: string; includes?: string };
165
+ }
166
+ | { target: "textSpan"; section?: string; nodeType?: string; textIncludes?: string }
167
+ | { target: "link"; section?: string; text?: string; url?: string }
168
+ | { target: "list"; section?: string; ordered?: boolean; depth?: number };
169
+ ```
170
+
171
+ Unsupported selector targets emit `profile.compile.unsupportedSelector`.
172
+
173
+ String matching is deterministic literal matching. Heading, section, header,
174
+ column, `equals`, and frontmatter field names use exact string equality.
175
+ `includes`, `contains`, `excludes`, and `textOccurrenceCount.text` use literal
176
+ substring matching. No profile-supplied regular expression is compiled.
177
+
178
+ Table `header` and `tableHeader` arrays match normalized table header cells as
179
+ an exact-title ordered subsequence. Unrelated columns may appear before, between,
180
+ or after listed values. Duplicate supplied values require separate matching
181
+ header cells.
182
+
183
+ `tableRow.where` and `tableCell.rowWhere` require a non-empty `column` and at
184
+ least one of `equals` or `includes`. When both are present, both tests must
185
+ pass. A missing predicate column makes the row fail the predicate; it does not
186
+ emit a diagnostic by itself.
187
+
188
+ ## Assertion Contract
189
+
190
+ Supported assertion members are:
191
+
192
+ ```ts
193
+ interface DeclarativeAssertion {
194
+ exists?: true;
195
+ sectionsRequired?: {
196
+ headings: readonly string[];
197
+ order?: "none" | "strict";
198
+ };
199
+ tableColumnsRequired?: {
200
+ columns: readonly string[];
201
+ };
202
+ ids?: {
203
+ prefix?: string;
204
+ unique?: boolean;
205
+ caseSensitive?: boolean;
206
+ };
207
+ references?: {
208
+ idsFrom: { section?: string; column?: string; prefix?: string };
209
+ mustAppearIn: readonly string[];
210
+ };
211
+ text?: {
212
+ contains?: string;
213
+ excludes?: readonly string[];
214
+ };
215
+ textOccurrenceCount?: {
216
+ text: string;
217
+ count: number;
218
+ };
219
+ textLength?: {
220
+ min?: number;
221
+ max?: number;
222
+ };
223
+ frontmatterRequired?: {
224
+ fields: readonly string[];
225
+ };
226
+ }
227
+ ```
228
+
229
+ Unsupported first-level assertion members parsed from YAML or JSON-safe profile
230
+ input emit `profile.compile.unsupportedAssertion`, except regex-like and
231
+ executable-like keys, which retain `profile.config.unsupportedKey` precedence.
232
+ Direct typed profile objects passed to validation are hardened as closed
233
+ JSON-safe data before execution; unsupported assertion properties on that path
234
+ emit `profile.config.unsupportedKey`.
235
+
236
+ Selector/assertion compatibility is part of the public contract:
237
+
238
+ | Assertion | Compatible selector targets |
239
+ | --- | --- |
240
+ | `exists` | all supported selector targets |
241
+ | `sectionsRequired` | `document` |
242
+ | `tableColumnsRequired` | `table` |
243
+ | `ids` | all supported selector targets |
244
+ | `references` | `document` |
245
+ | `text` | all supported selector targets |
246
+ | `textOccurrenceCount` | all supported selector targets |
247
+ | `textLength` | all supported selector targets |
248
+ | `frontmatterRequired` | `document` |
249
+
250
+ Incompatible supported selector/assertion pairs emit
251
+ `profile.compile.incompatibleSelectorAssertion`.
252
+
253
+ `exists` must be `true`. It passes when the selector resolves at least one
254
+ target and fails with `profile.validation.emptySelection` when the selector
255
+ resolves zero targets.
256
+
257
+ `sectionsRequired.order` defaults to `none`. `strict` checks that configured
258
+ headings appear as an ordered subsequence in the normalized section tree
259
+ flattened in source order.
260
+
261
+ `ids.unique` must be `true`; `prefix` and `caseSensitive` are modifiers, not
262
+ standalone predicates. `caseSensitive` defaults to `true`. ID tokens use the
263
+ documented token grammar `[A-Za-z][A-Za-z0-9]*-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*`.
264
+
265
+ `text` must include `contains` or a non-empty `excludes` array.
266
+ `textOccurrenceCount.count` is a finite number and counts non-overlapping
267
+ literal occurrences per selected target.
268
+ `textLength` must include `min`, `max`, or both. Bounds are non-negative
269
+ integers, `min` must be less than or equal to `max` when both are present, and
270
+ evaluation uses JavaScript string `.length` for each selected target's
271
+ normalized text.
272
+
273
+ Empty selector results produce `profile.validation.emptySelection` for exists,
274
+ table, ID, reference, text, occurrence, and text-length assertions.
275
+ Document-scoped required-section and required-frontmatter assertions evaluate
276
+ against the document.
277
+
278
+ ## Diagnostics
279
+
280
+ All declarative validation diagnostics use the public `MarkdownDiagnostic`
281
+ shape. Config diagnostics are error severity except
282
+ `profile.config.yamlWarning`, which is warning severity. Compile diagnostics are
283
+ error severity. Validation diagnostics use the rule severity. Source ranges are
284
+ included when a selected target has source evidence; locations are omitted
285
+ rather than fabricated when unavailable.
286
+
287
+ | Code | Severity source | Emitted when |
288
+ | --- | --- | --- |
289
+ | `profile.config.invalidYaml` | `error` | YAML text cannot be parsed or materialized as JSON-safe profile data. |
290
+ | `profile.config.yamlWarning` | `warning` | YAML materialization produces a non-fatal parser warning. |
291
+ | `profile.config.unsupportedSyntaxVersion` | `error` | `syntaxVersion` is missing or is not `markdown-engine.validation@v1`. |
292
+ | `profile.config.invalidShape` | `error` | Required fields are missing, fields have wrong types, arrays or strings are empty, rule IDs duplicate, scalar values are invalid, table predicates are ineffective, or assertion payloads contain no effective predicate. |
293
+ | `profile.config.documentVersionMismatch` | `error` | Resolved profile `documentVersion` differs from the supplied `EngineDocument.version`. |
294
+ | `profile.config.unsupportedKey` | `error` | A closed profile, rule, selector, known assertion object, nested object, regex-like key, executable-like key, or direct typed profile object contains unsupported syntax. |
295
+ | `profile.compile.unsupportedSelector` | `error` | `select.target` is not a supported v1 target. |
296
+ | `profile.compile.unsupportedAssertion` | `error` | Parsed YAML or JSON-safe `assert` input contains an unsupported first-level assertion member that does not have unsupported-key precedence. |
297
+ | `profile.compile.incompatibleSelectorAssertion` | `error` | A supported selector target is paired with an incompatible supported assertion. |
298
+ | `profile.validation.emptySelection` | Rule severity | A rule cannot evaluate because its selector matches no applicable target. |
299
+ | `profile.validation.assertionFailed` | Rule severity | A supported assertion evaluates and fails without a more specific diagnostic code, including missing table columns, exact occurrence-count mismatches, and text-length bound failures. |
300
+ | `profile.validation.duplicateId` | Rule severity | An `ids.unique` assertion finds repeated IDs. |
301
+ | `profile.validation.frontmatterFieldMissing` | Rule severity | A required frontmatter field is absent. |
302
+ | `profile.validation.referenceMissing` | Rule severity | A source ID is absent from a required target section. |
303
+ | `profile.validation.sectionMissing` | Rule severity | A required section heading is absent. |
304
+ | `profile.validation.sectionOrder` | Rule severity | A strict required-section order cannot be satisfied. |
305
+ | `profile.validation.textExcluded` | Rule severity | A selected target contains forbidden literal text. |
306
+ | `profile.validation.textMissing` | Rule severity | A selected target lacks required literal text from a `text.contains` assertion. |
307
+ | `profile.validation.assertionUnsupported` | `error` | A compiled assertion has no evaluator implementation; this is an internal safety diagnostic. |
308
+
309
+ ## Result Shape
310
+
311
+ `DeclarativeValidationResult` extends the public validation result shape:
312
+
313
+ ```ts
314
+ interface DeclarativeValidationResult extends ValidationResult {
315
+ valid: boolean;
316
+ diagnostics: readonly MarkdownDiagnostic[];
317
+ ruleResults: readonly ValidationRuleResult[];
318
+ profile: {
319
+ syntaxVersion: "markdown-engine.validation@v1";
320
+ documentVersion: EngineDocumentVersion;
321
+ ruleCount: number;
322
+ };
323
+ evidence?: DeclarativeValidationEvidence;
324
+ }
325
+ ```
326
+
327
+ `valid` is `false` when any error-severity diagnostic exists. Warning and info
328
+ validation diagnostics can make a rule result fail without making the aggregate
329
+ result invalid.
330
+
331
+ Rule results are sorted deterministically. Each rule result includes the public
332
+ `ruleId`, `passed`, and cloned diagnostics. Results do not expose compiled rule
333
+ plans or selector internals.
334
+
335
+ ## Evidence Fields
336
+
337
+ Evidence is emitted only when `DeclarativeValidationOptions.includeEvidence` is
338
+ `true`:
339
+
340
+ ```ts
341
+ interface DeclarativeValidationEvidence {
342
+ inputHash: string;
343
+ profileHash: string;
344
+ engineVersion: string;
345
+ runtimeVersion: string;
346
+ ruleResults: readonly ValidationRuleResult[];
347
+ diagnostics: readonly MarkdownDiagnostic[];
348
+ }
349
+ ```
350
+
351
+ `inputHash` is a lowercase hexadecimal SHA-256 digest of the stable JSON
352
+ serialization of the supplied normalized `EngineDocument` after omitting only
353
+ the top-level `document.path` field. Structural target paths remain part of the
354
+ canonical input.
355
+
356
+ `profileHash` is a lowercase hexadecimal SHA-256 digest of the stable JSON
357
+ serialization of the resolved `ValidationProfile` after applying the resolved
358
+ `documentVersion` and default rule severity of `error`. An omitted
359
+ `documentVersion` and an explicit matching `documentVersion` therefore produce
360
+ the same profile hash for the same document version and rules.
361
+
362
+ `engineVersion` records the package version that produced the evidence. In the
363
+ 2.0 release line this is `"2.0.0"` even though `documentVersion` remains
364
+ `"1.0.0"`.
365
+
366
+ Raw Markdown bytes, raw YAML bytes, YAML comments, caller file paths, and
367
+ `includeEvidence` itself are not part of either evidence hash.
368
+
369
+ ## CLI Behavior
370
+
371
+ The declarative validation CLI command is:
372
+
373
+ ```sh
374
+ markdown-engine validate --file <markdown-file> --profile <profile-file> [--format json]
375
+ ```
376
+
377
+ `--format json` is the default and only supported validation output format.
378
+ The command always normalizes Markdown with `documentVersion: "1.0.0"` and does
379
+ not accept `--document-version`.
380
+
381
+ The CLI reads and checks the profile before reading the Markdown file. Profile
382
+ parse, config, and compile failures emit profile-stage JSON and do not parse or
383
+ validate the Markdown file.
384
+
385
+ After profile compilation succeeds, the CLI emits a validation-result JSON
386
+ shape whether the document passes or fails. Validation CLI results include
387
+ evidence.
388
+
389
+ ## CLI JSON Union
390
+
391
+ The CLI JSON output is:
392
+
393
+ ```ts
394
+ type DeclarativeValidationCliJsonResult =
395
+ | DeclarativeValidationResult
396
+ | DeclarativeValidationConfigErrorResult;
397
+
398
+ interface DeclarativeValidationConfigErrorResult {
399
+ valid: false;
400
+ stage: "profile";
401
+ diagnostics: readonly MarkdownDiagnostic[];
402
+ ruleResults: readonly [];
403
+ profile?: undefined;
404
+ evidence?: undefined;
405
+ }
406
+ ```
407
+
408
+ Profile-stage JSON is used for invalid YAML, invalid profile shape,
409
+ unsupported syntax version, unsupported keys, unsupported selector targets,
410
+ unsupported assertion members, and incompatible selector/assertion pairs. It
411
+ contains no `profile` and no `evidence`.
412
+
413
+ Validation-result JSON is used after profile compilation succeeds. It contains
414
+ `profile`, `ruleResults`, `diagnostics`, `valid`, and `evidence`.
415
+
416
+ ## Exit Codes
417
+
418
+ | Exit code | Meaning |
419
+ | --- | --- |
420
+ | `0` | Validation completed with no error-severity diagnostics. |
421
+ | `1` | Profile/config/compile, Markdown normalization, document-version mismatch, or validation diagnostics include at least one error. |
422
+ | `2` | CLI usage, unsupported format, unknown argument, missing argument value, repeated singleton flag, unsupported `--document-version`, or local file read error. |
423
+
424
+ ## Compatibility And Migration
425
+
426
+ The v1 declarative validation syntax is a durable authoring contract for the
427
+ 2.0 package release line. Changes to profile syntax names, selector names,
428
+ assertion names, result fields, diagnostic codes, CLI flags, CLI JSON shape, or
429
+ evidence hash inputs require explicit compatibility review.
430
+
431
+ Migration notes:
432
+
433
+ - Consumers using fixed `validate(document, config)` rule families can continue
434
+ using that API. Declarative validation is additive and does not replace fixed
435
+ rule validation.
436
+ - Consumers that need reusable structural policies should move profile-owned
437
+ checks into `parseValidationProfile` and `validateWithProfile`.
438
+ - Consumers parsing CLI validation output must handle the
439
+ `DeclarativeValidationCliJsonResult` union. Profile-stage failures do not
440
+ include `profile` or `evidence`.
441
+ - Consumers that compare evidence hashes must normalize expectations around
442
+ resolved `documentVersion`, default rule severity, stable key order, and
443
+ exclusion of only top-level `document.path` from `inputHash`.
444
+ - Regex-like matching, JavaScript predicates, plugins, semantic scoring, and
445
+ profile-specific rules belong outside this package unless a future contract
446
+ explicitly expands the engine boundary.
447
+
448
+ ## Examples
449
+
450
+ Minimal profile:
451
+
452
+ ```yaml
453
+ syntaxVersion: markdown-engine.validation@v1
454
+ rules:
455
+ - id: sections.present
456
+ select:
457
+ target: document
458
+ assert:
459
+ sectionsRequired:
460
+ headings:
461
+ - Mission Brief
462
+ ```
463
+
464
+ Link existence profile:
465
+
466
+ ```yaml
467
+ syntaxVersion: markdown-engine.validation@v1
468
+ rules:
469
+ - id: rollback-link.exists
470
+ select:
471
+ target: link
472
+ section: Escalation
473
+ text: rollback guide
474
+ url: ./rollback-guide.md
475
+ assert:
476
+ exists: true
477
+ ```
478
+
479
+ Table cell text profile:
480
+
481
+ ```yaml
482
+ syntaxVersion: markdown-engine.validation@v1
483
+ documentVersion: 1.0.0
484
+ rules:
485
+ - id: requirement-text
486
+ severity: error
487
+ select:
488
+ target: tableCell
489
+ section: Requirements
490
+ tableHeader:
491
+ - ID
492
+ - Requirement statement
493
+ column: Requirement statement
494
+ assert:
495
+ text:
496
+ contains: shall
497
+ excludes:
498
+ - and/or
499
+ textOccurrenceCount:
500
+ text: shall
501
+ count: 1
502
+ ```
503
+
504
+ CLI invocation:
505
+
506
+ ```sh
507
+ markdown-engine validate --file docs/mission.md --profile validation-profile.yaml
508
+ ```
509
+
510
+ Reader-facing operational spec, release checklist, and requirements
511
+ traceability examples live under
512
+ `fixtures/declarative-validation/examples/**`. After building from the
513
+ repository root, run one passing and one intentionally failing example with:
514
+
515
+ ```sh
516
+ node dist/cli/index.js validate --file fixtures/declarative-validation/examples/operational-spec/pass.md --profile fixtures/declarative-validation/examples/operational-spec/profile.yaml
517
+ node dist/cli/index.js validate --file fixtures/declarative-validation/examples/operational-spec/fail.md --profile fixtures/declarative-validation/examples/operational-spec/profile.yaml
518
+ ```
519
+
520
+ The passing command exits `0`; the intentionally failing command exits `1` and
521
+ emits validation JSON with representative diagnostics and evidence.
522
+
523
+ ## Boundary And Non-Goals
524
+
525
+ Declarative validation remains inside the `markdown-engine` deterministic local
526
+ boundary: parse, normalize, validate, diagnose, serialize, and emit evidence.
527
+
528
+ The v1 contract explicitly excludes:
529
+
530
+ - arbitrary JavaScript
531
+ - expression evaluation
532
+ - user-supplied regular expression compilation
533
+ - profile-sourced regex compilation
534
+ - plugins and plugin loading
535
+ - network calls
536
+ - LLM calls
537
+ - file watching
538
+ - persistence
539
+ - profile-specific core semantics
540
+ - operational-design-spec, AGENTS.md, TASK.md, or other domain-specific rule
541
+ meaning in core engine code
542
+
543
+ The CLI reads only the caller-specified local Markdown and profile files. The
544
+ API owns no file traversal, daemon, database, browser runtime, network service,
545
+ agent adapter, MCP transport, runtime lens, or persistent cache.
546
+
547
+ ## Contract Review Gates
548
+
549
+ The BEL-985 contract gates are:
550
+
551
+ ```sh
552
+ npm run docs:declarative-validation-contract
553
+ npm run audit:declarative-validation-boundary
554
+ ```
555
+
556
+ The documentation gate checks this contract, README links, evidence files, and
557
+ package script wiring. The boundary audit checks dependency drift, source-level
558
+ runtime boundary patterns, unsupported regex-like and executable profile-key
559
+ coverage, and declarative validation boundary evidence.
@@ -0,0 +1,122 @@
1
+ # Frontmatter Contract
2
+
3
+ Status: Initial contract for `BEL-907 / WP-2A`
4
+ Last updated: 2026-04-30
5
+
6
+ This document defines the public behavior of YAML frontmatter parsing in
7
+ `markdown-engine`. The raw `yaml` parser document and AST are internal adapter
8
+ details and are not exposed through the public parse result.
9
+
10
+ ## Extraction
11
+
12
+ Frontmatter is recognized only when the Markdown document begins with an
13
+ optional UTF-8 BOM followed by an opening `---` line and a later closing `---`
14
+ line. If the opening delimiter is not closed, the input is treated as Markdown
15
+ body content.
16
+
17
+ Absent frontmatter produces no diagnostics and leaves `parsed.frontmatter` and
18
+ `parsed.document.frontmatter` unset. Empty frontmatter, such as:
19
+
20
+ ```yaml
21
+ ---
22
+ ---
23
+ ```
24
+
25
+ produces `{}` with no diagnostics.
26
+
27
+ ## YAML Parser Options
28
+
29
+ The adapter uses `yaml` package APIs verified against `yaml@2.8.3`. Parser
30
+ behavior is pinned with these engine-owned options:
31
+
32
+ ```ts
33
+ {
34
+ compat: null,
35
+ customTags: null,
36
+ intAsBigInt: false,
37
+ keepSourceTokens: false,
38
+ logLevel: "error",
39
+ merge: false,
40
+ prettyErrors: false,
41
+ resolveKnownTags: false,
42
+ schema: "core",
43
+ strict: true,
44
+ stringKeys: false,
45
+ uniqueKeys: true,
46
+ version: "1.2",
47
+ }
48
+ ```
49
+
50
+ Materialization uses:
51
+
52
+ ```ts
53
+ {
54
+ mapAsMap: true,
55
+ maxAliasCount: 50,
56
+ }
57
+ ```
58
+
59
+ `mapAsMap: true` prevents JavaScript object key coercion during materialization.
60
+ The engine then validates keys and converts supported maps into JSON-safe plain
61
+ objects.
62
+
63
+ ## Values
64
+
65
+ The schema is YAML 1.2 core. Representative scalar behavior:
66
+
67
+ | YAML source | Parsed value |
68
+ | --- | --- |
69
+ | `yes`, `no`, `on`, `off` | strings |
70
+ | `true`, `false` | booleans |
71
+ | `null`, `~` | `null` |
72
+ | integer and finite float values | numbers |
73
+
74
+ The public frontmatter value must be JSON-safe: `null`, booleans, finite
75
+ numbers, strings, arrays, and objects with string keys. Non-finite numbers such
76
+ as `.nan` or `.inf` are rejected because JSON serialization would otherwise
77
+ coerce them silently.
78
+
79
+ ## Keys
80
+
81
+ Duplicate mapping keys are invalid. They produce a
82
+ `frontmatter.yaml.invalid` diagnostic and no parsed frontmatter value.
83
+
84
+ All mapping keys must be YAML string scalars. Numeric, boolean, null, sequence,
85
+ or mapping keys are rejected with a `frontmatter.yaml.invalid` diagnostic before
86
+ JavaScript key coercion can change the input shape.
87
+
88
+ ## Warnings
89
+
90
+ YAML warnings are preserved as `frontmatter.yaml.warning` diagnostics with
91
+ severity `warning`. Warnings do not block `parsed.frontmatter` when the YAML can
92
+ still materialize into JSON-safe data.
93
+
94
+ Explicit tags outside the YAML 1.2 core contract are not resolved through
95
+ YAML 1.1 known-tag behavior. For example, `!!timestamp 2026-04-30` is preserved
96
+ as the string `2026-04-30` and accompanied by an unresolved-tag warning.
97
+
98
+ ## Aliases And Merge Keys
99
+
100
+ Aliases are supported with `maxAliasCount: 50` as the materialization limit. In
101
+ `yaml@2.8.3`, inputs that reach that threshold are rejected as excessive alias
102
+ expansion. Missing aliases are invalid and produce `frontmatter.yaml.invalid`.
103
+
104
+ Cyclic aliases are invalid. They produce `frontmatter.yaml.invalid` and no
105
+ parsed frontmatter value.
106
+
107
+ YAML merge keys are disabled. A `<<` key is treated as an ordinary string key,
108
+ not as an instruction to merge mappings.
109
+
110
+ ## Diagnostics
111
+
112
+ Invalid YAML, multiple YAML documents, duplicate keys, unsupported keys, alias
113
+ failures, cyclic aliases, non-finite numbers, and non-JSON-safe materialized
114
+ values produce
115
+ `frontmatter.yaml.invalid` diagnostics with severity `error`. Diagnostic source
116
+ ranges are mapped back to Markdown source positions when the YAML package
117
+ provides offsets; otherwise the full frontmatter block range is used.
118
+
119
+ Warnings produce `frontmatter.yaml.warning` diagnostics with severity
120
+ `warning`.
121
+
122
+ Markdown body parsing continues even when frontmatter parsing fails.