Skip to content

Write manni metadata from another tool

A link checker, a test runner or a sample runner learns something about a page each time it runs. When it writes that result into the page, every manni tool can read it. That holds only when the write follows the family’s rules. This page states those rules for a tool author, and for whoever wires the tool into a docs repository.

Each rule links to the reference that defines it. The vocabularies are the contract, and this page says how to honor them.

  1. Read the vocabulary first. Field names, types and marks live in the schema, not in this page. manni meta schemas lists every built-in, and manni meta schemas show <id> prints one with each field’s type and description. Each field also carries an x-manni-location mark and, where it matters, an x-manni-graph-output mark.
  2. Write a field where it lives. A page field goes in the page’s front matter. An external field goes in the manifest the collection’s externalMetadata names. A tool that cannot read manni.config.yaml writes page fields only. It leaves external ones to manni meta relocate.
  3. Attribute what you write. A measured fact goes in its field’s companion, such as verified-by beside last-verified, or reviewed-by beside last-reviewed. A value proposed for a person to review goes in a meta-provenance entry, with generated-by and the field pointers. Never write provenance, which meta derive manages.
  4. Never write a derived or managed field. manni meta derive manages created and last-updated. manni cite update manages a citation’s claim, source.integrity and source.commit-sha. A second writer for any of them is a value two tools fight over.
  5. Write dates as UTC days, or as RFC 3339 with a timezone. 2026-10-21 and 2026-10-21T14:02:11Z both pass. Never write a local time with no offset, and never a month alone.
  6. Write on success only. A failed check leaves its value alone. The age of the last good value is the honest signal, and a reader sees it.
  7. Write without churn. Write only when a value changes, so a second run leaves the file as it was. Keep the block’s YAML style and key order, and add a new key at the end of its block. A value that would differ on two branches for the same content is not written per branch. That is the rule merge-safe derivation follows too.
  8. Validate after writing. Run manni meta validate <file> in the same job, and fail the job on exit 1. Validation checks the form of every value the vocabularies define.
  9. Write a pin’s result on its citation entry. A test that runs the cited code records verified-by and last-verified on that entry, as the citations vocabulary defines them. It never touches the entry’s claim or source.
  10. Follow the worked example. Doc Detective’s write-back, below, applies every rule on this list to one page.
  11. Write a fact about one block as a statement. A meta statement sits above the block it describes, in the page’s own comment syntax. Change only your own entry in it. Pin the block by the hashing rule, with pinOf from @hawkeyexl/manni or the same rule in your own language. Before hashing, cut every statement inside the block’s lines out of the text, as manni does. That covers the statement you write and any other one, such as a step’s own statement inside a whole procedure. A <data name="manni-meta"> carrier goes whole, from its start tag to its end tag. Then drop each line the cut left blank, and keep the lines that were blank already. Keep the statement’s comment form, so the write is a one-line diff. Write it on success only, as rule 6 says.

The stewardship vocabulary defines two fields for a check’s result. verified-by lists the checks that verify a thing. last-verified is the date by which every one of them had passed. They go in a statement on the block a check verifies, or in front matter when the check covers the whole page.

The author declares the check in a statement above the procedure. The entry names the tool, and the test id the tool looks for:

docs/install.md
## Install
<!-- meta {"verified-by":[{"tool":"doc-detective","check":"install-steps"}]} -->
1. Run `npm install --save-dev @hawkeyexl/manni`.
2. Run `npx manni meta validate docs/`.

CI runs the tests, and the test passes. Doc Detective finds the statement whose entry names it, and fills in that entry. It sets last-verified, since every listed check has now passed. Then it pins the two steps it proved and writes the line back in its own form:

<!-- meta {"verified-by":[{"tool":"doc-detective","check":"install-steps"}]} -->
<!-- meta sha256-384b6729be777c15e9e55ae3f0a0fb1fbe63793abf601e5f517702e9019966e3 {"verified-by":[{"tool":"doc-detective","check":"install-steps","version":"4.27.0","passed":"2026-10-21"}],"last-verified":"2026-10-21"} -->
1. Run `npm install --save-dev @hawkeyexl/manni`.
2. Run `npx manni meta validate docs/`.

