@jasonbelmonti/markdown-engine 2.0.0 → 3.1.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 (196) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +132 -57
  3. package/dist/api/contracts.d.ts +2 -1
  4. package/dist/api/contracts.d.ts.map +1 -1
  5. package/dist/api/contracts.js +1 -0
  6. package/dist/api/contracts.js.map +1 -1
  7. package/dist/api/declarative-validation.d.ts +2 -2
  8. package/dist/api/declarative-validation.d.ts.map +1 -1
  9. package/dist/api/declarative-validation.js +53 -31
  10. package/dist/api/declarative-validation.js.map +1 -1
  11. package/dist/api/document-set-validation-types.d.ts +32 -0
  12. package/dist/api/document-set-validation-types.d.ts.map +1 -0
  13. package/dist/api/document-set-validation-types.js +2 -0
  14. package/dist/api/document-set-validation-types.js.map +1 -0
  15. package/dist/api/document-set-validation.d.ts +4 -0
  16. package/dist/api/document-set-validation.d.ts.map +1 -0
  17. package/dist/api/document-set-validation.js +60 -0
  18. package/dist/api/document-set-validation.js.map +1 -0
  19. package/dist/api/serialize.d.ts +2 -1
  20. package/dist/api/serialize.d.ts.map +1 -1
  21. package/dist/api/serialize.js.map +1 -1
  22. package/dist/cli/declarative-validation.d.ts.map +1 -1
  23. package/dist/cli/declarative-validation.js +7 -12
  24. package/dist/cli/declarative-validation.js.map +1 -1
  25. package/dist/declarative-validation/applicability/classifier.d.ts +18 -0
  26. package/dist/declarative-validation/applicability/classifier.d.ts.map +1 -0
  27. package/dist/declarative-validation/applicability/classifier.js +37 -0
  28. package/dist/declarative-validation/applicability/classifier.js.map +1 -0
  29. package/dist/declarative-validation/applicability/index.d.ts +2 -0
  30. package/dist/declarative-validation/applicability/index.d.ts.map +1 -0
  31. package/dist/declarative-validation/applicability/index.js +2 -0
  32. package/dist/declarative-validation/applicability/index.js.map +1 -0
  33. package/dist/declarative-validation/assertions/context.d.ts +2 -2
  34. package/dist/declarative-validation/assertions/context.d.ts.map +1 -1
  35. package/dist/declarative-validation/assertions/diagnostics.d.ts +7 -4
  36. package/dist/declarative-validation/assertions/diagnostics.d.ts.map +1 -1
  37. package/dist/declarative-validation/assertions/diagnostics.js +26 -1
  38. package/dist/declarative-validation/assertions/diagnostics.js.map +1 -1
  39. package/dist/declarative-validation/assertions/evaluator.d.ts +2 -2
  40. package/dist/declarative-validation/assertions/evaluator.d.ts.map +1 -1
  41. package/dist/declarative-validation/assertions/evaluator.js +9 -0
  42. package/dist/declarative-validation/assertions/evaluator.js.map +1 -1
  43. package/dist/declarative-validation/assertions/frontmatter-shape.d.ts +9 -0
  44. package/dist/declarative-validation/assertions/frontmatter-shape.d.ts.map +1 -0
  45. package/dist/declarative-validation/assertions/frontmatter-shape.js +105 -0
  46. package/dist/declarative-validation/assertions/frontmatter-shape.js.map +1 -0
  47. package/dist/declarative-validation/assertions/group-evaluator.d.ts +6 -0
  48. package/dist/declarative-validation/assertions/group-evaluator.d.ts.map +1 -0
  49. package/dist/declarative-validation/assertions/group-evaluator.js +57 -0
  50. package/dist/declarative-validation/assertions/group-evaluator.js.map +1 -0
  51. package/dist/declarative-validation/assertions/id-targets.d.ts +15 -0
  52. package/dist/declarative-validation/assertions/id-targets.d.ts.map +1 -1
  53. package/dist/declarative-validation/assertions/id-targets.js +18 -55
  54. package/dist/declarative-validation/assertions/id-targets.js.map +1 -1
  55. package/dist/declarative-validation/assertions/ids.d.ts.map +1 -1
  56. package/dist/declarative-validation/assertions/ids.js +100 -14
  57. package/dist/declarative-validation/assertions/ids.js.map +1 -1
  58. package/dist/declarative-validation/assertions/index.d.ts +1 -0
  59. package/dist/declarative-validation/assertions/index.d.ts.map +1 -1
  60. package/dist/declarative-validation/assertions/index.js +1 -0
  61. package/dist/declarative-validation/assertions/index.js.map +1 -1
  62. package/dist/declarative-validation/assertions/table-column-coverage.d.ts +9 -0
  63. package/dist/declarative-validation/assertions/table-column-coverage.d.ts.map +1 -0
  64. package/dist/declarative-validation/assertions/table-column-coverage.js +108 -0
  65. package/dist/declarative-validation/assertions/table-column-coverage.js.map +1 -0
  66. package/dist/declarative-validation/assertions/text-format.d.ts +9 -0
  67. package/dist/declarative-validation/assertions/text-format.d.ts.map +1 -0
  68. package/dist/declarative-validation/assertions/text-format.js +75 -0
  69. package/dist/declarative-validation/assertions/text-format.js.map +1 -0
  70. package/dist/declarative-validation/compiler/applicability-plan.d.ts +5 -0
  71. package/dist/declarative-validation/compiler/applicability-plan.d.ts.map +1 -0
  72. package/dist/declarative-validation/compiler/applicability-plan.js +22 -0
  73. package/dist/declarative-validation/compiler/applicability-plan.js.map +1 -0
  74. package/dist/declarative-validation/compiler/assertion-builders.d.ts +2 -1
  75. package/dist/declarative-validation/compiler/assertion-builders.d.ts.map +1 -1
  76. package/dist/declarative-validation/compiler/assertion-builders.js +88 -13
  77. package/dist/declarative-validation/compiler/assertion-builders.js.map +1 -1
  78. package/dist/declarative-validation/compiler/assertion-shapes.d.ts +5 -1
  79. package/dist/declarative-validation/compiler/assertion-shapes.d.ts.map +1 -1
  80. package/dist/declarative-validation/compiler/assertion-shapes.js +120 -0
  81. package/dist/declarative-validation/compiler/assertion-shapes.js.map +1 -1
  82. package/dist/declarative-validation/compiler/assertions.d.ts +2 -1
  83. package/dist/declarative-validation/compiler/assertions.d.ts.map +1 -1
  84. package/dist/declarative-validation/compiler/assertions.js +16 -4
  85. package/dist/declarative-validation/compiler/assertions.js.map +1 -1
  86. package/dist/declarative-validation/compiler/compatibility.d.ts.map +1 -1
  87. package/dist/declarative-validation/compiler/compatibility.js +3 -0
  88. package/dist/declarative-validation/compiler/compatibility.js.map +1 -1
  89. package/dist/declarative-validation/compiler/group-plans.d.ts +6 -0
  90. package/dist/declarative-validation/compiler/group-plans.d.ts.map +1 -0
  91. package/dist/declarative-validation/compiler/group-plans.js +67 -0
  92. package/dist/declarative-validation/compiler/group-plans.js.map +1 -0
  93. package/dist/declarative-validation/compiler/index.d.ts +1 -1
  94. package/dist/declarative-validation/compiler/index.d.ts.map +1 -1
  95. package/dist/declarative-validation/compiler/index.js +70 -35
  96. package/dist/declarative-validation/compiler/index.js.map +1 -1
  97. package/dist/declarative-validation/compiler/plan.d.ts +80 -5
  98. package/dist/declarative-validation/compiler/plan.d.ts.map +1 -1
  99. package/dist/declarative-validation/compiler/rule-fields.d.ts +6 -0
  100. package/dist/declarative-validation/compiler/rule-fields.d.ts.map +1 -0
  101. package/dist/declarative-validation/compiler/rule-fields.js +40 -0
  102. package/dist/declarative-validation/compiler/rule-fields.js.map +1 -0
  103. package/dist/declarative-validation/diagnostics/profile-config-diagnostics.d.ts +0 -1
  104. package/dist/declarative-validation/diagnostics/profile-config-diagnostics.d.ts.map +1 -1
  105. package/dist/declarative-validation/diagnostics/profile-config-diagnostics.js +8 -2
  106. package/dist/declarative-validation/diagnostics/profile-config-diagnostics.js.map +1 -1
  107. package/dist/declarative-validation/evidence/index.d.ts +3 -3
  108. package/dist/declarative-validation/evidence/index.d.ts.map +1 -1
  109. package/dist/declarative-validation/evidence/index.js +6 -6
  110. package/dist/declarative-validation/evidence/index.js.map +1 -1
  111. package/dist/declarative-validation/profile/applicability-schema.d.ts +4 -0
  112. package/dist/declarative-validation/profile/applicability-schema.d.ts.map +1 -0
  113. package/dist/declarative-validation/profile/applicability-schema.js +24 -0
  114. package/dist/declarative-validation/profile/applicability-schema.js.map +1 -0
  115. package/dist/declarative-validation/profile/assertion-schema.d.ts +2 -1
  116. package/dist/declarative-validation/profile/assertion-schema.d.ts.map +1 -1
  117. package/dist/declarative-validation/profile/assertion-schema.js +194 -14
  118. package/dist/declarative-validation/profile/assertion-schema.js.map +1 -1
  119. package/dist/declarative-validation/profile/direct-profile-diagnostics.d.ts +3 -1
  120. package/dist/declarative-validation/profile/direct-profile-diagnostics.d.ts.map +1 -1
  121. package/dist/declarative-validation/profile/direct-profile-diagnostics.js +10 -2
  122. package/dist/declarative-validation/profile/direct-profile-diagnostics.js.map +1 -1
  123. package/dist/declarative-validation/profile/frontmatter-shape-schema.d.ts +8 -0
  124. package/dist/declarative-validation/profile/frontmatter-shape-schema.d.ts.map +1 -0
  125. package/dist/declarative-validation/profile/frontmatter-shape-schema.js +178 -0
  126. package/dist/declarative-validation/profile/frontmatter-shape-schema.js.map +1 -0
  127. package/dist/declarative-validation/profile/group-schema.d.ts +5 -0
  128. package/dist/declarative-validation/profile/group-schema.d.ts.map +1 -0
  129. package/dist/declarative-validation/profile/group-schema.js +55 -0
  130. package/dist/declarative-validation/profile/group-schema.js.map +1 -0
  131. package/dist/declarative-validation/profile/ids-assertion-contract.d.ts +12 -0
  132. package/dist/declarative-validation/profile/ids-assertion-contract.d.ts.map +1 -0
  133. package/dist/declarative-validation/profile/ids-assertion-contract.js +32 -0
  134. package/dist/declarative-validation/profile/ids-assertion-contract.js.map +1 -0
  135. package/dist/declarative-validation/profile/index.d.ts +63 -2
  136. package/dist/declarative-validation/profile/index.d.ts.map +1 -1
  137. package/dist/declarative-validation/profile/index.js.map +1 -1
  138. package/dist/declarative-validation/profile/materialization.d.ts.map +1 -1
  139. package/dist/declarative-validation/profile/materialization.js +13 -10
  140. package/dist/declarative-validation/profile/materialization.js.map +1 -1
  141. package/dist/declarative-validation/profile/schema.d.ts.map +1 -1
  142. package/dist/declarative-validation/profile/schema.js +73 -12
  143. package/dist/declarative-validation/profile/schema.js.map +1 -1
  144. package/dist/declarative-validation/profile/syntax-version.d.ts +7 -0
  145. package/dist/declarative-validation/profile/syntax-version.d.ts.map +1 -0
  146. package/dist/declarative-validation/profile/syntax-version.js +11 -0
  147. package/dist/declarative-validation/profile/syntax-version.js.map +1 -0
  148. package/dist/declarative-validation/profile/text-format-contract.d.ts +4 -0
  149. package/dist/declarative-validation/profile/text-format-contract.d.ts.map +1 -0
  150. package/dist/declarative-validation/profile/text-format-contract.js +6 -0
  151. package/dist/declarative-validation/profile/text-format-contract.js.map +1 -0
  152. package/dist/declarative-validation/results/clone-rule-result.d.ts +6 -0
  153. package/dist/declarative-validation/results/clone-rule-result.d.ts.map +1 -0
  154. package/dist/declarative-validation/results/clone-rule-result.js +125 -0
  155. package/dist/declarative-validation/results/clone-rule-result.js.map +1 -0
  156. package/dist/declarative-validation/results/create-result.d.ts +15 -0
  157. package/dist/declarative-validation/results/create-result.d.ts.map +1 -0
  158. package/dist/declarative-validation/results/create-result.js +58 -0
  159. package/dist/declarative-validation/results/create-result.js.map +1 -0
  160. package/dist/declarative-validation/results/index.d.ts +3 -25
  161. package/dist/declarative-validation/results/index.d.ts.map +1 -1
  162. package/dist/declarative-validation/results/index.js +2 -1
  163. package/dist/declarative-validation/results/index.js.map +1 -1
  164. package/dist/declarative-validation/results/skipped-rule-result.d.ts +10 -0
  165. package/dist/declarative-validation/results/skipped-rule-result.d.ts.map +1 -0
  166. package/dist/declarative-validation/results/skipped-rule-result.js +18 -0
  167. package/dist/declarative-validation/results/skipped-rule-result.js.map +1 -0
  168. package/dist/declarative-validation/results/types.d.ts +77 -0
  169. package/dist/declarative-validation/results/types.d.ts.map +1 -0
  170. package/dist/declarative-validation/results/types.js +2 -0
  171. package/dist/declarative-validation/results/types.js.map +1 -0
  172. package/dist/declarative-validation/selectors/table-targets.d.ts +23 -0
  173. package/dist/declarative-validation/selectors/table-targets.d.ts.map +1 -1
  174. package/dist/declarative-validation/selectors/table-targets.js +52 -9
  175. package/dist/declarative-validation/selectors/table-targets.js.map +1 -1
  176. package/dist-bundled/markdown-engine-cli.mjs +27373 -0
  177. package/docs/contracts/api.md +79 -15
  178. package/docs/contracts/declarative-validation.md +462 -40
  179. package/fixtures/declarative-validation/examples/okf-v0.1/fail/invalid-log-date/log.md +5 -0
  180. package/fixtures/declarative-validation/examples/okf-v0.1/fail/missing-concept-type/concepts/customer-metric.md +8 -0
  181. package/fixtures/declarative-validation/examples/okf-v0.1/fail/non-root-index-frontmatter/datasets/index.md +7 -0
  182. package/fixtures/declarative-validation/examples/okf-v0.1/pass/datasets/index.md +3 -0
  183. package/fixtures/declarative-validation/examples/okf-v0.1/pass/datasets/sales.md +16 -0
  184. package/fixtures/declarative-validation/examples/okf-v0.1/pass/index.md +8 -0
  185. package/fixtures/declarative-validation/examples/okf-v0.1/pass/log.md +9 -0
  186. package/fixtures/declarative-validation/examples/okf-v0.1/pass/playbooks/incident-response.md +17 -0
  187. package/fixtures/declarative-validation/examples/okf-v0.1/profiles/concept.yaml +14 -0
  188. package/fixtures/declarative-validation/examples/okf-v0.1/profiles/log.yaml +10 -0
  189. package/fixtures/declarative-validation/examples/okf-v0.1/profiles/non-root-index.yaml +9 -0
  190. package/fixtures/declarative-validation/examples/okf-v0.1/profiles/root-index.yaml +12 -0
  191. package/package.json +9 -3
  192. package/scripts/install-markdown-engine-cli.sh +113 -0
  193. package/skills/profile-backed-markdown/SKILL.md +54 -0
  194. package/skills/profile-backed-markdown/assets/profiles/operational-spec.yaml +98 -0
  195. package/skills/profile-backed-markdown/references/repair-brief.md +14 -0
  196. package/skills/profile-backed-markdown/scripts/validate-profile-backed-markdown.mjs +590 -0
