DocsRecipes

Validate a status table

Enforce the columns, allowed values, required cells, and uniqueness of a Markdown table.

A status board that lets you type "sleeping" in the reactor's Status column is not a status board. It's a rumour.

Plenty of contracts live in a Markdown table: a systems board, an on-call roster, an API surface, a dependency inventory. mediva can lock the table's shape and its cell values, so a half-filled or made-up row never passes review.

The contract

systems-board.mdv.md
<!-- mdv: section required -->
## Systems Board

<!-- mdv: table required cols=[System, Owner, Status] minRows=1 noExtraCols col.System.unique col.Owner.required col.Status.oneOf=[nominal, degraded, offline] -->
| System | Owner | Status |
| ------ | ----- | ------ |
| Reactor | Dana | nominal |
| Life Support | Cole | nominal |
<!-- mdv: endtable -->
<!-- mdv: endsection -->

Every atom on that one table directive earns its place, and the enclosing section just needs to exist:

  • required (on the table leaf) — the Systems Board section must hold a table.
  • cols=[System, Owner, Status] — those columns must appear, in that order. They're matched as an ordered subsequence, so extra columns are tolerated unless you also lock the set.
  • noExtraCols — locks it: a stray Notes column is now [table-extra-columns].
  • minRows=1 — the board can't be empty (pair with maxRows=N to cap it).
  • col.System.unique — no two rows can name the same system.
  • col.Owner.required — every row needs an owner; an empty Owner cell fails.
  • col.Status.oneOf=[nominal, degraded, offline] — Status must be one of the three real states. "sleeping" is a rumour, and it's rejected.

What it catches

Point the contract at a board where someone fudged a row:

systems-board.md — every cell tells a small lie
## Systems Board

| System | Owner | Status | Notes |
| ------ | ----- | ------ | ----- |
| Reactor | Dana | sleeping | |
| Reactor |  | nominal | dup |
Form — edit the document

systems-board.md
  3  error  Unexpected table column  table-extra-columns
      The "Systems Board" table has an unexpected column "Notes".
      fix: Remove "Notes", or add it to cols=.

State — do the work, then record it

systems-board.md
  6  error  Duplicate table cell       table-cell-duplicate
      In the "Systems Board" table, column "System" repeats "Reactor".
      fix: This column is used as a unique identifier or distinct value. Work out what uniquely and truthfully identifies each affected row, then give each row that value.
  6  error  Required table cell empty  table-cell-required
      In the "Systems Board" table, column "Owner" is required but has an empty cell.
      fix: This column is required for every row. Look up the real value for this row, then fill the cell with it. If the value genuinely does not exist, resolve that outside the document first.
  5  error  Invalid table cell         table-cell-invalid
      In the "Systems Board" table, column "Status" has "sleeping", which is not allowed.
      fix: Use one of: nominal, degraded, offline.

contract: systems-board.mdv.md
    Restructuring on purpose? Update the contract in the same change so document and contract move together. One-off exception: <!-- mdv: disable-next-line <rule> -- reason -->

✖ 4 problems (4 errors, 0 warnings)

Four lies, four diagnostics, exit code 1. Fix the cells and the board passes clean.

The column constraints

Each col.<column>.<constraint> atom targets one named column:

ConstraintSyntaxChecks
requiredcol.Owner.requiredthe cell is non-empty in every row
oneOfcol.Status.oneOf=[open, done]the cell is one of the listed values
matchescol.Version.matches=/^\d+\.\d+\.\d+$/the cell matches a regex
uniquecol.ID.uniqueno value repeats down the column

Empty cells are skipped by oneOf, matches, and unique — only required objects to a blank. So you can demand a format without demanding a value: add both col.X.required and col.X.matches=/.../ when a cell must be present and well-formed. A constraint naming a column the table doesn't have raises a [table-unknown-column] warning, so a typo in the contract surfaces instead of silently passing.

Run it in CI

Keep the contract beside the file as systems-board.mdv.md and the zero-config scan picks it up, or check it explicitly:

npx mediva check systems-board.md --schema systems-board.mdv.md

Variations

  • For a roster where each person may own several systems, drop col.System.unique but keep col.Owner.required.
  • To allow a column the schema doesn't enumerate, omit noExtraColscols= still requires the named columns, but extras are tolerated.
  • Combine a table contract with sibling section headings in the same file: the table leaf is just one construct among the document's slots.

On this page