@jasonbelmonti/markdown-engine 3.0.0 → 3.1.1
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 +28 -0
- package/README.md +77 -8
- 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 +1 -1
- package/dist/api/declarative-validation.d.ts.map +1 -1
- package/dist/api/declarative-validation.js.map +1 -1
- package/dist/api/document-set-validation-types.d.ts +32 -0
- package/dist/api/document-set-validation-types.d.ts.map +1 -0
- package/dist/api/document-set-validation-types.js +2 -0
- package/dist/api/document-set-validation-types.js.map +1 -0
- package/dist/api/document-set-validation.d.ts +4 -0
- package/dist/api/document-set-validation.d.ts.map +1 -0
- package/dist/api/document-set-validation.js +60 -0
- package/dist/api/document-set-validation.js.map +1 -0
- package/dist/api/serialize.d.ts +2 -1
- package/dist/api/serialize.d.ts.map +1 -1
- package/dist/api/serialize.js.map +1 -1
- package/dist/declarative-validation/assertions/evaluator.d.ts.map +1 -1
- package/dist/declarative-validation/assertions/evaluator.js +6 -0
- package/dist/declarative-validation/assertions/evaluator.js.map +1 -1
- package/dist/declarative-validation/assertions/frontmatter-shape.d.ts +9 -0
- package/dist/declarative-validation/assertions/frontmatter-shape.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/frontmatter-shape.js +105 -0
- package/dist/declarative-validation/assertions/frontmatter-shape.js.map +1 -0
- package/dist/declarative-validation/assertions/text-format.d.ts +9 -0
- package/dist/declarative-validation/assertions/text-format.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/text-format.js +75 -0
- package/dist/declarative-validation/assertions/text-format.js.map +1 -0
- package/dist/declarative-validation/compiler/assertion-builders.d.ts.map +1 -1
- package/dist/declarative-validation/compiler/assertion-builders.js +38 -1
- package/dist/declarative-validation/compiler/assertion-builders.js.map +1 -1
- package/dist/declarative-validation/compiler/assertion-shapes.d.ts +3 -1
- package/dist/declarative-validation/compiler/assertion-shapes.d.ts.map +1 -1
- package/dist/declarative-validation/compiler/assertion-shapes.js +30 -0
- package/dist/declarative-validation/compiler/assertion-shapes.js.map +1 -1
- package/dist/declarative-validation/compiler/assertions.d.ts.map +1 -1
- package/dist/declarative-validation/compiler/assertions.js +2 -0
- package/dist/declarative-validation/compiler/assertions.js.map +1 -1
- package/dist/declarative-validation/compiler/compatibility.d.ts.map +1 -1
- package/dist/declarative-validation/compiler/compatibility.js +2 -0
- package/dist/declarative-validation/compiler/compatibility.js.map +1 -1
- package/dist/declarative-validation/compiler/plan.d.ts +8 -1
- package/dist/declarative-validation/compiler/plan.d.ts.map +1 -1
- package/dist/declarative-validation/evidence/index.d.ts.map +1 -1
- package/dist/declarative-validation/evidence/index.js +2 -1
- package/dist/declarative-validation/evidence/index.js.map +1 -1
- package/dist/declarative-validation/profile/assertion-schema.d.ts.map +1 -1
- package/dist/declarative-validation/profile/assertion-schema.js +48 -3
- package/dist/declarative-validation/profile/assertion-schema.js.map +1 -1
- package/dist/declarative-validation/profile/frontmatter-shape-schema.d.ts +8 -0
- package/dist/declarative-validation/profile/frontmatter-shape-schema.d.ts.map +1 -0
- package/dist/declarative-validation/profile/frontmatter-shape-schema.js +178 -0
- package/dist/declarative-validation/profile/frontmatter-shape-schema.js.map +1 -0
- package/dist/declarative-validation/profile/index.d.ts +21 -0
- package/dist/declarative-validation/profile/index.d.ts.map +1 -1
- package/dist/declarative-validation/profile/text-format-contract.d.ts +4 -0
- package/dist/declarative-validation/profile/text-format-contract.d.ts.map +1 -0
- package/dist/declarative-validation/profile/text-format-contract.js +6 -0
- package/dist/declarative-validation/profile/text-format-contract.js.map +1 -0
- package/dist/internal/package-version.d.ts +2 -0
- package/dist/internal/package-version.d.ts.map +1 -0
- package/dist/internal/package-version.js +2 -0
- package/dist/internal/package-version.js.map +1 -0
- package/dist-bundled/markdown-engine-cli.mjs +556 -6
- package/docs/contracts/api.md +60 -0
- package/docs/contracts/declarative-validation.md +161 -22
- package/fixtures/declarative-validation/examples/okf-v0.1/fail/invalid-log-date/log.md +5 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/fail/missing-concept-type/concepts/customer-metric.md +8 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/fail/non-root-index-frontmatter/datasets/index.md +7 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/pass/datasets/index.md +3 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/pass/datasets/sales.md +16 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/pass/index.md +8 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/pass/log.md +9 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/pass/playbooks/incident-response.md +17 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/profiles/concept.yaml +14 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/profiles/log.yaml +10 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/profiles/non-root-index.yaml +9 -0
- package/fixtures/declarative-validation/examples/okf-v0.1/profiles/root-index.yaml +12 -0
- package/package.json +3 -2
- package/scripts/install-markdown-engine-cli.sh +113 -0
package/docs/contracts/api.md
CHANGED
|
@@ -22,6 +22,7 @@ The package root exports the API functions, helpers, and types from `src/api/**`
|
|
|
22
22
|
- `validateAnnotations(document, annotations)`
|
|
23
23
|
- `parseValidationProfile(input, options?)`
|
|
24
24
|
- `validateWithProfile(document, profile, options?)`
|
|
25
|
+
- `validateDocumentSet(entries, options?)`
|
|
25
26
|
|
|
26
27
|
The package root also exports the public result, document, diagnostic, config,
|
|
27
28
|
and function types declared in `src/api/**`.
|
|
@@ -249,6 +250,64 @@ defaults, so an omitted `documentVersion` and an explicit matching
|
|
|
249
250
|
`documentVersion` produce the same profile hash for the same document version
|
|
250
251
|
and rules.
|
|
251
252
|
|
|
253
|
+
## `validateDocumentSet`
|
|
254
|
+
|
|
255
|
+
Signature:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
validateDocumentSet(
|
|
259
|
+
entries: readonly ValidateDocumentSetEntry[],
|
|
260
|
+
options?: ValidateDocumentSetOptions,
|
|
261
|
+
): ValidateDocumentSetResult
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`validateDocumentSet` is a pure aggregate API for caller-supplied Markdown
|
|
265
|
+
documents and caller-supplied validation profiles. Each entry contains:
|
|
266
|
+
|
|
267
|
+
- `path`: caller-owned document path or stable identifier
|
|
268
|
+
- `markdown`: Markdown text to parse and normalize
|
|
269
|
+
- `profile`: YAML profile text, JSON-safe profile data, or parsed
|
|
270
|
+
`ValidationProfile`
|
|
271
|
+
- `profilePath`: optional caller-owned profile path or stable identifier for
|
|
272
|
+
profile diagnostics
|
|
273
|
+
|
|
274
|
+
The function processes entries in the supplied order and returns `entries` in
|
|
275
|
+
that same order. It does not read directories, expand globs, watch files, write
|
|
276
|
+
persistent state, add CLI behavior, classify OKF paths, or choose profiles for
|
|
277
|
+
callers. Consumers own discovery, routing, and IO.
|
|
278
|
+
|
|
279
|
+
For OKF-style bundles, `validateDocumentSet` stays a generic aggregation API.
|
|
280
|
+
Callers must classify each path before calling the function and provide the
|
|
281
|
+
profile for that caller-classified role. A non-reserved concept document can use
|
|
282
|
+
a concept profile that requires `type` frontmatter, the bundle-root `index.md`
|
|
283
|
+
can use a root-index profile that accepts the `okf_version: "0.1"` exception, a
|
|
284
|
+
non-root `index.md` can use a no-frontmatter profile, and `log.md` can use a
|
|
285
|
+
log profile that checks date headings.
|
|
286
|
+
|
|
287
|
+
The API does not infer those roles from paths.
|
|
288
|
+
|
|
289
|
+
`ValidateDocumentSetOptions` forwards supported document normalization and
|
|
290
|
+
validation options:
|
|
291
|
+
|
|
292
|
+
- `documentVersion`
|
|
293
|
+
- `preserveSourceLocations`
|
|
294
|
+
- `includeEvidence`
|
|
295
|
+
|
|
296
|
+
Each `ValidateDocumentSetEntryResult` contains:
|
|
297
|
+
|
|
298
|
+
- `path` and optional `profilePath`
|
|
299
|
+
- `parseDiagnostics`
|
|
300
|
+
- `normalizationDiagnostics`
|
|
301
|
+
- `profileDiagnostics`
|
|
302
|
+
- `validationDiagnostics`
|
|
303
|
+
- aggregate entry `diagnostics`
|
|
304
|
+
- `validationResult` when profile parsing succeeds without error-severity
|
|
305
|
+
diagnostics
|
|
306
|
+
|
|
307
|
+
Profile-stage failures on one entry do not stop later entries from processing.
|
|
308
|
+
The aggregate `valid` field is `false` when any aggregate diagnostic has
|
|
309
|
+
severity `error`; otherwise it is `true`.
|
|
310
|
+
|
|
252
311
|
## `serialize`
|
|
253
312
|
|
|
254
313
|
Signature:
|
|
@@ -260,6 +319,7 @@ serialize(
|
|
|
260
319
|
| NormalizeResult
|
|
261
320
|
| ValidationResult
|
|
262
321
|
| DeclarativeValidationResult
|
|
322
|
+
| ValidateDocumentSetResult
|
|
263
323
|
| EngineDocument
|
|
264
324
|
| AnnotationValidationResult,
|
|
265
325
|
options?: SerializeOptions,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# Declarative Validation Contract
|
|
2
2
|
|
|
3
3
|
Status: package 3.0.0, v1 profile syntax with v2 Conditional V2, document contract 1.0.0
|
|
4
|
-
Last updated: 2026-06-
|
|
4
|
+
Last updated: 2026-06-15
|
|
5
5
|
Current v2 surface: flat-rule result/evidence shell, ID count-bound schema and
|
|
6
6
|
runtime evaluator contract, plus `tableColumnCoverage` schema, compiled-plan,
|
|
7
|
-
and runtime evaluator contract,
|
|
8
|
-
`
|
|
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
|
|
9
11
|
cloning contract.
|
|
10
12
|
|
|
11
13
|
This document defines the public declarative validation contract for
|
|
@@ -25,8 +27,11 @@ optional rule-level `when`. The admitted v2 path exposes the result and evidence
|
|
|
25
27
|
shell needed to distinguish assertion, grouped, and skipped evaluation output
|
|
26
28
|
from v1 output, plus the ID count-bound schema, compiled-plan, and runtime
|
|
27
29
|
evaluator contract; the `tableColumnCoverage` schema, compiled-plan, and
|
|
28
|
-
runtime evaluator contract;
|
|
29
|
-
|
|
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.
|
|
30
35
|
Non-matching applicability returns a public skipped rule result with
|
|
31
36
|
`status: "skipped"`, `passed: true`, `evaluation.kind: "skipped"`,
|
|
32
37
|
`reason: "whenNotMatched"`, `skippedRuleCount`, no top-level diagnostics, and a
|
|
@@ -53,6 +58,11 @@ validateWithProfile(
|
|
|
53
58
|
profile: ValidationProfile,
|
|
54
59
|
options?: DeclarativeValidationOptions,
|
|
55
60
|
): DeclarativeValidationResult
|
|
61
|
+
|
|
62
|
+
validateDocumentSet(
|
|
63
|
+
entries: readonly ValidateDocumentSetEntry[],
|
|
64
|
+
options?: ValidateDocumentSetOptions,
|
|
65
|
+
): ValidateDocumentSetResult
|
|
56
66
|
```
|
|
57
67
|
|
|
58
68
|
Compiled declarative validation plans are internal. They are not exported from
|
|
@@ -79,13 +89,16 @@ syntaxVersion: markdown-engine.validation@v2
|
|
|
79
89
|
This release recognizes v2 as a distinct syntax version at profile admission,
|
|
80
90
|
admits ID count bounds at the schema, compiled-plan, and runtime evaluator
|
|
81
91
|
layers, admits `tableColumnCoverage` at the schema, internal compiled-plan, and
|
|
82
|
-
runtime evaluator layers, admits
|
|
83
|
-
compiled-plan, and runtime evaluator layers,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`
|
|
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.
|
|
89
102
|
|
|
90
103
|
The admitted v1/v2 flat vocabulary is closed. Unknown profile keys, rule keys,
|
|
91
104
|
selector keys, known assertion keys, and nested assertion keys emit
|
|
@@ -207,8 +220,8 @@ emit `profile.config.invalidShape`.
|
|
|
207
220
|
|
|
208
221
|
Rule-level `when` is allowed only on v2 rules. Branch-level `when` remains
|
|
209
222
|
unsupported. V1 profiles preserve the original flat rule authoring contract;
|
|
210
|
-
grouped `anyOf` / `allOf`, ID count bounds, `tableColumnCoverage`,
|
|
211
|
-
rule-level `when` are v2 additions.
|
|
223
|
+
grouped `anyOf` / `allOf`, ID count bounds, `tableColumnCoverage`,
|
|
224
|
+
`frontmatterShape`, `textFormat`, and rule-level `when` are v2 additions.
|
|
212
225
|
|
|
213
226
|
Profile values must be JSON-safe data properties after YAML materialization.
|
|
214
227
|
Functions, accessors, proxies, cyclic structures, sparse arrays, `undefined`
|
|
@@ -300,6 +313,15 @@ interface DeclarativeAssertion {
|
|
|
300
313
|
};
|
|
301
314
|
require: "everySourceId";
|
|
302
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
|
+
};
|
|
303
325
|
text?: {
|
|
304
326
|
contains?: string;
|
|
305
327
|
excludes?: readonly string[];
|
|
@@ -312,6 +334,9 @@ interface DeclarativeAssertion {
|
|
|
312
334
|
min?: number;
|
|
313
335
|
max?: number;
|
|
314
336
|
};
|
|
337
|
+
textFormat?: {
|
|
338
|
+
format: "isoDate";
|
|
339
|
+
};
|
|
315
340
|
frontmatterRequired?: {
|
|
316
341
|
fields: readonly string[];
|
|
317
342
|
};
|
|
@@ -335,9 +360,11 @@ Selector/assertion compatibility is part of the public contract:
|
|
|
335
360
|
| `ids` | all supported selector targets |
|
|
336
361
|
| `references` | `document` |
|
|
337
362
|
| `tableColumnCoverage` | `document` |
|
|
363
|
+
| `frontmatterShape` | `document` |
|
|
338
364
|
| `text` | all supported selector targets |
|
|
339
365
|
| `textOccurrenceCount` | all supported selector targets |
|
|
340
366
|
| `textLength` | all supported selector targets |
|
|
367
|
+
| `textFormat` | all supported selector targets |
|
|
341
368
|
| `frontmatterRequired` | `document` |
|
|
342
369
|
|
|
343
370
|
Incompatible supported selector/assertion pairs emit
|
|
@@ -378,6 +405,35 @@ target section do not satisfy coverage. Missing target sections, missing target
|
|
|
378
405
|
columns, and missing target-column IDs emit deterministic validation diagnostics
|
|
379
406
|
source-grounded to the source ID when source evidence is available.
|
|
380
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.
|
|
436
|
+
|
|
381
437
|
`text` must include `contains` or a non-empty `excludes` array.
|
|
382
438
|
`textOccurrenceCount.count` is a finite number and counts non-overlapping
|
|
383
439
|
literal occurrences per selected target.
|
|
@@ -386,10 +442,22 @@ integers, `min` must be less than or equal to `max` when both are present, and
|
|
|
386
442
|
evaluation uses JavaScript string `.length` for each selected target's
|
|
387
443
|
normalized text.
|
|
388
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
|
+
|
|
389
457
|
Empty selector results produce `profile.validation.emptySelection` for exists,
|
|
390
|
-
table, ID, reference, text, occurrence, and text-
|
|
391
|
-
Document-scoped required-section
|
|
392
|
-
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.
|
|
393
461
|
|
|
394
462
|
## Diagnostics
|
|
395
463
|
|
|
@@ -418,9 +486,13 @@ diagnostic can still leave the aggregate `valid` value `true`.
|
|
|
418
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. |
|
|
419
487
|
| `profile.compile.incompatibleSelectorAssertion` | `error` | A supported selector target is paired with an incompatible supported assertion. |
|
|
420
488
|
| `profile.validation.emptySelection` | Rule severity | A rule cannot evaluate because its selector matches no applicable target. |
|
|
421
|
-
| `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,
|
|
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. |
|
|
422
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. |
|
|
423
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. |
|
|
424
496
|
| `profile.validation.idCountTooHigh` | Rule severity | Unique ID count after filtering is higher than `ids.maxCount`. |
|
|
425
497
|
| `profile.validation.idCountTooLow` | Rule severity | Unique ID count after filtering is lower than `ids.minCount`. |
|
|
426
498
|
| `profile.validation.referenceMissing` | Rule severity | A source ID is absent from a required target section. |
|
|
@@ -552,8 +624,8 @@ serialization of the resolved `ValidationProfile` after applying the resolved
|
|
|
552
624
|
`documentVersion` and an explicit matching `documentVersion` therefore produce
|
|
553
625
|
the same profile hash for the same document version and rules.
|
|
554
626
|
|
|
555
|
-
`engineVersion` records the package version that produced the evidence.
|
|
556
|
-
|
|
627
|
+
`engineVersion` records the package version that produced the evidence. This
|
|
628
|
+
matches the package metadata version even though `documentVersion` remains
|
|
557
629
|
`"1.0.0"`.
|
|
558
630
|
|
|
559
631
|
Raw Markdown bytes, raw YAML bytes, YAML comments, caller file paths, and
|
|
@@ -665,8 +737,21 @@ rules:
|
|
|
665
737
|
exists: true
|
|
666
738
|
```
|
|
667
739
|
|
|
668
|
-
|
|
669
|
-
profile
|
|
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
|
|
670
755
|
`evaluatedRuleCount` and `skippedRuleCount`; v2 rule results include `status`
|
|
671
756
|
and `evaluation`. The CLI does not add a second discriminator for v2; consumers
|
|
672
757
|
branch on
|
|
@@ -778,6 +863,60 @@ rules:
|
|
|
778
863
|
exists: true
|
|
779
864
|
```
|
|
780
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
|
+
|
|
781
920
|
CLI invocation:
|
|
782
921
|
|
|
783
922
|
```sh
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: BigQuery Dataset
|
|
3
|
+
title: Sales Dataset
|
|
4
|
+
description: Sales reporting assets for the example bundle.
|
|
5
|
+
resource: https://example.com/bigquery/datasets/sales
|
|
6
|
+
tags: [sales, reporting]
|
|
7
|
+
timestamp: 2026-06-15T00:00:00Z
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Schema
|
|
11
|
+
|
|
12
|
+
The sales dataset contains curated order and customer reporting tables.
|
|
13
|
+
|
|
14
|
+
# Citations
|
|
15
|
+
|
|
16
|
+
[1] [Internal sales catalog](https://example.com/catalog/sales)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Playbook
|
|
3
|
+
title: Incident Response
|
|
4
|
+
description: Triage steps for a data freshness incident.
|
|
5
|
+
tags: [oncall, freshness]
|
|
6
|
+
timestamp: 2026-06-15T00:00:00Z
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Trigger
|
|
10
|
+
|
|
11
|
+
A freshness alert fires when an expected dataset update is delayed.
|
|
12
|
+
|
|
13
|
+
# Steps
|
|
14
|
+
|
|
15
|
+
1. Check the ingestion dashboard.
|
|
16
|
+
2. Notify the on-call owner.
|
|
17
|
+
3. Record the incident in the bundle log.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
syntaxVersion: markdown-engine.validation@v2
|
|
2
|
+
documentVersion: 1.0.0
|
|
3
|
+
rules:
|
|
4
|
+
- id: okf.concept.frontmatter
|
|
5
|
+
select:
|
|
6
|
+
target: document
|
|
7
|
+
assert:
|
|
8
|
+
frontmatterShape:
|
|
9
|
+
presence: required
|
|
10
|
+
fields:
|
|
11
|
+
- field: type
|
|
12
|
+
required: true
|
|
13
|
+
valueType: string
|
|
14
|
+
nonEmpty: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jasonbelmonti/markdown-engine",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Deterministic Markdown parsing and validation engine package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"files": [
|
|
33
33
|
"dist",
|
|
34
34
|
"dist-bundled",
|
|
35
|
+
"scripts/install-markdown-engine-cli.sh",
|
|
35
36
|
"skills",
|
|
36
37
|
"docs/contracts",
|
|
37
38
|
"fixtures/declarative-validation/examples",
|
|
@@ -69,7 +70,7 @@
|
|
|
69
70
|
"test:validation:profile": "npm run build && vitest run tests/declarative-validation-profile.test.ts \"--exclude=.worktrees/**\"",
|
|
70
71
|
"test:validation:compiler": "npm run build && vitest run tests/declarative-validation-compiler.test.ts \"--exclude=.worktrees/**\"",
|
|
71
72
|
"test:validation:selectors": "npm run build && vitest run tests/declarative-validation-selectors.test.ts \"--exclude=.worktrees/**\"",
|
|
72
|
-
"test:validation:assertions": "npm run build && vitest run tests/declarative-validation-assertions.test.ts tests/declarative-validation-table-column-coverage-fixtures.test.ts tests/declarative-validation-grouped-rules-fixtures.test.ts tests/declarative-validation-when-skipped-rules-fixtures.test.ts \"--exclude=.worktrees/**\"",
|
|
73
|
+
"test:validation:assertions": "npm run build && vitest run tests/declarative-validation-assertions.test.ts tests/declarative-validation-frontmatter-shape-assertions.test.ts tests/declarative-validation-table-column-coverage-fixtures.test.ts tests/declarative-validation-grouped-rules-fixtures.test.ts tests/declarative-validation-when-skipped-rules-fixtures.test.ts \"--exclude=.worktrees/**\"",
|
|
73
74
|
"test:validation:diagnostics": "npm run build && vitest run tests/declarative-validation-diagnostics.test.ts \"--exclude=.worktrees/**\"",
|
|
74
75
|
"test:validation:cli": "npm run build && vitest run tests/declarative-validation-cli.test.ts \"--exclude=.worktrees/**\"",
|
|
75
76
|
"test:validation:examples": "npm run build && vitest run tests/declarative-validation-examples.test.ts \"--exclude=.worktrees/**\"",
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
VERSION="3.1.1"
|
|
5
|
+
PACKAGE="@jasonbelmonti/markdown-engine"
|
|
6
|
+
EXPECTED_SHA256="8c7ff572765cbdc6c38b94de52d69709f583ee7b4d2b7e25ba6be8c92125addf"
|
|
7
|
+
DEFAULT_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
8
|
+
MARKDOWN_ENGINE_HOME="${MARKDOWN_ENGINE_HOME:-$DEFAULT_DATA_HOME/markdown-engine}"
|
|
9
|
+
MARKDOWN_ENGINE_BIN_DIR="${MARKDOWN_ENGINE_BIN_DIR:-$HOME/.local/bin}"
|
|
10
|
+
INSTALL_DIR="$MARKDOWN_ENGINE_HOME/tools/markdown-engine/$VERSION"
|
|
11
|
+
INSTALL_CLI="$INSTALL_DIR/markdown-engine-cli.mjs"
|
|
12
|
+
BIN_DIR="$MARKDOWN_ENGINE_BIN_DIR"
|
|
13
|
+
BIN_PATH="$BIN_DIR/markdown-engine"
|
|
14
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
15
|
+
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
16
|
+
LOCAL_CLI="$REPO_ROOT/dist-bundled/markdown-engine-cli.mjs"
|
|
17
|
+
PACKED_CLI_ENTRY="package/dist-bundled/markdown-engine-cli.mjs"
|
|
18
|
+
TMP_DIR=""
|
|
19
|
+
SOURCE_CLI=""
|
|
20
|
+
|
|
21
|
+
cleanup() {
|
|
22
|
+
if [ -n "$TMP_DIR" ] && [ -d "$TMP_DIR" ]; then
|
|
23
|
+
rm -rf "$TMP_DIR"
|
|
24
|
+
fi
|
|
25
|
+
}
|
|
26
|
+
trap cleanup EXIT HUP INT TERM
|
|
27
|
+
|
|
28
|
+
fail() {
|
|
29
|
+
printf 'install-markdown-engine-cli: %s\n' "$1" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
require_command() {
|
|
34
|
+
command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
sha256_of() {
|
|
38
|
+
shasum -a 256 "$1" | awk '{print $1}'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
shell_quote() {
|
|
42
|
+
printf "'"
|
|
43
|
+
printf '%s' "$1" | sed "s/'/'\\\\''/g"
|
|
44
|
+
printf "'"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
has_expected_sha() {
|
|
48
|
+
candidate_sha=$(sha256_of "$1")
|
|
49
|
+
|
|
50
|
+
if [ "$candidate_sha" = "$EXPECTED_SHA256" ]; then
|
|
51
|
+
return 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
printf 'install-markdown-engine-cli: ignoring local CLI with unexpected SHA-256 for %s: %s\n' "$1" "$candidate_sha" >&2
|
|
55
|
+
return 1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
resolve_source_cli() {
|
|
59
|
+
if [ -f "$LOCAL_CLI" ]; then
|
|
60
|
+
if has_expected_sha "$LOCAL_CLI"; then
|
|
61
|
+
SOURCE_CLI="$LOCAL_CLI"
|
|
62
|
+
return 0
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
require_command npm
|
|
67
|
+
require_command tar
|
|
68
|
+
|
|
69
|
+
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/markdown-engine-cli.XXXXXX")
|
|
70
|
+
pack_file=$(npm pack "$PACKAGE@$VERSION" --ignore-scripts --pack-destination "$TMP_DIR" --silent)
|
|
71
|
+
|
|
72
|
+
[ -n "$pack_file" ] || fail "npm pack did not return a tarball name"
|
|
73
|
+
[ -f "$TMP_DIR/$pack_file" ] || fail "npm pack tarball not found: $TMP_DIR/$pack_file"
|
|
74
|
+
|
|
75
|
+
tar -xzf "$TMP_DIR/$pack_file" -C "$TMP_DIR" "$PACKED_CLI_ENTRY" ||
|
|
76
|
+
fail "packed CLI artifact not found in $PACKAGE@$VERSION"
|
|
77
|
+
|
|
78
|
+
packed_cli="$TMP_DIR/$PACKED_CLI_ENTRY"
|
|
79
|
+
[ -f "$packed_cli" ] || fail "packed CLI artifact not found in $PACKAGE@$VERSION"
|
|
80
|
+
SOURCE_CLI="$packed_cli"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
require_command shasum
|
|
84
|
+
require_command node
|
|
85
|
+
require_command sed
|
|
86
|
+
|
|
87
|
+
resolve_source_cli
|
|
88
|
+
source_cli="$SOURCE_CLI"
|
|
89
|
+
[ -n "$source_cli" ] || fail "unable to resolve bundled CLI source"
|
|
90
|
+
actual_sha=$(sha256_of "$source_cli")
|
|
91
|
+
|
|
92
|
+
if [ "$actual_sha" != "$EXPECTED_SHA256" ]; then
|
|
93
|
+
fail "unexpected CLI SHA-256 for $source_cli: $actual_sha"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
mkdir -p "$INSTALL_DIR" "$BIN_DIR"
|
|
97
|
+
cp "$source_cli" "$INSTALL_CLI"
|
|
98
|
+
chmod 755 "$INSTALL_CLI"
|
|
99
|
+
|
|
100
|
+
quoted_install_cli=$(shell_quote "$INSTALL_CLI")
|
|
101
|
+
{
|
|
102
|
+
printf '%s\n' '#!/usr/bin/env sh'
|
|
103
|
+
printf 'DEFAULT_MARKDOWN_ENGINE_CLI=%s\n' "$quoted_install_cli"
|
|
104
|
+
printf '%s\n' 'MARKDOWN_ENGINE_CLI="${MARKDOWN_ENGINE_CLI:-$DEFAULT_MARKDOWN_ENGINE_CLI}"'
|
|
105
|
+
printf '%s\n' 'exec "${NODE_BINARY:-node}" "$MARKDOWN_ENGINE_CLI" "$@"'
|
|
106
|
+
} > "$BIN_PATH"
|
|
107
|
+
chmod 755 "$BIN_PATH"
|
|
108
|
+
|
|
109
|
+
"${NODE_BINARY:-node}" "$INSTALL_CLI" --help >/dev/null
|
|
110
|
+
|
|
111
|
+
printf 'Installed markdown-engine CLI: %s\n' "$INSTALL_CLI"
|
|
112
|
+
printf 'Installed markdown-engine wrapper: %s\n' "$BIN_PATH"
|
|
113
|
+
printf 'SHA-256: %s\n' "$actual_sha"
|