@@ -1,19 +1,41 @@
1
1
  # Declarative Validation Contract
2
2
 
3
- Status: package 2.0.0, v1 profile syntax, document contract 1.0.0
4
- Last updated: 2026-05-14
3
+ Status: package 3.0.0, v1 profile syntax with v2 Conditional V2, document contract 1.0.0
4
+ Last updated: 2026-06-15
5
+ Current v2 surface: flat-rule result/evidence shell, ID count-bound schema and
6
+ runtime evaluator contract, plus `tableColumnCoverage` schema, compiled-plan,
7
+ and runtime evaluator contract, `frontmatterShape` schema, compiled-plan, and
8
+ runtime evaluator contract, `textFormat` schema, compiled-plan, and runtime
9
+ evaluator contract, grouped rule runtime contract, and rule-level `when`
10
+ schema, matcher, public skipped-rule result, skipped counts, and evidence
11
+ cloning contract.
5
12
 
6
13
  This document defines the public declarative validation contract for
7
14
  `@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.
15
+ the v1 profile syntax, the admitted v2 profile syntax and runtime subset, the
16
+ CLI validation command, diagnostic codes, serialized result shapes, and evidence
17
+ fields. Internal parser output, compiled rule-plan records, selector target
18
+ records, and evaluator implementation modules are not public contracts.
19
+
20
+ Package 3.0 does not introduce `documentVersion: "3.0.0"` or CLI JSON
21
+ discrimination.
22
+ Declarative validation continues to use the existing `documentVersion: "1.0.0"`
23
+ rich IR document contract, while the profile admission path recognizes
24
+ `markdown-engine.validation@v2` for the same flat rule shape with `id`, optional
25
+ `severity`, `select`, and `assert`; non-recursive `anyOf` and `allOf`; and
26
+ optional rule-level `when`. The admitted v2 path exposes the result and evidence
27
+ shell needed to distinguish assertion, grouped, and skipped evaluation output
28
+ from v1 output, plus the ID count-bound schema, compiled-plan, and runtime
29
+ evaluator contract; the `tableColumnCoverage` schema, compiled-plan, and
30
+ runtime evaluator contract; the `frontmatterShape` schema, private
31
+ compiled-plan, and runtime evaluator contract; the `textFormat` schema,
32
+ private compiled-plan, and runtime evaluator contract; and the `when` schema
33
+ plus private compiled-plan and matcher contract. Matched applicability
34
+ continues into normal rule evaluation.
35
+ Non-matching applicability returns a public skipped rule result with
36
+ `status: "skipped"`, `passed: true`, `evaluation.kind: "skipped"`,
37
+ `reason: "whenNotMatched"`, `skippedRuleCount`, no top-level diagnostics, and a
38
+ nested `when` applicability result.
17
39
 
18
40
  ## 1.0 Contract
19
41
 
@@ -36,6 +58,11 @@ validateWithProfile(
36
58
  profile: ValidationProfile,
37
59
  options?: DeclarativeValidationOptions,
38
60
  ): DeclarativeValidationResult
61
+
62
+ validateDocumentSet(
63
+ entries: readonly ValidateDocumentSetEntry[],
64
+ options?: ValidateDocumentSetOptions,
65
+ ): ValidateDocumentSetResult
39
66
  ```
