Grounded templates

Bind logical source names to real data — files, commands, loaders, or in-memory values — so a contract checks claims against current truth, not just shape.

AIRLOCK CONTROL: Visitor "@jsmith" is spelled correctly and is not on the crew manifest. Please wait outside.

A grounded rule checks a claim against real data, not just its shape.

incident-roles.mdv.md
<!-- mdv: section content.mask="On call: <mention|in: ./team.json>" -->
## Incident Roles
team.json
["maya", "priya"]
incident-roles.md
## Incident Roles

On call: @jsmith
incident-roles.md
  3  error  "@jsmith" not found in "./team.json"  unknown-entity
            help: Correct a misspelt name to the source's spelling; never swap in another name

Naming a source

SpellingExampleBound by
./path (contract-relative, no ..)<mention|in: ./team.json>the file beside the contract
logical name<issue|in: openIssues?>the host, at check time

A trailing ? makes the ref optional.

CaseResult
optional, unboundsilent skip: shape-only until wired
required, unboundloud failure naming the missing wiring
bound but failing to loadalways loud

Binding

Later wins: ground, then sources.declare, then --source.

// mediva.config.mjs
import { defineConfig } from "mediva";

export default defineConfig({
  ground: {
    // mdv sources sync writes .mdv/openIssues.json; auto-bound
    openIssues: "gh issue list --state open --json number --jq '[.[].number | tostring]'",
  },
  sources: {
    declare: {
      changedFiles: "./.mdv/changed.json",          // a path
      team: ["@ana", "@bo"],                         // literal data
      // changedFiles: async () => computeChangedFiles(), // or a live loader
    },
  },
});

--source NAME=FILE[,NAME=FILE] pins names to static dumps for one run. In code, use schema.bind().

Projections

EntrySource becomes
{ path, members: [...] }the named fields of a JSON array of objects
{ path, markdown: { headings: { level, mask? } } }the text of each heading at level
{ path, entries: "stem" | "name" }the directory's child filenames
{ path, records: [...] }rows kept as tuples, for table rows.in=
declare: {
  metrics: { path: "./metrics.json", records: ["metric", "period", "unit", "value"] },
  reviewers: { path: "./team.json", members: ["login"], compare: "exact" },
},
<!-- mdv: section required -->
## Metrics

<!-- mdv: table rows.in=metrics -->
| metric  | period | unit | value |
| ------- | ------ | ---- | ----- |
| Latency | Q1     | ms   | 120   |
compareEquality
exactNFC, then code points
foldedNFC, then lowercase
mention (default)folded, minus one leading @

CLI

mdv sources sync            # run ground producers into .mdv/ (generated, gitignored)
mdv sources status --check  # exit 1 if anything is absent or stale
mdv sources list            # every binding and where it came from
mdv sources doctor          # unbound refs, orphaned config keys
mdv check --source-snapshot capture.json  # record capture.json when missing, else replay it offline
mdv check --source team=team.json         # pin one name to a file for this run

A failed producer keeps the last good file. A capture pins the mediva version, the bindings and the contract hashes; when a contract or a binding has changed since, the replay cannot run: delete the capture and run again to re-record it.

Check new documents against current data; old ones drift. In CI the capture is an artifact of the run, not a committed file: a committed capture makes every later run replay stale data, with only a note when a source file's bytes changed.

- run: mdv sources sync
- run: mdv sources status --check
- run: mdv check --source-snapshot source-capture.json
- uses: actions/upload-artifact   # reproduce locally: mdv check --source-snapshot source-capture.json
  with: { name: source-capture, path: source-capture.json }

On this page