A vocabulary is a shorthand for a tiny scheme: named values, each with a label and a blurb, and nothing else. Reach for this guide when a value needs to say more than that:
- it needs a parent or children, because the values form a hierarchy;
- it needs standing, because a value can be retired and replaced;
- it needs relations of its own, or a history worth keeping;
- you are about to ask for a richer vocabulary feature.
Each of these means the field is typed as the wrong kind of thing. The repair is to promote the vocabulary to a scheme: one document per value, and every field that drew from it becomes a reference (ADR-124). The reasoning is in Vocabularies.
The examples below start from this record:
vocabularies:
statuses:
Active: {}
Superseded: {}
area:
runtime:
label: Runtime
blurb: the execution engine
storage: {}
queues: {}
schemes:
RFC:
dir: record/rfcs.d
output: docs/rfcs
axis: area
fields:
status: {vocabulary: statuses}
area: {vocabulary: area, many: true}1. Write the migration spec
Promotion is a migration (ADR-040): it rewrites the config and documents mechanically, so it is planned, dry-run and committed like a scheme rename.
$ luria new migration --title "Areas become a scheme"
record/migrations.d/0001-areas-become-a-scheme.yamlUncomment the promote_vocabulary example, or replace the file with:
title: "Areas become a scheme"
issue: ""
operations:
- op: promote_vocabulary
vocabulary: area
to: AREAdir: and output: are optional and default from the prefix
(record/areas.d, docs/areas). The new scheme’s status uses the
statuses vocabulary when one is declared; name another with
status_vocabulary:, and its in-force word with active:.
2. Read the plan
$ luria migrate 0001 --dry-run
migration: Areas become a scheme
promote vocabulary area -> AREA (record/areas.d, docs/areas)
runtime -> AREA-001 (cited as AREA-runtime)
storage -> AREA-002 (cited as AREA-storage)
queues -> AREA-003 (cited as AREA-queues)
RFC.area: vocabulary -> grouped reference to AREA
would sweep 0 file(s)The values are numbered in the vocabulary’s order. An open vocabulary’s values that are in use but never declared come last, alphabetically: a reference is closed, so every value in use needs a document to point at.
3. Run it
$ luria migrate 0001 --commit
migrated: 0 move(s), 0 cop(y/ies), 2 rewrite(s) in 2 file(s)
promoted: vocabulary area -> scheme AREA, 3 document(s) filed, 1 field(s) now reference it
committed 1fa348f699a4 and appended it to .git-blame-ignore-revs
$ luria index
$ luria lintWhat changed:
-
record/areas.d/holds one document per value. A value’slabelbecame the title and itsblurbthe summary and body. Its old spelling is kept asslug:, declaredunique, and the scheme derives an alias from it (alias: AREA-{slug}, ADR-088):AREA-runtimenames AREA-001 anywhere a code would. A value an alias cannot spell (a space, an underscore) is slugged, solong_termbecomesAREA-long-term. The directory also gets the_template.mdandREADME.stuba declared scheme starts with, so the next term isluria new area. -
The field became a reference:
references: area: scheme: AREA many: true required: false group: truerequiredis written out because the defaults differ: a reference is required unless it says otherwise.group: true, or the field being the scheme’saxisas here, keeps the views: each term gets a page listing the RFCs that cite it, atdocs/rfcs/area/AREA-001.md. -
The documents hold each term’s alias:
area: [AREA-runtime, AREA-queues]. That is a typed reference the lint checks, and it still reads as the value did. Only that frontmatter field is rewritten; prose is never swept for a value’s spelling. New documents can cite either spelling, andluria link --fixleaves an alias as written. -
The
areavocabulary is gone. If it described itself (the nestedlabel/blurb/termsform), that description became the scheme’stitleandblurb.
4. Give the terms what the vocabulary could not
The promotion is the point of entry, not the point. A hierarchy, for example, is a converse pair on the new scheme and a chain to draw it:
schemes:
AREA:
references:
broader: {scheme: AREA, many: true, required: false, converse: narrower}
narrower: {scheme: AREA, many: true, required: false, converse: broader}
chains:
area-tree:
scheme: AREA
relation: broader
output: docs/area-tree.md
title: Areas, broadest first$ luria relate AREA-queues broader AREA-runtime
record/areas.d/AREA-003.md: broader += AREA-runtime
$ luria link --fix
wrote 1 back-reference(s) in 1 file(s)luria link --fix writes the converse, in the alias spelling:
narrower: [AREA-queues] on AREA-001.
List the chain page in docs/README.md, as every page in docs/ must be,
then luria index renders the tree:
## From Runtime
- [AREA-001](../record/areas.d/AREA-001.md) — Runtime *(Active)*
- [AREA-003](../record/areas.d/AREA-003.md) — queues *(Active)*A term with no broader or narrower edge is in no line, so storage
does not appear on the tree page; it still has its own document and its
page under docs/rfcs/area/.
What is refused
Promotion refuses rather than dropping what a reference cannot carry, so the record never silently checks less (DP-1):
- a vocabulary behind a
statusfield: standing is read off those words; - a field with a
default,groups, aderive, analert, or any other key a reference does not have; - a value declaring
primary_for; - a target prefix that already names a scheme;
- two values whose aliases would be the same spelling (
net_workandnet work), or a value that would read as a number (7→AREA-7is a code, and a code outranks an alias).
Each refusal names the field and the key. Remove it, or carry it into the new scheme by hand, then run the migration again.