40
67
 
41
68
  Compiled declarative validation plans are internal. They are not exported from
@@ -53,8 +80,28 @@ syntaxVersion: markdown-engine.validation@v1
53
80
  `syntaxVersion` is required. Missing or unsupported values emit
54
81
  `profile.config.unsupportedSyntaxVersion`.
55
82
 
56
- The v1 vocabulary is closed. Unknown profile keys, rule keys, selector keys,
57
- known assertion keys, and nested assertion keys emit
83
+ The v2 syntax is admitted as an additive profile syntax:
84
+
85
+ ```yaml
86
+ syntaxVersion: markdown-engine.validation@v2
87
+ ```
88
+
89
+ This release recognizes v2 as a distinct syntax version at profile admission,
90
+ admits ID count bounds at the schema, compiled-plan, and runtime evaluator
91
+ layers, admits `tableColumnCoverage` at the schema, internal compiled-plan, and
92
+ runtime evaluator layers, admits `frontmatterShape` at the schema, internal
93
+ compiled-plan, and runtime evaluator layers, admits `textFormat` at the schema,
94
+ internal compiled-plan, and runtime evaluator layers, admits non-recursive
95
+ grouped rules at the schema, compiled-plan, and runtime evaluator layers, and
96
+ admits optional rule-level `when` at the schema, internal compiled-plan, and
97
+ matcher layers.
98
+ Matching `when` rules continue through normal flat or grouped evaluation and do
99
+ not add a public `when` field to the evaluated rule result. Non-matching `when`
100
+ rules are not evaluated; they return the public skipped-rule result shape,
101
+ increment `skippedRuleCount`, and leave `evaluatedRuleCount` unchanged.
102
+
103
+ The admitted v1/v2 flat vocabulary is closed. Unknown profile keys, rule keys,
104
+ selector keys, known assertion keys, and nested assertion keys emit
58
105
  `profile.config.unsupportedKey` unless the contract assigns a more specific
