Skip to content

manni:citations:1.0.0-proposal.4

The question it answers: what do this page’s claims rest on? A common vocabulary for pinning a sentence to the source lines it was written from. Any drift check can implement it. Hash the cited lines the same way, compare against the pin, and report the sentence that stopped being true.

One page-level key, citations. The citation- prefix is reserved, so any citation-* key at the page root is rejected.

Key Type Location Notes
citations non-empty list of entries external One entry per claim or pin. A page with no citations omits the key

The Location column is the x-manni-location mark the key carries in manni:citations:1.0.0-proposal.4. page means the value belongs in the document’s own metadata and is meant to reach delivered output. external means it belongs in the collection’s external-metadata manifest. A key is page when a third-party or user-owned agent fetching the page would act on it. Citations are CI’s: a drift check reads them, and an agent reading the page does not. The fields inside an entry carry no mark of their own. See field location for how the marks are used.

An entry is a closed object with one required block, source. The optional claim block is the other end, and both ends carry a line range and a hash:

Field Type Notes
id kebab-case, unique per page What a body marker names, and what a finding is reported under. Required when a marker names the entry
claim.lines L or "L1-L2" Lines of the page body, counted after the frontmatter, so editing the frontmatter never moves a claim. Required unless a marker names the entry
claim.integrity sha256- and 64 hex digits The claimed lines hashed under the one rule. Always plain, because the page is public
source.file source file A repo-root-relative posix path. In place of the path, ~ and at least 82 base64url characters is an encrypted source: the path, encrypted under the family encryption key
source.lines L or "L1-L2" File lines, 1-based and inclusive. Absent, the whole file is pinned. Readable even when file is encrypted
source.integrity sha256- or hmac-sha256-, and 64 hex digits The pin over the cited lines. hmac-sha256- exactly when file is encrypted
source.commit-sha 7 to 64 hex digits The commit the source pin was taken at. Tooling writes the full hash
quote boolean, default false The claim is a fenced block that reproduces the source lines

An entry with no claim and no marker is a bare pin, checked source-side only. The source.file grammar rejects an absolute path, a drive letter, a backslash, a . or .. segment, an empty segment and a URL. So a source that cannot be resolved fails at the schema. Spaces and dots inside a segment are fine: docs/release notes/v1.2.md is a file. That the end line is not before the start line is a rule on implementations, since a pattern cannot compare two numbers.

The hashing rule is stated once, in the schema’s source.integrity description, and applies to both ends. Decode as UTF-8, drop one leading byte-order mark, and turn CRLF into LF. Split on LF and discard the empty element a trailing LF leaves. Take the cited lines, join them with LF and no trailing LF, and keep trailing whitespace. A plain file and every claim are pinned sha256- over that text. An encrypted file is pinned hmac-sha256-, the HMAC-SHA256 of that text under a key derived from the family encryption key. Proposal 0045 states the construction of both the ciphertext and the keyed pin.

A citation can be anchored from the body by a marker. A marker is one comment naming an entry by id, written in the format’s own comment syntax. It anchors the rest of its own line when that carries text, else the paragraph that follows it. With quote: true it anchors the fenced block that follows it. The payload is an id and nothing else. The entry itself lives in the frontmatter or a sidecar manifest, never in the body.

Format Forms
markdown, mdx <!-- cite ID -->, {/* cite ID */}, [comment]: # (cite ID)
html, xml <!-- cite ID -->
asciidoc // (cite ID)
rst .. (cite ID)

A marker naming no entry is a finding, and so are two markers naming the same id. Metadata validation sees the entries only; an implementer of the drift check reads the markers itself.

One sentence on body line 3, pinned to one source line:

citations:
- id: fetch-timeout
claim:
lines: 3
integrity: sha256-c41f09aa8ba1a04a3d5a0a0dbb1d9c1e8e9e26a1c5f0b2dd4e9a0c17f4f19aab
source:
file: src/limits.ts
lines: 2
integrity: sha256-78af1d3321f9cbb177a7e4c958e39be56fd14cb93c1e441778bc4232e0fe4b1f
commit-sha: 3f9c2a1e7b0d4c5a6f8e9d0b1a2c3d4e5f607182

A bare pin says the page rests on a whole file:

citations:
- source:
file: src/limits.ts
integrity: sha256-…

A marker anchors an entry to a paragraph, in markdown and in MDX:

<!-- cite retries -->
Retries default to 3, and the count is not configurable.
{/* cite retries */}
Retries default to 3, and the count is not configurable.

An encrypted entry, citing a private source from a public page. The ciphertext replaces the path, and the pin is keyed by the same family encryption key. So the page carries neither the path nor a verifier for the line:

citations:
- id: retries
quote: true
source:
file: "~AQ…"
lines: 1-3
integrity: hmac-sha256-…
  • A pin is a record of the past. The family’s fourth principle says a derivable fact lies, and a hash of lines that sit right there looks derivable. It is not. The pin says what the lines were when the sentence was written; the check is the comparison between that record and the file now. Recompute it on every run and there is nothing to compare against. It is the same object as evals’ generated-assertion-hash and the integrity pins on a config’s schema entries.
  • integrity, with an algorithm prefix. Named after what it is rather than how it is computed, as the config’s schema pins and Subresource Integrity are. So a second algorithm can arrive without a second field. Hex rather than base64, because the family’s other hash is hex.
  • One normalization rule, up front. CRLF and a byte-order mark are not changes. A check that runs on every push cannot afford a page of false findings on one platform. Trailing whitespace is kept, because a trailing space is a change.
  • An encrypted path, under the family’s key. A checkout with the family encryption key and the files can decrypt the path and read the lines, so it needs no second secret. The encryption is deterministic, so one path under one key always gives one ciphertext. A public site therefore does reveal how often a private file is cited; that is recorded as accepted.
  • A keyed pin for an encrypted source. A plain hash over a short private line is a verifier a reader can run against guesses. A keyed pin closes that, at the cost that only a checkout with the key can check the pin. It reads hmac-sha256-, so the prefix says what the value is.
  • The claim is pinned, not copied. A claim is a line range and a hash, the same shape as the source end. A pinned range cannot be ambiguous on the page it was taken from, and a copied sentence could be.
  • Entries in one place, markers as anchors. An entry lives in the frontmatter or a sidecar manifest, and a marker only names it. So a source is never written into the body, and a public page never carries a private path twice.
  • A closed entry and a guarded prefix. The entry rejects unknown fields, so line: 2 beside file fails instead of being ignored. The page root stays open for sibling vocabularies, and the citation- prefix is reserved so a misspelled citation-comit fails the way a closed container would make it.

Three questions. Is one hash function in the integrity pattern too tight for a draft, given that adding one is a schema revision? Should the ciphertext differ from page to page? The cost is a drift check that no longer recognises one private file across two pages. And are the parenthesised marker forms worth keeping?