Context
Two features shipped in 0.25.0 and had never been used together.
alert (#273) is the sentence a vocabulary prints when a closed-set
violation fires — “closed so every tag is one somebody chose, not because
the list is finished”. label and blurb (#279) are what the vocabulary
is at rest, and #279 put them in the central vocabularies: table, on
the argument ADR-098 had already settled: a set two schemes share is declared
once so the copies cannot drift.
alert was left on the field. That was not a decision; it is where the
Vocabulary object happens to be built, and #273’s own docstring says the
opposite — “attached to the vocabulary rather than the field so it rides the
one rule it describes”.
The first record to adopt both found it. Writing the alert where the blurb
goes was refused with a message about a value named values, because the
nested form’s discriminator was spelled inline:
set(table) <= {"label", "blurb", "values"}
alert is not in that set. The message named three keys while the code
allowed three and the dataclass carried four, and the failure it produced
described a completely different fault.
Decision
alert is read from the central table, beside label and blurb. A
vocabulary three schemes name carries one alert.
The key set is read off a dataclass that mirrors the table.
VocabularyTable declares label, blurb, alert and the key holding the
values; KEYS is {f.name for f in fields(VocabularyTable)}, and both the
discriminator and the refusal message read it. _vocabulary_tables builds one
and reads its attributes, so the schema is load-bearing rather than
decorative and a fifth key reaches all three places in one edit.
The values live under terms:, not values:. A controlled vocabulary has
terms; values was the commonest word for the things a vocabulary’s own
entries are called, which is exactly why a project naming one of them values
was plausible enough to need a refusal. Renaming does not remove the
ambiguity — a vocabulary could name a term terms — but it moves the
collision from a word every such table is about to one that is merely
possible. 0.25.0 is the only release carrying the old spelling, it is hours
old, and the sole record using it is on an unmerged branch, so this is a hard
rename with no alias.
A TagGroup’s alert stays where it is. A group is declared inline, under
the field whose values it constrains, and there is no central table for it to
move to — the two are not inconsistent, they are different shapes of thing.
Alternatives considered
- Add
alertto the inline key set and leave it on the field. One character of the bug fixed and none of the cause. A vocabulary three schemes name would still need the sentence three times, which is the exact drift ADR-098 centralised vocabularies to prevent and #279 cited when it movedblurb. - Honour both spellings, field-level overriding the set. Two homes for one fact, which is what ADR-089 argues against upstream and what this project keeps deciding against. Nothing has released a record using the field-level spelling — 0.25.0 is hours old — so there is nothing to keep working.
- A named constant,
VOCABULARY_KEYS = frozenset({...}). What this PR shipped first, and what review rejected as inelegant — correctly. The argument for it was thatVocabularyalso carriesmany,required,closed,defaultandrequired_when, all declared per field, so the keys a table may carry are not the fields that object has. That is true and it is an argument against deriving fromVocabulary, not against deriving at all: the answer is a second dataclass shaped like the table. Writing the set down by hand was the thing the ADR’s own opening paragraph says not to do. OmegaConf.structuredfor the table, which omegaconf being already a dependency makes plausible. It works — merging a flat table against a nested schema raisesConfigKeyError, so try/except discriminates — and it is still the wrong trade here. It makes exception handling the control flow for an expected, common case; it replaces a message this project wrote and tested with omegaconf’s; andconfig.pyhand-parses every other object it builds, so one structured table is a local inconsistency rather than a direction. Adopting structured configs across the whole loader is a real question and is filed as its own, not settled by a four-key table.- Status quo. The composition stays broken, and the message sends the
next person looking for a value named
values.
Consequences
The example in test_a_closed_vocabulary_can_print_its_own_advice declares
its alert on the set now, which is also the first exercise of the nested form
outside the tests written for it.
Fired on the real case, as the working agreement asks. anthology-of-the-sota
declares topics with all three keys; the config loads, one declaration
reaches SOTA, LIT and THEORY, and a bad tag prints:
record/practices.d/SOTA-001.md: `tags: kv-cache-paging` is not in the
`topics` vocabulary — the values are training-optimization, …
↳ Closed so that every tag is one somebody chose and blurbed, NOT
because the list is finished. If your document wants a word this
vocabulary cannot say, add it — …
That is #273’s whole purpose, reaching a real reader for the first time.
Left open: nothing here checks that a vocabulary’s alert is only meaningful
where some field declares closed: true. An alert on an open vocabulary is
inert rather than wrong, and a finding for it would want to know which of the
naming fields is closed — worth a look if anyone writes one.