59
106
  compile diagnostic for an unsupported selector target or unsupported assertion
60
107
  member.
@@ -113,15 +160,52 @@ results, and does not evaluate rules.
113
160
  The top-level profile shape is:
114
161
 
115
162
  ```ts
163
+ type ValidationProfileSyntaxVersion =
164
+ | "markdown-engine.validation@v1"
165
+ | "markdown-engine.validation@v2";
166
+
116
167
  interface ValidationProfile {
117
- syntaxVersion: "markdown-engine.validation@v1";
168
+ syntaxVersion: ValidationProfileSyntaxVersion;
118
169
  documentVersion?: EngineDocumentVersion;
119
170
  rules: readonly DeclarativeValidationRule[];
120
171
  }
121
172
 
122
- interface DeclarativeValidationRule {
173
+ type DeclarativeValidationRule =
174
+ | DeclarativeValidationFlatRule
175
+ | DeclarativeValidationGroupRule;
176
+
177
+ interface DeclarativeValidationRuleFields {
123
178
  id: string;
124
179
  severity?: "error" | "warning" | "info";
180
+ when?: DeclarativeValidationApplicability;
181
+ }
182
+
183
+ interface DeclarativeValidationFlatRule extends DeclarativeValidationRuleFields {
184
+ select: DeclarativeSelector;
185
+ assert: DeclarativeAssertion;
186
+ }
187
+
188
+ interface DeclarativeValidationApplicability {
189
+ select: DeclarativeSelector;
190
+ assert: DeclarativeAssertion;
191
+ }
192
+
193
+ type DeclarativeValidationGroupRule =
194
+ | DeclarativeValidationAnyOfRule
195
+ | DeclarativeValidationAllOfRule;
196
+
197
+ interface DeclarativeValidationAnyOfRule
198
+ extends DeclarativeValidationRuleFields {
199
+ anyOf: readonly DeclarativeValidationBranch[];
200
+ }
201
+
202
+ interface DeclarativeValidationAllOfRule
203
+ extends DeclarativeValidationRuleFields {
204
+ allOf: readonly DeclarativeValidationBranch[];
205
+ }
206
+
207
+ interface DeclarativeValidationBranch {
208
+ label?: string;
125
209
  select: DeclarativeSelector;
126
210
  assert: DeclarativeAssertion;
127
211
  }
@@ -134,6 +218,11 @@ evidence identify output by `ruleId`.
134
218
  Rule `severity` defaults to `error` when omitted. Unsupported severity values
135
219
  emit `profile.config.invalidShape`.
136
220
 
221
+ Rule-level `when` is allowed only on v2 rules. Branch-level `when` remains
222
+ unsupported. V1 profiles preserve the original flat rule authoring contract;
223
+ grouped `anyOf` / `allOf`, ID count bounds, `tableColumnCoverage`,
224
+ `frontmatterShape`, `textFormat`, and rule-level `when` are v2 additions.
225
+
137
226
  Profile values must be JSON-safe data properties after YAML materialization.
138
227
  Functions, accessors, proxies, cyclic structures, sparse arrays, `undefined`
139
228
  payloads in required positions, non-finite numbers, and `__proto__` properties
@@ -203,11 +292,36 @@ interface DeclarativeAssertion {
203
292
  prefix?: string;
204
293
  unique?: boolean;
205
294
  caseSensitive?: boolean;
295
+ minCount?: number;
296
+ maxCount?: number;
206
297
  };
207
298
  references?: {
208
299
  idsFrom: { section?: string; column?: string; prefix?: string };
209
300
  mustAppearIn: readonly string[];
210
301
  };
302
+ tableColumnCoverage?: {
303
+ source: {
304
+ section: string;
305
+ column: string;
306
+ prefix?: string;
307
+ caseSensitive?: boolean;
308
+ };
309
+ target: {
310
+ section: string;
311
+ tableHeader?: readonly string[];
312
+ column: string;
313
+ };
314
+ require: "everySourceId";
315
+ };
316
+ frontmatterShape?: {
317
+ presence?: "required" | "forbidden";
318
+ fields?: readonly {
319
+ field: string;
320
+ required?: true;
321
+ valueType?: "string" | "number" | "boolean" | "array" | "object" | "null";
322
+ nonEmpty?: true;
323
+ }[];
324
+ };
211
325
  text?: {
212
326
  contains?: string;
213
327
  excludes?: readonly string[];
@@ -220,6 +334,9 @@ interface DeclarativeAssertion {
220
334
  min?: number;
221
335
  max?: number;
222
336
  };
337
+ textFormat?: {
338
+ format: "isoDate";
339
+ };
223
340
  frontmatterRequired?: {
224
341
  fields: readonly string[];
225
342
  };
@@ -242,9 +359,12 @@ Selector/assertion compatibility is part of the public contract:
242
359
  | `tableColumnsRequired` | `table` |
243
360
  | `ids` | all supported selector targets |
244
361
  | `references` | `document` |
362
+ | `tableColumnCoverage` | `document` |
363
+ | `frontmatterShape` | `document` |
245
364
  | `text` | all supported selector targets |
246
365
  | `textOccurrenceCount` | all supported selector targets |
247
366
  | `textLength` | all supported selector targets |
367
+ | `textFormat` | all supported selector targets |
248
368
  | `frontmatterRequired` | `document` |
249
369
 
250
370
  Incompatible supported selector/assertion pairs emit
@@ -258,9 +378,61 @@ resolves zero targets.
258
378
  headings appear as an ordered subsequence in the normalized section tree
259
379
  flattened in source order.
260
380
 
261
- `ids.unique` must be `true`; `prefix` and `caseSensitive` are modifiers, not
262
- standalone predicates. `caseSensitive` defaults to `true`. ID tokens use the
381
+ For v1 profiles, `ids.unique` must be `true`. For v2 profiles, `ids.unique`
382
+ must be `true` when provided and may be omitted when `ids.minCount` or
383
+ `ids.maxCount` provides the predicate. `prefix` and `caseSensitive` are modifiers,
384
+ not standalone predicates. `caseSensitive` defaults to `true`. ID tokens use the
263
385
  documented token grammar `[A-Za-z][A-Za-z0-9]*-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*`.
386
+ For v2 profiles, `ids.minCount` and `ids.maxCount` are admitted as non-negative
387
+ integer schema and compiled-plan fields. When both are present, `minCount` must
388
+ be less than or equal to `maxCount`. Runtime count evaluation uses unique
389
+ comparison values after prefix filtering and duplicate occurrence de-duplication.
390
+ Failed lower and upper bounds emit `profile.validation.idCountTooLow` and
391
+ `profile.validation.idCountTooHigh`.
392
+
393
+ For v2 profiles, `tableColumnCoverage` is admitted as a flat-rule schema and
394
+ internal compiled-plan assertion. It is compatible only with a `document`
395
+ selector because the assertion owns its source and target table-column inputs.
396
+ `source.section`, `source.column`, `target.section`, and `target.column` are
397
+ required non-empty strings. `source.prefix` is optional and must be non-empty
398
+ when provided. `source.caseSensitive` is optional and defaults to `true` in the
399
+ compiled plan. `target.tableHeader` is an optional non-empty string array.
400
+ `require` must be exactly `"everySourceId"`. Runtime evaluation extracts unique
401
+ source IDs from `source.section` and `source.column`, applies `source.prefix`
402
+ and `source.caseSensitive`, and requires every source ID comparison value to
403
+ appear in the configured target table column. IDs appearing elsewhere in the
404
+ target section do not satisfy coverage. Missing target sections, missing target
405
+ columns, and missing target-column IDs emit deterministic validation diagnostics
406
+ source-grounded to the source ID when source evidence is available.
407
+
408
+ For v2 profiles, `frontmatterShape` is admitted as a flat-rule schema and
409
+ internal compiled-plan assertion. It is compatible only with a `document`
410
+ selector because frontmatter is document metadata. `presence` is optional and
411
+ must be exactly `"required"` or `"forbidden"` when provided. `fields` is an
412
+ optional non-empty array of field constraints. Each field constraint has a
413
+ required non-empty `field` name and must include at least one effective
414
+ constraint: `required: true`, `valueType`, or `nonEmpty: true`. Field names
415
+ within one `frontmatterShape.fields` array must be unique. `valueType` must be
416
+ one of `"string"`, `"number"`, `"boolean"`, `"array"`, `"object"`, or `"null"`.
417
+ `nonEmpty: true` is a string predicate; when it is combined with `valueType`,
418
+ `valueType` must be `"string"`. `presence: "forbidden"` cannot be combined with
419
+ `fields`.
420
+
421
+ Runtime evaluation treats frontmatter as present when
422
+ `document.frontmatter !== undefined`. `presence: "required"` fails absent
423
+ frontmatter with `profile.validation.frontmatterMissing`.
424
+ `presence: "forbidden"` fails any present frontmatter value, including empty
425
+ frontmatter, with `profile.validation.frontmatterForbidden`. Field constraints
426
+ evaluate only top-level frontmatter fields and do not coerce values. A required
427
+ field that is absent, or any required field on non-object frontmatter, emits
428
+ `profile.validation.frontmatterFieldMissing`. Optional fields are ignored when
429
+ absent. `valueType` checks distinguish `"string"`, finite `"number"`,
430
+ `"boolean"`, `"array"`, plain `"object"`, and `"null"` values and emit
431
+ `profile.validation.frontmatterFieldTypeMismatch` on mismatch. `nonEmpty: true`
432
+ requires a present field value to be a non-empty string and emits
433
+ `profile.validation.frontmatterFieldEmpty` when that predicate fails. When
434
+ `valueType: "string"` and `nonEmpty: true` are combined, a non-string value
435
+ emits the type-mismatch diagnostic without a duplicate empty-string diagnostic.
264
436
 
265
437
  `text` must include `contains` or a non-empty `excludes` array.
266
438
  `textOccurrenceCount.count` is a finite number and counts non-overlapping
@@ -270,10 +442,22 @@ integers, `min` must be less than or equal to `max` when both are present, and
270
442
  evaluation uses JavaScript string `.length` for each selected target's
271
443
  normalized text.
272
444
 
445
+ For v2 profiles, `textFormat` is admitted as a flat-rule schema and internal
446
+ compiled-plan assertion. `textFormat.format` must be exactly `"isoDate"`;
447
+ custom formats, locale options, regex-like formats, profile-supplied patterns,
448
+ and additional `textFormat` members are unsupported. Runtime evaluation checks
449
+ each selected target's normalized text as an exact real calendar date in
450
+ `YYYY-MM-DD` form. Valid dates such as `2026-06-14` and leap dates such as
451
+ `2024-02-29` pass. Non-dates, invalid calendar dates, timestamps, partial
452
+ dates, and strings with extra text fail with
453
+ `profile.validation.assertionFailed`. The evaluator does not compile
454
+ profile-supplied regular expressions, call `Date.parse`, perform locale
455
+ parsing, or implement date ordering.
456
+
273
457
  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.
458
+ table, ID, reference, text, occurrence, text-length, and text-format
459
+ assertions. Document-scoped required-section, required-frontmatter, and
460
+ frontmatter-shape assertions evaluate against the document.
277
461
 
278
462
  ## Diagnostics
279
463
 
@@ -284,11 +468,17 @@ error severity. Validation diagnostics use the rule severity. Source ranges are
284
468
  included when a selected target has source evidence; locations are omitted
285
469
  rather than fabricated when unavailable.
286
470
 
471
+ Rule-level `when` uses the existing validation diagnostic codes. When
472
+ applicability does not match, those diagnostics are cloned into
473
+ `ruleResults[].when.diagnostics`; they are not promoted into top-level
474
+ `diagnostics`, so a skipped rule with a nested error-severity applicability
475
+ diagnostic can still leave the aggregate `valid` value `true`.
476
+
287
477
  | Code | Severity source | Emitted when |
288
478
  | --- | --- | --- |
289
479
  | `profile.config.invalidYaml` | `error` | YAML text cannot be parsed or materialized as JSON-safe profile data. |
290
480
  | `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`. |
481
+ | `profile.config.unsupportedSyntaxVersion` | `error` | `syntaxVersion` is missing or is not `markdown-engine.validation@v1` or `markdown-engine.validation@v2`. |
292
482
  | `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
