@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.
- package/CHANGELOG.md +24 -1
- package/README.md +75 -26
- package/SECURITY.md +5 -3
- package/dist/api/annotation-source-range.d.ts +7 -0
- package/dist/api/annotation-source-range.d.ts.map +1 -0
- package/dist/api/annotation-source-range.js +103 -0
- package/dist/api/annotation-source-range.js.map +1 -0
- package/dist/api/annotation-target-candidate.d.ts +10 -0
- package/dist/api/annotation-target-candidate.d.ts.map +1 -0
- package/dist/api/annotation-target-candidate.js +14 -0
- package/dist/api/annotation-target-candidate.js.map +1 -0
- package/dist/api/annotation-target-cloning.d.ts +5 -0
- package/dist/api/annotation-target-cloning.d.ts.map +1 -0
- package/dist/api/annotation-target-cloning.js +122 -0
- package/dist/api/annotation-target-cloning.js.map +1 -0
- package/dist/api/annotation-target-diagnostics.d.ts +8 -0
- package/dist/api/annotation-target-diagnostics.d.ts.map +1 -0
- package/dist/api/annotation-target-diagnostics.js +57 -0
- package/dist/api/annotation-target-diagnostics.js.map +1 -0
- package/dist/api/annotation-target-validation.d.ts +2 -2
- package/dist/api/annotation-target-validation.d.ts.map +1 -1
- package/dist/api/annotation-target-validation.js +5 -272
- package/dist/api/annotation-target-validation.js.map +1 -1
- package/dist/api/contracts.d.ts +2 -1
- package/dist/api/contracts.d.ts.map +1 -1
- package/dist/api/contracts.js +1 -0
- package/dist/api/contracts.js.map +1 -1
- package/dist/api/declarative-validation.d.ts +13 -0
- package/dist/api/declarative-validation.d.ts.map +1 -0
- package/dist/api/declarative-validation.js +63 -0
- package/dist/api/declarative-validation.js.map +1 -0
- package/dist/api/document-queries.d.ts.map +1 -1
- package/dist/api/document-queries.js +74 -3
- package/dist/api/document-queries.js.map +1 -1
- package/dist/api/document.d.ts +44 -0
- package/dist/api/document.d.ts.map +1 -1
- package/dist/api/engine-node-attributes.d.ts +15 -0
- package/dist/api/engine-node-attributes.d.ts.map +1 -0
- package/dist/api/engine-node-attributes.js +99 -0
- package/dist/api/engine-node-attributes.js.map +1 -0
- package/dist/api/normalize.d.ts.map +1 -1
- package/dist/api/normalize.js +22 -1
- package/dist/api/normalize.js.map +1 -1
- package/dist/api/serialize.js +2 -14
- package/dist/api/serialize.js.map +1 -1
- package/dist/cli/args.d.ts +6 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +33 -25
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/declarative-validation.d.ts +18 -0
- package/dist/cli/declarative-validation.d.ts.map +1 -0
- package/dist/cli/declarative-validation.js +131 -0
- package/dist/cli/declarative-validation.js.map +1 -0
- package/dist/cli/files.d.ts +1 -0
- package/dist/cli/files.d.ts.map +1 -1
- package/dist/cli/files.js +3 -0
- package/dist/cli/files.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +18 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/validate-args.d.ts +18 -0
- package/dist/cli/validate-args.d.ts.map +1 -0
- package/dist/cli/validate-args.js +131 -0
- package/dist/cli/validate-args.js.map +1 -0
- package/dist/declarative-validation/assertions/context.d.ts +8 -0
- package/dist/declarative-validation/assertions/context.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/context.js +2 -0
- package/dist/declarative-validation/assertions/context.js.map +1 -0
- package/dist/declarative-validation/assertions/diagnostics.d.ts +22 -0
- package/dist/declarative-validation/assertions/diagnostics.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/diagnostics.js +71 -0
- package/dist/declarative-validation/assertions/diagnostics.js.map +1 -0
- package/dist/declarative-validation/assertions/document-text-offsets.d.ts +3 -0
- package/dist/declarative-validation/assertions/document-text-offsets.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/document-text-offsets.js +32 -0
- package/dist/declarative-validation/assertions/document-text-offsets.js.map +1 -0
- package/dist/declarative-validation/assertions/evaluator.d.ts +5 -0
- package/dist/declarative-validation/assertions/evaluator.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/evaluator.js +46 -0
- package/dist/declarative-validation/assertions/evaluator.js.map +1 -0
- package/dist/declarative-validation/assertions/exists.d.ts +4 -0
- package/dist/declarative-validation/assertions/exists.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/exists.js +8 -0
- package/dist/declarative-validation/assertions/exists.js.map +1 -0
- package/dist/declarative-validation/assertions/frontmatter-required.d.ts +9 -0
- package/dist/declarative-validation/assertions/frontmatter-required.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/frontmatter-required.js +15 -0
- package/dist/declarative-validation/assertions/frontmatter-required.js.map +1 -0
- package/dist/declarative-validation/assertions/id-targets.d.ts +29 -0
- package/dist/declarative-validation/assertions/id-targets.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/id-targets.js +428 -0
- package/dist/declarative-validation/assertions/id-targets.js.map +1 -0
- package/dist/declarative-validation/assertions/id-tokens.d.ts +15 -0
- package/dist/declarative-validation/assertions/id-tokens.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/id-tokens.js +27 -0
- package/dist/declarative-validation/assertions/id-tokens.js.map +1 -0
- package/dist/declarative-validation/assertions/ids.d.ts +9 -0
- package/dist/declarative-validation/assertions/ids.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/ids.js +42 -0
- package/dist/declarative-validation/assertions/ids.js.map +1 -0
- package/dist/declarative-validation/assertions/index.d.ts +3 -0
- package/dist/declarative-validation/assertions/index.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/index.js +3 -0
- package/dist/declarative-validation/assertions/index.js.map +1 -0
- package/dist/declarative-validation/assertions/literal-text.d.ts +2 -0
- package/dist/declarative-validation/assertions/literal-text.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/literal-text.js +14 -0
- package/dist/declarative-validation/assertions/literal-text.js.map +1 -0
- package/dist/declarative-validation/assertions/normalized-source-ranges.d.ts +3 -0
- package/dist/declarative-validation/assertions/normalized-source-ranges.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/normalized-source-ranges.js +105 -0
- package/dist/declarative-validation/assertions/normalized-source-ranges.js.map +1 -0
- package/dist/declarative-validation/assertions/ordering.d.ts +6 -0
- package/dist/declarative-validation/assertions/ordering.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/ordering.js +71 -0
- package/dist/declarative-validation/assertions/ordering.js.map +1 -0
- package/dist/declarative-validation/assertions/references.d.ts +9 -0
- package/dist/declarative-validation/assertions/references.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/references.js +354 -0
- package/dist/declarative-validation/assertions/references.js.map +1 -0
- package/dist/declarative-validation/assertions/sections-required.d.ts +9 -0
- package/dist/declarative-validation/assertions/sections-required.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/sections-required.js +49 -0
- package/dist/declarative-validation/assertions/sections-required.js.map +1 -0
- package/dist/declarative-validation/assertions/table-columns-required.d.ts +9 -0
- package/dist/declarative-validation/assertions/table-columns-required.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/table-columns-required.js +31 -0
- package/dist/declarative-validation/assertions/table-columns-required.js.map +1 -0
- package/dist/declarative-validation/assertions/text-length.d.ts +9 -0
- package/dist/declarative-validation/assertions/text-length.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/text-length.js +40 -0
- package/dist/declarative-validation/assertions/text-length.js.map +1 -0
- package/dist/declarative-validation/assertions/text-occurrence-count.d.ts +9 -0
- package/dist/declarative-validation/assertions/text-occurrence-count.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/text-occurrence-count.js +22 -0
- package/dist/declarative-validation/assertions/text-occurrence-count.js.map +1 -0
- package/dist/declarative-validation/assertions/text.d.ts +9 -0
- package/dist/declarative-validation/assertions/text.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/text.js +32 -0
- package/dist/declarative-validation/assertions/text.js.map +1 -0
- package/dist/declarative-validation/compiler/assertion-builders.d.ts +6 -0
- package/dist/declarative-validation/compiler/assertion-builders.d.ts.map +1 -0
- package/dist/declarative-validation/compiler/assertion-builders.js +199 -0
- package/dist/declarative-validation/compiler/assertion-builders.js.map +1 -0
- package/dist/declarative-validation/compiler/assertion-shapes.d.ts +18 -0
- package/dist/declarative-validation/compiler/assertion-shapes.d.ts.map +1 -0
- package/dist/declarative-validation/compiler/assertion-shapes.js +129 -0
- package/dist/declarative-validation/compiler/assertion-shapes.js.map +1 -0
- package/dist/declarative-validation/compiler/assertions.d.ts +5 -0
- package/dist/declarative-validation/compiler/assertions.d.ts.map +1 -0
- package/dist/declarative-validation/compiler/assertions.js +25 -0
- package/dist/declarative-validation/compiler/assertions.js.map +1 -0
- package/dist/declarative-validation/compiler/compatibility.d.ts +4 -0
- package/dist/declarative-validation/compiler/compatibility.d.ts.map +1 -0
- package/dist/declarative-validation/compiler/compatibility.js +28 -0
- package/dist/declarative-validation/compiler/compatibility.js.map +1 -0
- package/dist/declarative-validation/compiler/diagnostics.d.ts +3 -0
- package/dist/declarative-validation/compiler/diagnostics.d.ts.map +1 -0
- package/dist/declarative-validation/compiler/diagnostics.js +9 -0
- package/dist/declarative-validation/compiler/diagnostics.js.map +1 -0
- package/dist/declarative-validation/compiler/index.d.ts +5 -0
- package/dist/declarative-validation/compiler/index.d.ts.map +1 -0
- package/dist/declarative-validation/compiler/index.js +164 -0
- package/dist/declarative-validation/compiler/index.js.map +1 -0
- package/dist/declarative-validation/compiler/plan.d.ts +58 -0
- package/dist/declarative-validation/compiler/plan.d.ts.map +1 -0
- package/dist/declarative-validation/compiler/plan.js +2 -0
- package/dist/declarative-validation/compiler/plan.js.map +1 -0
- package/dist/declarative-validation/diagnostics/index.d.ts +7 -0
- package/dist/declarative-validation/diagnostics/index.d.ts.map +1 -0
- package/dist/declarative-validation/diagnostics/index.js +2 -0
- package/dist/declarative-validation/diagnostics/index.js.map +1 -0
- package/dist/declarative-validation/diagnostics/profile-config-diagnostics.d.ts +11 -0
- package/dist/declarative-validation/diagnostics/profile-config-diagnostics.d.ts.map +1 -0
- package/dist/declarative-validation/diagnostics/profile-config-diagnostics.js +45 -0
- package/dist/declarative-validation/diagnostics/profile-config-diagnostics.js.map +1 -0
- package/dist/declarative-validation/evidence/index.d.ts +14 -0
- package/dist/declarative-validation/evidence/index.d.ts.map +1 -0
- package/dist/declarative-validation/evidence/index.js +38 -0
- package/dist/declarative-validation/evidence/index.js.map +1 -0
- package/dist/declarative-validation/profile/assertion-schema.d.ts +4 -0
- package/dist/declarative-validation/profile/assertion-schema.d.ts.map +1 -0
- package/dist/declarative-validation/profile/assertion-schema.js +285 -0
- package/dist/declarative-validation/profile/assertion-schema.js.map +1 -0
- package/dist/declarative-validation/profile/data-closure.d.ts +6 -0
- package/dist/declarative-validation/profile/data-closure.d.ts.map +1 -0
- package/dist/declarative-validation/profile/data-closure.js +167 -0
- package/dist/declarative-validation/profile/data-closure.js.map +1 -0
- package/dist/declarative-validation/profile/direct-profile-diagnostics.d.ts +5 -0
- package/dist/declarative-validation/profile/direct-profile-diagnostics.d.ts.map +1 -0
- package/dist/declarative-validation/profile/direct-profile-diagnostics.js +73 -0
- package/dist/declarative-validation/profile/direct-profile-diagnostics.js.map +1 -0
- package/dist/declarative-validation/profile/index.d.ts +113 -0
- package/dist/declarative-validation/profile/index.d.ts.map +1 -0
- package/dist/declarative-validation/profile/index.js +2 -0
- package/dist/declarative-validation/profile/index.js.map +1 -0
- package/dist/declarative-validation/profile/materialization.d.ts +9 -0
- package/dist/declarative-validation/profile/materialization.d.ts.map +1 -0
- package/dist/declarative-validation/profile/materialization.js +109 -0
- package/dist/declarative-validation/profile/materialization.js.map +1 -0
- package/dist/declarative-validation/profile/parse.d.ts +3 -0
- package/dist/declarative-validation/profile/parse.d.ts.map +1 -0
- package/dist/declarative-validation/profile/parse.js +89 -0
- package/dist/declarative-validation/profile/parse.js.map +1 -0
- package/dist/declarative-validation/profile/schema-values.d.ts +14 -0
- package/dist/declarative-validation/profile/schema-values.d.ts.map +1 -0
- package/dist/declarative-validation/profile/schema-values.js +81 -0
- package/dist/declarative-validation/profile/schema-values.js.map +1 -0
- package/dist/declarative-validation/profile/schema.d.ts +9 -0
- package/dist/declarative-validation/profile/schema.d.ts.map +1 -0
- package/dist/declarative-validation/profile/schema.js +98 -0
- package/dist/declarative-validation/profile/schema.js.map +1 -0
- package/dist/declarative-validation/profile/selector-schema.d.ts +4 -0
- package/dist/declarative-validation/profile/selector-schema.d.ts.map +1 -0
- package/dist/declarative-validation/profile/selector-schema.js +145 -0
- package/dist/declarative-validation/profile/selector-schema.js.map +1 -0
- package/dist/declarative-validation/results/index.d.ts +26 -0
- package/dist/declarative-validation/results/index.d.ts.map +1 -0
- package/dist/declarative-validation/results/index.js +2 -0
- package/dist/declarative-validation/results/index.js.map +1 -0
- package/dist/declarative-validation/selectors/index.d.ts +57 -0
- package/dist/declarative-validation/selectors/index.d.ts.map +1 -0
- package/dist/declarative-validation/selectors/index.js +149 -0
- package/dist/declarative-validation/selectors/index.js.map +1 -0
- package/dist/declarative-validation/selectors/source.d.ts +8 -0
- package/dist/declarative-validation/selectors/source.d.ts.map +1 -0
- package/dist/declarative-validation/selectors/source.js +57 -0
- package/dist/declarative-validation/selectors/source.js.map +1 -0
- package/dist/declarative-validation/selectors/table-targets.d.ts +13 -0
- package/dist/declarative-validation/selectors/table-targets.d.ts.map +1 -0
- package/dist/declarative-validation/selectors/table-targets.js +106 -0
- package/dist/declarative-validation/selectors/table-targets.js.map +1 -0
- package/dist/declarative-validation/selectors/table-text.d.ts +4 -0
- package/dist/declarative-validation/selectors/table-text.d.ts.map +1 -0
- package/dist/declarative-validation/selectors/table-text.js +16 -0
- package/dist/declarative-validation/selectors/table-text.js.map +1 -0
- package/dist/{ir → internal}/document-node-walk.d.ts +5 -0
- package/dist/internal/document-node-walk.d.ts.map +1 -0
- package/dist/internal/document-node-walk.js +36 -0
- package/dist/internal/document-node-walk.js.map +1 -0
- package/dist/internal/stable-json.d.ts +3 -0
- package/dist/internal/stable-json.d.ts.map +1 -0
- package/dist/internal/stable-json.js +17 -0
- package/dist/internal/stable-json.js.map +1 -0
- package/dist/ir/document-derived-views.d.ts.map +1 -1
- package/dist/ir/document-derived-views.js +2 -0
- package/dist/ir/document-derived-views.js.map +1 -1
- package/dist/ir/document-link-reference-views.d.ts +3 -0
- package/dist/ir/document-link-reference-views.d.ts.map +1 -0
- package/dist/ir/document-link-reference-views.js +150 -0
- package/dist/ir/document-link-reference-views.js.map +1 -0
- package/dist/ir/document-link-views.d.ts.map +1 -1
- package/dist/ir/document-link-views.js +7 -8
- package/dist/ir/document-link-views.js.map +1 -1
- package/dist/ir/document-list-views.d.ts.map +1 -1
- package/dist/ir/document-list-views.js +14 -13
- package/dist/ir/document-list-views.js.map +1 -1
- package/dist/ir/document-sections.d.ts.map +1 -1
- package/dist/ir/document-sections.js +2 -4
- package/dist/ir/document-sections.js.map +1 -1
- package/dist/ir/document-table-views.js +1 -1
- package/dist/ir/document-table-views.js.map +1 -1
- package/dist/ir/document-text-spans.js +1 -1
- package/dist/ir/document-text-spans.js.map +1 -1
- package/dist/ir/document.d.ts +2 -3
- package/dist/ir/document.d.ts.map +1 -1
- package/dist/ir/document.js +1 -1
- package/dist/ir/document.js.map +1 -1
- package/dist/ir/index.d.ts +1 -0
- package/dist/ir/index.d.ts.map +1 -1
- package/dist/ir/normalization-input.d.ts +12 -0
- package/dist/ir/normalization-input.d.ts.map +1 -0
- package/dist/ir/normalization-input.js +2 -0
- package/dist/ir/normalization-input.js.map +1 -0
- package/dist/rules/code-fence-languages.d.ts.map +1 -1
- package/dist/rules/code-fence-languages.js +4 -6
- package/dist/rules/code-fence-languages.js.map +1 -1
- package/dist/rules/document-query.d.ts +0 -2
- package/dist/rules/document-query.d.ts.map +1 -1
- package/dist/rules/document-query.js +1 -17
- package/dist/rules/document-query.js.map +1 -1
- package/dist/rules/index.d.ts +2 -6
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +3 -37
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/links-allowed-schemes.d.ts.map +1 -1
- package/dist/rules/links-allowed-schemes.js +3 -2
- package/dist/rules/links-allowed-schemes.js.map +1 -1
- package/dist/rules/registry.d.ts +12 -0
- package/dist/rules/registry.d.ts.map +1 -0
- package/dist/rules/registry.js +61 -0
- package/dist/rules/registry.js.map +1 -0
- package/docs/contracts/api.md +661 -0
- package/docs/contracts/declarative-validation.md +559 -0
- package/docs/contracts/frontmatter.md +122 -0
- package/fixtures/declarative-validation/examples/operational-spec/fail.md +32 -0
- package/fixtures/declarative-validation/examples/operational-spec/pass.md +34 -0
- package/fixtures/declarative-validation/examples/operational-spec/profile.yaml +131 -0
- package/fixtures/declarative-validation/examples/release-checklist/fail.md +22 -0
- package/fixtures/declarative-validation/examples/release-checklist/pass.md +22 -0
- package/fixtures/declarative-validation/examples/release-checklist/profile.yaml +96 -0
- package/fixtures/declarative-validation/examples/requirements-traceability/fail.md +27 -0
- package/fixtures/declarative-validation/examples/requirements-traceability/pass.md +27 -0
- package/fixtures/declarative-validation/examples/requirements-traceability/profile.yaml +119 -0
- package/package.json +21 -6
- package/dist/ir/document-node-walk.d.ts.map +0 -1
- package/dist/ir/document-node-walk.js +0 -16
- 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.
|