Context

A reference field says that two documents are related. The body is where a reader learns why, and nothing connected the two (#333). A citation in prose was always a mention, even when the sentence around it said “this builds on that”, and a relation in frontmatter could stand with nothing in the body explaining it. The issue asked for both directions: state a relation inline and have it pushed up into frontmatter, and have a declared relation pushed down into prose. It proposed an arrow after the link, [[X]]{--F-->here}, and left the notation open.

Luria already has one grammar for an author saying something to the tooling from inside prose: the comment directive (ADR-006, ADR-035), with its scopes, its — reason and its expiry (ADR-095). An acknowledgement is a claim about a citation (“this retired document is cited on purpose, because …”). A relation stated in prose is the same kind of thing (“this citation is the extends relation, because …”), so it gets the same grammar rather than a second one.

Decision

A relation is a directive in the ref:: namespace, named by the reference field that holds it in this document’s frontmatter:

The recovery path is LIT-007's, generalised. <!-- ref::extends: LIT-007 -->
<!-- ref::extended_by-block: LIT-012, LIT-015 — both carry the retry loop on -->

Any reference field works, whether declared in references: or the built-in successor. Nothing in the mechanism knows one relation’s name. Direction is the field’s name, so the other direction is written with the converse.

The directive grammar’s name pattern now admits a namespace:: prefix and _, because field names use it. The namespace is what separates a family of user-defined names from the fixed vocabulary. Every ref:: directive is a relation claim, so one naming a field the document does not hold is a finding, not a comment nothing reads. A field may take any name except one ending in -block or -file, which would read as a scope suffix, and luria.yaml refuses those.

[[extends::X]] (Semantic MediaWiki’s typed-link spelling) is shorthand for a citation plus its statement. The link fixer expands it to [X](X.md)<!-- ref::extends: X -->. A shorthand naming a field the document cannot hold is not expanded, and the wikilink lint fails on it.

  • Push up is mechanical. A statement missing from frontmatter is unrecorded-relations, and luria link --fix writes it. The converse completion (ADR-084) writes the far side in the same run.

  • Push down is opt-in per reference, at one of two strengths.

    • explain: true (or cited): a code the field holds is explained when the body cites it anywhere. The citation is taken to serve the relation. A code the body never cites is unexplained-relations, a report, because the explanation is prose only a person can write.
    • explain: stated: a code is explained only when a statement of it governs a citation of the code. A citation with no statement is unannotated-relations, and the fixer writes <!-- ref::F: X --> after the first citation.

    At either strength, a statement with a — reason counts as the explanation. The weak form is the default spelling because it is where the value is. On anthology-of-the-sota the strong form mostly added statements beside citations whose sentence already said what the relation was. What earned its keep were the relations the prose never mentioned, which prompted new explanatory prose, and the weak form finds exactly those.

  • A statement the record cannot hold as written is bad-annotations: a field the document does not hold, an argument that is not a code, a code naming no document here, a code in a scheme the field does not hold, a document naming itself, or a single-valued field that already holds another code.

All four are ordinary warning classes, so fail_on and mute reach them.

Consequences

  • A statement is invisible in every rendered view, like every other directive. The prose around the citation is what a reader sees, and the statement is what the tooling reads.
  • The acknowledgement and the statement are one directive. A statement’s reason is its explanation, so <!-- ref::cites-file: LIT-001 — the method section is its --> both states the relation and says it needs no more prose. No separate “unexplained-ok” is needed.
  • A citation can carry any number of statements, so two explained relations holding one code no longer compete for a single citation.
  • A misspelt field (<!-- ref::extnds: X -->) is reported, because the namespace says a relation was meant. A misspelt namespace (<!-- rf::extends: X -->) is still read as no directive at all, the same as a misspelt acknowledgement is today.
  • The issue’s last example has the fixer writing a whole citation into a body that had none. That was not done. Which sentence the citation belongs in is the non-deterministic part the same issue says must be a report.

Rejected

  • The arrow notation, [[X]]{--F-->here} / [[X]]{here--F-->}. It needs a direction the author has to get right, when a relation already carries its direction in its name. It also rendered as literal braces in every view.
  • The relation as a link title, [X](X.md "extends"). This was the second draft. It rendered well, but it was a second annotation grammar beside the directive one: no scope, no reason, no expiry. It also gave a meaning to a slot that markdown authors use for tooltips.
  • A directive named by the bare field, <!-- extends: LIT-007 -->. This was the third draft. The field names then shared a namespace with the fixed vocabulary, so a field could not be called pin or anything ending in -ok. A misspelt field was also indistinguishable from an unrelated comment, so it could only be ignored.
  • A separate acknowledgement for an unexplained relation. The statement’s own reason says the same thing in the same place.
  • One strength only. The strong form alone generates rote annotations. The weak form alone can’t ask for the relation to be named where a project wants that.
  • Making every declared reference owe prose. Every existing record would get one finding per edge on upgrade.