483
  | `profile.config.documentVersionMismatch` | `error` | Resolved profile `documentVersion` differs from the supplied `EngineDocument.version`. |
294
484
  | `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. |
@@ -296,15 +486,24 @@ rather than fabricated when unavailable.
296
486
  | `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
487
  | `profile.compile.incompatibleSelectorAssertion` | `error` | A supported selector target is paired with an incompatible supported assertion. |
298
488
  | `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. |
489
+ | `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, text-length bound failures, and text-format `isoDate` failures. |
300
490
  | `profile.validation.duplicateId` | Rule severity | An `ids.unique` assertion finds repeated IDs. |
491
+ | `profile.validation.frontmatterForbidden` | Rule severity | A `frontmatterShape` assertion with `presence: "forbidden"` finds present frontmatter. |
492
+ | `profile.validation.frontmatterFieldEmpty` | Rule severity | A frontmatter field configured with `nonEmpty: true` is not a non-empty string. |
301
493
  | `profile.validation.frontmatterFieldMissing` | Rule severity | A required frontmatter field is absent. |
494
+ | `profile.validation.frontmatterFieldTypeMismatch` | Rule severity | A frontmatter field value does not match the configured `frontmatterShape.fields[].valueType` without coercion. |
495
+ | `profile.validation.frontmatterMissing` | Rule severity | A `frontmatterShape` assertion with `presence: "required"` finds absent frontmatter. |
496
+ | `profile.validation.idCountTooHigh` | Rule severity | Unique ID count after filtering is higher than `ids.maxCount`. |
497
+ | `profile.validation.idCountTooLow` | Rule severity | Unique ID count after filtering is lower than `ids.minCount`. |
302
498
  | `profile.validation.referenceMissing` | Rule severity | A source ID is absent from a required target section. |
