Context
The logic-core migration (ADR-127) put a fact interface between the Python that reads the record and the rules that reason over it. That boundary exposed relations whose meaning came from code rather than configuration:
contract.built_in()synthesized a reference named bysuccessor(defaultsuperseded_by) into every scheme, into any scheme or remote, required whileretires_on(defaultSuperseded) and forbidden whileactive(ADR-071, ADR-125).influenced_bywas read as a list of codes on every scheme byedges.py,relate.py, the site pages,luria newand the index’s “shaped by” line (ADR-012).relations.lpcarriedfar(S, F, S) :- doc(C, S), ref_value(C, F, _), not ref(S, F).—document_facts()emitted reference values from the compiled contract,schema_facts()described onlyscheme.references, and the rule reconstructed the missing type from an absence.
ADR-085 already named the pattern: a built-in is a declaration nobody wrote. The facts boundary made it a second implementation as well.
Decision
Configuration is the only source of relation semantics. Any frontmatter field that participates as a typed relation is declared under schemes.<S>.references.
- Nothing is synthesized.
contract.built_in(),Field.builtinandANY_SCHEMEare gone. A scheme’s contract is exactly whatluria.yamldeclares. - Targets are concrete. A reference names the schemes its codes may belong to and, new here, the declared remotes (
scheme: [LIT, ARXIV]) — so a paper superseded by a paper is said in the config. There is no “any scheme” target. - Roles point; they do not declare.
successornames a declared reference,retires_ona word in the scheme’s status vocabulary, andinfluence(new) the declared reference the index renders as “shaped by”. All three are unset unless set, and config load refuses one naming something undeclared. - The facts are a complete interface. Every
ref_value(C, F, X)hasref/2andref_target/3for its field, because every reference field comes from a declaration.relations.lpderives targets fromref_target/3alone; the onlynot ref(...)left in the rules are consistency findings, where a missing declaration is the error. - Migration, not compatibility.
luria upgrade explicit-relationswrites down what older versions supplied: the roles, the successor reference with its old conditions where a scheme used it or its vocabulary holds the retiring word,influenced_bywhere documents use it, each with the narrowest target set its values support. It refuses a value naming no declared scheme or remote, keeps any declaration already written, and loads the result before writing it. Nothing at run time reads the old shape.
DEFAULTS and the luria init scaffold declare their relations explicitly. They are conventional defaults, written in configuration where a reader of the configuration reference sees them.
Audit of hard-coded field names
Every frontmatter field name generic code spells, classified:
| Name | Class | Outcome |
|---|---|---|
influenced_by | historical | Removed from edges, relate, site_page, new and the index; a declared reference, named by the influence role where the index should render it. |
superseded_by | role | Read through scheme.successor everywhere. statuses.set_status stripped a literal superseded_by: block while writing the scheme’s own field — a renamed successor got a duplicate key; fixed. migrate’s tombstone and statuses.repair now use the scheme’s retires_on and successor. |
Superseded (status word) | role | Read through scheme.retires_on; the literal in statuses.repair is gone. |
tags | role | scheme.axis. The fallback document and the generic _template.md write the axis field, or none; promote’s workaround that stripped the seeded tags: is gone. The configuration reference no longer lists tags as a fixed name. Quartz’s own tags key in site_page is Quartz’s, not ours. |
status | primitive | The field every document’s standing lives in; its words are the scheme’s vocabulary, and active is a role. |
title, date, version, history, number, formerly, created | primitive | Identity, ordering and history every document has by design. |
summary, origin, status_note | primitive | Prose keys: the index and site render them, which is what makes them prose (ADR-051). |
Alternatives considered
- Keep the built-ins, emit them as facts. It closes the facts gap but keeps two sources of truth: a reader of
luria.yamlwould still not see the relations the record has. - Keep an explicit
scheme: "*"target. No record needs it once remotes can be targets: every successor in nucleation, the anthology, the examples and this record names a declared scheme or remote. - Default the roles to the old names and accept them undeclared. That is the built-in again, one level up.
Consequences
- A record on an older version fails to load until it runs
luria upgrade explicit-relations, by design. nucleation and the anthology need that run; the migration was exercised on copies of both. - The record page states each scheme’s successor obligation under the scheme that declares it, rather than once as a standard rule.
- The site’s backlink for
influenced_byreadsCited as “influenced by” by, like any declared reference, instead of a hard-coded “Influenced”. - ADR-071’s “standard field” clause no longer holds; its substance — succession is a typed edge read from a field — stands.