Each rule shows in that diff. Both fields are page fields, so they stay in the page (rule 2). The tool’s version and the date sit in verified-by, the companion of last-verified (rule 3). The date is a UTC day (rule 5). A failing run writes nothing (rule 6). New keys go at the end of their objects, and a second passing run on the same day changes nothing (rule 7). The pin hashes the two steps by the hashing rule, and the comment form is unchanged (rule 11).

The job then validates the page (rule 8), and any manni tool reads the statement back:

Terminal window
manni meta validate docs/install.md
manni meta get title docs/install.md
✓ docs/install.md
1 file checked, 1 passed, 0 failed, 0 errors
docs/install.md: title=Install manni (asserted)
docs/install.md: statement at line 9 (lines 10-11, current) {"verified-by":[{"tool":"doc-detective","check":"install-steps","version":"4.27.0","passed":"2026-10-21"}],"last-verified":"2026-10-21"}

A later edit to either step makes validate warn statement-changed, until the next passing run pins the new text. A procedure on the same page with no statement claims nothing.

When the check covers the whole page, the same entry goes in front matter:

---
title: Set up cite
description: "Pin your first sentence to its source and gate it in CI."
last-updated: 2026-10-17
verified-by:
- tool: doc-detective
check: cite-set-up
---

On a pass, Doc Detective writes the same values into that block:

last-updated: 2026-10-17
verified-by:
- tool: doc-detective
check: cite-set-up
version: 4.27.0
passed: 2026-10-21
last-verified: 2026-10-21

last-updated is untouched, since derive owns it (rule 4). The new keys go at the end of their blocks (rule 7). test/fixtures/verification/one-entry.md holds that page after the write. The job then validates it (rule 8):

Terminal window
manni meta validate test/fixtures/verification/one-entry.md
✓ test/fixtures/verification/one-entry.md
1 file checked, 1 passed, 0 failed, 0 errors

Any manni tool reads the values back as written:

Terminal window
manni meta get test/fixtures/verification/one-entry.md --fields last-verified,verified-by
test/fixtures/verification/one-entry.md: last-verified=2026-10-21 (asserted)
test/fixtures/verification/one-entry.md: verified-by=[{"tool":"doc-detective","check":"cite-set-up","version":"4.27.0","passed":"2026-10-21"}] (asserted)

A tool that writes a local time with no offset fails at that line, and exits 1. The guidance line quotes the date rule:

Terminal window
manni meta validate test/fixtures/verification/bad-no-timezone.md
✗ test/fixtures/verification/bad-no-timezone.md
/verified-by/0/passed must match pattern "^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])(?:T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:\.[0-9]+)?(?:Z|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9]))?$" (line 8) [manni:stewardship:1.2.0]
manni:stewardship:1.2.0 verified-by/passed: A day such as 2026-10-21, or a timestamp with a timezone such as 2026-10-21T14:02:11Z.
Guide: https://hawkeyexl.github.io/manni/meta/reference/schemas/manni-stewardship/
1 file checked, 0 passed, 1 failed, 1 error

Each tool gets an entry of its own, and writes only that entry. A link checker added beside Doc Detective looks like this:

verified-by:
- tool: doc-detective
check: cite-set-up
version: 4.27.0
passed: 2026-10-21
- tool: lychee

An entry with no check means the tool verifies the whole page however it does. When the link check passes on 2026-10-22, its writer sets passed: 2026-10-22 on its own entry. last-verified stays 2026-10-21, the oldest pass. That is the day by which every listed check had passed, so the date never claims more than the checks proved.

manni writes neither field. It defines them, validates their form, and carries them into a graph. Record when a page was last verified shows the same flow from the author’s side.