303
499
  | `profile.validation.sectionMissing` | Rule severity | A required section heading is absent. |
304
500
  | `profile.validation.sectionOrder` | Rule severity | A strict required-section order cannot be satisfied. |
501
+ | `profile.validation.tableColumnCoverageIdMissing` | Rule severity | A source ID is absent from the configured target table column. |
502
+ | `profile.validation.tableColumnCoverageTargetColumnMissing` | Rule severity | The configured target table column cannot be resolved. |
503
+ | `profile.validation.tableColumnCoverageTargetSectionMissing` | Rule severity | The configured target section cannot be resolved. |
305
504
  | `profile.validation.textExcluded` | Rule severity | A selected target contains forbidden literal text. |
306
505
  | `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. |
506
+ | `profile.validation.assertionUnsupported` | `error` | A compiled assertion or compiled assertion feature has no evaluator implementation; this is an internal safety diagnostic. |
308
507
 
309
508
  ## Result Shape
310
509
 
@@ -314,9 +513,12 @@ rather than fabricated when unavailable.
314
513
  interface DeclarativeValidationResult extends ValidationResult {
315
514
  valid: boolean;
316
515
  diagnostics: readonly MarkdownDiagnostic[];
317
- ruleResults: readonly ValidationRuleResult[];
516
+ ruleResults: readonly (
517
+ | ValidationRuleResult
518
+ | DeclarativeValidationRuleResultV2
519
+ )[];
318
520
  profile: {
319
- syntaxVersion: "markdown-engine.validation@v1";
521
+ syntaxVersion: ValidationProfileSyntaxVersion;
320
522
  documentVersion: EngineDocumentVersion;
321
523
  ruleCount: number;
322
524
  };
@@ -324,9 +526,61 @@ interface DeclarativeValidationResult extends ValidationResult {
324
526
  }
325
527
  ```
326
528
 
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.
529
+ For admitted v2 profiles, result metadata records
530
+ `syntaxVersion: "markdown-engine.validation@v2"`, `evaluatedRuleCount`, and
531
+ `skippedRuleCount`. V2 rule results include the v1-compatible `ruleId`,
532
+ `passed`, and `diagnostics` fields plus `status`, optional skipped
533
+ applicability metadata, and flat, grouped, or skipped evaluation metadata:
534
+
535
+ ```ts
536
+ interface DeclarativeValidationRuleResultV2 extends ValidationRuleResult {
537
+ status: "passed" | "failed" | "skipped";
538
+ when?: DeclarativeValidationApplicabilityResult;
539
+ evaluation:
540
+ | { kind: "assertions"; diagnostics: readonly MarkdownDiagnostic[] }
541
+ | {
542
+ kind: "anyOf";
543
+ selectedBranch?: DeclarativeValidationBranchReference;
544
+ branches: readonly DeclarativeValidationBranchResult[];
545
+ }
546
+ | {
547
+ kind: "allOf";
548
+ branches: readonly DeclarativeValidationBranchResult[];
549
+ }
550
+ | { kind: "skipped"; reason: "whenNotMatched" };
551
+ }
552
+
553
+ interface DeclarativeValidationApplicabilityResult {
554
+ status: "matched" | "notMatched";
555
+ diagnostics: readonly MarkdownDiagnostic[];
556
+ }
557
+
558
+ interface DeclarativeValidationBranchReference {
559
+ branchIndex: number;
560
+ label?: string;
561
+ }
562
+
563
+ interface DeclarativeValidationBranchResult
564
+ extends DeclarativeValidationBranchReference {
565
+ status: "passed" | "failed";
566
+ diagnostics: readonly MarkdownDiagnostic[];
567
+ }
568
+ ```
569
+
570
+ For configured `when`, matched applicability continues into normal flat or
571
+ grouped evaluation and contributes one evaluated rule. The evaluated rule result
572
+ does not serialize a `when` field. Non-matching applicability returns one
573
+ skipped rule result with `status: "skipped"`, `passed: true`, empty top-level
574
+ rule `diagnostics`, `when.status: "notMatched"`, nested applicability
575
+ diagnostics, `evaluation.kind: "skipped"`, and `reason: "whenNotMatched"`.
576
+ Skipped rules increment `skippedRuleCount`, do not increment
577
+ `evaluatedRuleCount`, and do not evaluate flat assertions or grouped branches.
578
+
579
+ `valid` is `false` when any top-level error-severity diagnostic exists in
580
+ `diagnostics`. Nested skipped applicability diagnostics under
581
+ `ruleResults[].when.diagnostics` do not by themselves make the aggregate result
582
+ invalid. Warning and info validation diagnostics can make a rule result fail
583
+ without making the aggregate result invalid.
330
584
 
