Require citations in the standard
You own the schema that says what a page must declare. A class of pages in your repository makes claims about code, and you want those claims pinned rather than trusted. This page makes that a rule of the standard. A reference page without a citation then fails the metadata gate, and a citation that stopped holding fails the citation gate.
Two tools do two halves of the work, and neither can do the other’s.
manni meta validate decides whether an entry is well formed and whether
a page has one at all. manni cite check decides whether the entry still
holds. The schema is where the first half is written down.
Every transcript below is real output from the built tool.
Put the vocabulary in the repository
Section titled “Put the vocabulary in the repository”The entry shape is already specified, as manni:citations:1.0.0-proposal.4.
Do not restate it in your own schema. Two definitions of one key is the drift
this composition avoids.
The vocabulary is not one of the built-in
schemas, and no published URL serves
it yet, because the draft is
open for review. So
manni meta schemas vendor, which downloads an http(s) URL, has nothing to
download. Copy
1.0.0-proposal.4.json
into your repository beside your own schemas, and commit it:
schema/citations-1.0.0-proposal.4.jsonschema/reference-page.schema.jsonRequire the key
Section titled “Require the key”Your schema carries the house rule and nothing the vocabulary already says:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://example.com/reference-page.schema.json", "title": "A reference page must carry at least one citation", "type": "object", "required": ["title", "citations"], "properties": { "title": { "type": "string", "minLength": 1 } }, "additionalProperties": true}citations is required, and nothing else about it is said here. The
vocabulary already requires a non-empty list, requires source on every
entry, pins the hash formats, and rejects a stray citation-* key at the page
root. The two ends lists every
field it constrains.
Wire both schemas to the class of pages
Section titled “Wire both schemas to the class of pages”A schema set applies each member whole, and reports the union of what they find, each finding tagged with the schema that produced it. A page passes only when it satisfies every member. That is how the two halves compose:
collections: - name: reference paths: ["docs/reference/**/*.md"]
meta: overrides: - collection: reference schemas: - ./schema/reference-page.schema.json - ./schema/citations-1.0.0-proposal.4.jsonAn override entry carries exactly one of collection: and files:. Use
files: "docs/reference/**" where the class of pages is not a collection you
already declared. Paths in config are relative to the config file. The
precedence chain says
which level wins, and validating against more than one
schema
says what a set does.
Three pages, three outcomes, one run:
manni meta validate docs/reference/✗ docs/reference/entry-invalid.md /citations/0/source must have required property 'integrity' (line 5) [./schema/citations-1.0.0-proposal.4.json] /citations warning "citations" is stored in the page; schema/citations-1.0.0-proposal.4.json prefers external metadata. Run manni meta relocate. (line 3) [location:external]⚠ docs/reference/limits.md /citations warning "citations" is stored in the page; schema/citations-1.0.0-proposal.4.json prefers external metadata. Run manni meta relocate. (line 3) [location:external]✗ docs/reference/retries.md (root) must have required property 'citations' (line 1) [./schema/reference-page.schema.json]
3 files checked, 1 passed, 2 failed, 2 errors, 2 warningsThe page with no citation fails under your schema. The page with a malformed
one fails under the vocabulary. Each finding names the file to open and the
schema that objected, so a contributor knows which rule they met. The two
warnings do not fail the run. They say the vocabulary prefers citations in
the collection’s manifest rather than the page, a choice
settled below.
Choose a severity per rule
Section titled “Choose a severity per rule”The schema says a page must carry a citation. It cannot say what should happen
when a citation stops holding, because that is a comparison against the files
as they are now. manni cite check decides it, and severity: is where your
policy goes:
cite: severity: claim-changed: error # every edit to a cited sentence gets reviewed source-moved: error # a moved pin blocks until update runs marker-repeated: off # not reported at allThe one worth deciding deliberately is claim-changed. It fires on any
edit to a pinned paragraph, a typo fix beside the cited sentence included. At
its default it is a warning, reported and green:
manni cite check docs/reference/limits.md⚠ docs/reference/limits.md ↕ fetch-timeout :15 changed lib/limits.ts:2 current
1 file checked, 1 passed, 0 failed, 1 finding (1 warning)With claim-changed: error the same run blocks the branch, and the person who
reworded the sentence has to confirm it still rests on the same line:
✗ docs/reference/limits.md ✗ fetch-timeout :15 changed lib/limits.ts:2 current
1 file checked, 0 passed, 1 failed, 1 findingChoose error where a cited sentence is a contract, as in a reference page a
support team quotes. Choose the default where the same repository carries
prose that gets copy-edited weekly. A red build on a typo fix teaches
contributors to stop citing. A team that wants the ratchet without the noise
sets error and turns on a baseline,
so only new findings fail.
| Level | Reported | Fails the run |
|---|---|---|
error |
yes | yes, exit 1, unless a baseline records it |
warning |
yes | no |
notice |
yes | no |
off |
no | no |
All fourteen rules, their defaults and the reasoning behind each default are in the configuration reference. A level the tool does not know, or a rule name it does not have, is an operational error naming the supported set.
Settle frontmatter against a sidecar
Section titled “Settle frontmatter against a sidecar”This is a policy decision, made once per collection, and it is yours rather than each author’s. The key, the schema and every rule are the same either way.
| What differs | Frontmatter | Sidecar manifest |
|---|---|---|
| Where the YAML sits | In each page, above the prose | In one file per collection |
| What an author opens | A page whose header is mostly hashes | A page with a title |
A git mv |
Moves the entries with the page | Orphans the entry, unless the manifest uses join: |
| Merge conflicts | On the page, between two people editing prose | On one file, between everyone adding citations |
| Review | The pin sits beside the sentence it pins | The pin is a second file in the diff |
| A public page citing private code | Publishes an entry, even when the path is encrypted | Publishes nothing on the page |
Pick the sidecar when the pages are written by people who should not have to read a hash, and when the sources are private. Pick frontmatter when a page carries one or two citations and reviewers want the pin in the same diff as the sentence.
Declare it on the collection, and say so in the standard, so the answer is not re-litigated per page:
collections: - name: reference paths: ["docs/reference/**/*.md"] externalMetadata: - file: ./reference-citations.yaml keys: [citations]What the schema still cannot say
Section titled “What the schema still cannot say”Three rules live in the tool rather than in JSON Schema, because a pattern cannot express them. Write them into the prose of your standard, so an author meets them before CI does.
-
Ids are unique within a page. The vocabulary says so in its description and cannot enforce it over a list whose members may omit the id. A duplicate is
entry-invalidat check time, on the second entry’s line:✗ fetch-timeout lib/limits.ts:2 current✗ duplicate id "fetch-timeout" (line 13) -
A range must not end before it starts.
lines: "9-3"passes the pattern and is refused byaddand bycheck. -
A pin has to hold. No schema can compare a hash with a file. That is the whole of what
manni cite checkadds, and it is why the citation gate runs beside the metadata gate rather than inside it.