331
585
  Rule results are sorted deterministically. Each rule result includes the public
332
586
  `ruleId`, `passed`, and cloned diagnostics. Results do not expose compiled rule
@@ -338,16 +592,27 @@ Evidence is emitted only when `DeclarativeValidationOptions.includeEvidence` is
338
592
  `true`:
339
593
 
340
594
  ```ts
341
- interface DeclarativeValidationEvidence {
595
+ interface DeclarativeValidationEvidence<
596
+ RuleResult extends ValidationRuleResult = ValidationRuleResult,
597
+ > {
342
598
  inputHash: string;
343
599
  profileHash: string;
344
600
  engineVersion: string;
345
601
  runtimeVersion: string;
346
- ruleResults: readonly ValidationRuleResult[];
602
+ ruleResults: readonly RuleResult[];
347
603
  diagnostics: readonly MarkdownDiagnostic[];
348
604
  }
349
605
  ```
350
606
 
607
+ For v1 profiles, `ruleResults` contains the unchanged v1 rule-result shape. For
608
+ admitted v2 profiles, `ruleResults` clones the public v2 rule-result shape,
609
+ including `status`, flat assertion evaluation, and grouped `anyOf` / `allOf`
610
+ branch evaluation. Skipped v2 rule results are cloned through evidence in the
611
+ same `ruleResults` array, and `evidence.diagnostics` clones the top-level
612
+ diagnostics array. Evidence does not serialize compiled rule plans, selector
613
+ target records, assertion-specific ID count evidence, or assertion-specific
614
+ table-column coverage evidence.
615
+
351
616
  `inputHash` is a lowercase hexadecimal SHA-256 digest of the stable JSON
352
617
  serialization of the supplied normalized `EngineDocument` after omitting only
353
618
  the top-level `document.path` field. Structural target paths remain part of the
@@ -360,7 +625,7 @@ serialization of the resolved `ValidationProfile` after applying the resolved
360
625
  the same profile hash for the same document version and rules.
361
626
 
362
627
  `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
628
+ 3.0 release line this is `"3.0.0"` even though `documentVersion` remains
364
629
  `"1.0.0"`.
365
630
 
366
631
  Raw Markdown bytes, raw YAML bytes, YAML comments, caller file paths, and
@@ -384,7 +649,9 @@ validate the Markdown file.
384
649
 
385
650
  After profile compilation succeeds, the CLI emits a validation-result JSON
386
651
  shape whether the document passes or fails. Validation CLI results include
387
- evidence.
652
+ evidence. V2 CLI results use the same validation-result arm of the CLI JSON
653
+ union; there is no extra CLI discriminator beyond
654
+ `profile.syntaxVersion: "markdown-engine.validation@v2"`.
388
655
 
389
656
  ## CLI JSON Union
390
657
 
@@ -411,23 +678,85 @@ unsupported assertion members, and incompatible selector/assertion pairs. It
411
678
  contains no `profile` and no `evidence`.
412
679
 
413
680
  Validation-result JSON is used after profile compilation succeeds. It contains
414
- `profile`, `ruleResults`, `diagnostics`, `valid`, and `evidence`.
681
+ `profile`, `ruleResults`, `diagnostics`, `valid`, and `evidence`. For v2
682
+ profiles, that same validation-result JSON can include `evaluatedRuleCount`,
683
+ `skippedRuleCount`, `status: "skipped"`, nested `when` diagnostics, and
684
+ `evaluation.kind: "skipped"`.
415
685
 
416
686
  ## Exit Codes
417
687
 
418
688
  | Exit code | Meaning |
419
689
  | --- | --- |
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. |
690
+ | `0` | Validation completed with no top-level error-severity diagnostics. |
691
+ | `1` | Profile/config/compile, Markdown normalization, document-version mismatch, or top-level validation diagnostics include at least one error. |
422
692
  | `2` | CLI usage, unsupported format, unknown argument, missing argument value, repeated singleton flag, unsupported `--document-version`, or local file read error. |
423
693
 
424
694
  ## Compatibility And Migration
425
695
 
426
696
  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,
697
+ 3.0 package release line. Changes to profile syntax names, selector names,
428
698
  assertion names, result fields, diagnostic codes, CLI flags, CLI JSON shape, or
429
699
  evidence hash inputs require explicit compatibility review.
430
700
 
701
+ V1 preservation is explicit: v1 authoring syntax, v1 rule result shape, v1
702
+ diagnostic inventory, v1 CLI JSON behavior, and v1 evidence hash inputs remain
703
+ unchanged by the admitted v2 syntax.
704
+
705
+ Compatibility examples:
706
+
707
+ ```yaml
708
+ # v1 compatibility profile: remains on the v1 authoring and result contract.
709
+ syntaxVersion: markdown-engine.validation@v1
710
+ rules:
711
+ - id: sections.present
712
+ select:
713
+ target: document
714
+ assert:
715
+ sectionsRequired:
716
+ headings:
717
+ - Mission Brief
718
+ ```
719
+
720
+ ```yaml
721
+ # v2 opt-in profile: selects Conditional V2 behavior explicitly.
722
+ syntaxVersion: markdown-engine.validation@v2
723
+ rules:
724
+ - id: release.docs
725
+ anyOf:
726
+ - label: release-section
727
+ select:
728
+ target: section
729
+ title: Release
730
+ assert:
731
+ exists: true
732
+ - label: changelog-link
733
+ select:
734
+ target: link
735
+ text: changelog
736
+ assert:
737
+ exists: true
738
+ ```
739
+
740
+ ```yaml
741
+ # v2 date-heading profile: validates exact ISO date heading text.
742
+ syntaxVersion: markdown-engine.validation@v2
743
+ rules:
744
+ - id: log.date-heading
745
+ select:
746
+ target: heading
747
+ depth: 2
748
+ assert:
749
+ textFormat:
750
+ format: isoDate
751
+ ```
752
+
753
+ The v1 profile above continues to emit the v1 validation-result shape. V2
754
+ profiles emit syntax-versioned v2 result metadata with
755
+ `evaluatedRuleCount` and `skippedRuleCount`; v2 rule results include `status`
756
+ and `evaluation`. The CLI does not add a second discriminator for v2; consumers
757
+ branch on
758
+ `profile.syntaxVersion: "markdown-engine.validation@v2"`.
759
+
431
760
  Migration notes:
432
761
 
433
762
  - Consumers using fixed `validate(document, config)` rule families can continue
@@ -438,6 +767,11 @@ Migration notes:
438
767
  - Consumers parsing CLI validation output must handle the
439
768
  `DeclarativeValidationCliJsonResult` union. Profile-stage failures do not
440
769
  include `profile` or `evidence`.
770
+ - Consumers opting into `markdown-engine.validation@v2` must handle rule
771
+ `status` values of `"passed"`, `"failed"`, and `"skipped"`, plus
772
+ `evaluatedRuleCount`, `skippedRuleCount`, grouped branch results, and nested
773
+ skipped-rule `when` diagnostics. Aggregate validity is still determined from
774
+ top-level diagnostics.
441
775
  - Consumers that compare evidence hashes must normalize expectations around
442
776
  resolved `documentVersion`, default rule severity, stable key order, and
443
777
  exclusion of only top-level `document.path` from `inputHash`.
@@ -501,6 +835,88 @@ rules:
501
835
  count: 1
502
836
  ```
503
837
 
838
+ Conditional v2 grouped rule with applicability:
839
+
840
+ ```yaml
841
+ syntaxVersion: markdown-engine.validation@v2
842
+ rules:
843
+ - id: release.when.docs-ready
844
+ when:
845
+ select:
846
+ target: section
847
+ title: Release
848
+ assert:
849
+ exists: true
850
+ anyOf:
851
+ - label: contract-link
852
+ select:
853
+ target: link
854
+ section: Release
855
+ text: contract
856
+ assert:
857
+ exists: true
858
+ - label: contract-heading
859
+ select:
860
+ target: heading
861
+ text: Contract
862
+ assert:
863
+ exists: true
864
+ ```
865
+
866
+ ### OKF v0.1 Hard-Validation Profile Composition
867
+
868
+ `markdown-engine` does not ship a built-in OKF validator. Consumers that want to
869
+ validate OKF v0.1 hard conformance compose generic declarative-validation
870
+ profiles and route documents to those profiles themselves. The caller owns
871
+ bundle traversal, path classification, IO, and role selection before invoking
872
+ `validateDocumentSet`.
873
+
874
+ The packaged OKF proof under
875
+ `fixtures/declarative-validation/examples/okf-v0.1/**` uses four generic
876
+ profiles:
877
+
878
+ | Caller-classified role | Example profile | Generic checks |
879
+ | --- | --- | --- |
880
+ | Concept document | `profiles/concept.yaml` | `frontmatterShape` requires parseable frontmatter with a non-empty string `type`. |
881
+ | Bundle-root `index.md` | `profiles/root-index.yaml` | `frontmatterShape` accepts a non-empty string `okf_version` when root version frontmatter is present. |
882
+ | Non-root `index.md` | `profiles/non-root-index.yaml` | `frontmatterShape` forbids frontmatter on directory index files. |
883
+ | `log.md` | `profiles/log.yaml` | `textFormat` checks level-2 log headings as real `YYYY-MM-DD` dates. |
884
+
885
+ Concept validation applies only to non-reserved concept documents. Reserved
886
+ `index.md` and `log.md` files are not routed through the concept profile and do
887
+ not fail merely because they lack concept `type` frontmatter. The bundle-root
888
+ `index.md` may carry the OKF version exception `okf_version: "0.1"` through the
889
+ root-index profile; non-root `index.md` files use the non-root index profile and
890
+ forbid frontmatter. `log.md` is validated as a log with `textFormat`, not as a
891
+ concept document.
892
+
893
+ A caller-owned routing layer can classify entries by path and choose the
894
+ matching profile before validation:
895
+
896
+ ```ts
897
+ const entries = bundleMarkdownFiles.map((file) => {
898
+ const role = classifyOkfPath(file.path);
899
+
900
+ return {
901
+ path: file.path,
902
+ markdown: file.markdown,
903
+ profile: profilesByRole[role],
904
+ profilePath: profilePathsByRole[role],
905
+ };
906
+ });
907
+
908
+ const result = validateDocumentSet(entries, {
909
+ documentVersion: "1.0.0",
910
+ includeEvidence: true,
911
+ });
912
+ ```
913
+
914
+ This composition proves hard OKF conformance only. It does not add CLI
915
+ traversal, an OKF adapter, runtime OKF semantics, directory watching, bundle
916
+ discovery, link checking, unknown-type rejection, optional metadata quality
917
+ checks, or rejection of missing optional `index.md` files. It also does not add
918
+ engine-owned path classification.
919
+
504
920
  CLI invocation:
505
921
 
506
922
  ```sh
@@ -540,6 +956,11 @@ The v1 contract explicitly excludes:
540
956
  - operational-design-spec, AGENTS.md, TASK.md, or other domain-specific rule
541
957
  meaning in core engine code
542
958
 
959
+ The admitted v2 Conditional V2 surface also excludes `documentVersion: "3.0.0"`,
960
+ recursive grouped rules, branch-level `when`, profile-defined predicates,
961
+ assertion-specific evidence payloads, a separate skipped-rule evidence channel,
962
+ and a new CLI JSON discriminator.
963
+
543
964
  The CLI reads only the caller-specified local Markdown and profile files. The
544
965
  API owns no file traversal, daemon, database, browser runtime, network service,
545
966
  agent adapter, MCP transport, runtime lens, or persistent cache.
@@ -553,7 +974,8 @@ npm run docs:declarative-validation-contract
553
974
  npm run audit:declarative-validation-boundary
554
975
  ```
555
976
 
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.
977
+ The documentation gate checks this contract, README links, legacy contract and
978
+ boundary evidence files, Conditional V2 EVD-6 reviewer notes, and package script
979
+ wiring. The boundary audit checks dependency drift, source-level runtime
980
+ boundary patterns, unsupported regex-like and executable profile-key coverage,
981
+ and declarative validation boundary evidence.