Lexicon
A lexicon holds the words someone needs defined to talk about using or
developing the system — the domain's language, in the DDD sense. It exists so
that two people, or two documents, cannot mean different things by the same
word.
The code takes its names from the lexicon, never the other way round. A
module, a class, a thread or a mechanism is part of the code, and the code is
what you read to talk about it; its documentation lives beside it. A lexicon that
admits the code's parts turns into a second, drifting description of the code,
and stops being read.
Location
The lexicon lives wherever the project already keeps it. Scan for it:
docs/lexicon.md, docs/design/lexicon.md
docs/glossary.md, GLOSSARY.md, LEXICON.md
.claude/lexicon.md
Where none exists and one is needed, ask where it goes, with a concrete
suggestion drawn from where the project keeps its other design documentation.
The tests
A term earns an entry only by passing all three. Apply test 1, then check
whether the word is a synonym of a surviving entry (test 3's first rule), then
test 2, then the rest of test 3. The first test it fails decides its verdict.
1. It is language, not code
Does someone need the word to talk about what the system does, without
pointing at a particular part of the code? It is language if either holds:
- A user meets it — in the user documentation, the configuration, the
commands, the messages, the output — unless what the user meets is a
mechanism's name; then the third case below decides.
- Developers need it to talk about the system's behaviour across more than one
part of the code, and it would survive a rewrite of the code with a different
decomposition — a concept the whole design is reasoned in, such as
settlement in a payments system.
Fails this test: a module, class, component, service, layer, thread, queue,
file, data structure, or the name of a mechanism inside the build — including a
word that spans several parts only because the build is cut the way it is, such
as a hand-off between two components. The behaviour such a word names goes to
the concept it serves, usually a case of an existing entry, or to the decision
records. Also fails: the project's working vocabulary — its test doubles,
harnesses, tools and process — which belongs in its conventions.
Three cases need care:
- A concept and the component that realizes it. An order's fulfilment is a
concept anyone talking about a shop needs, however the code is cut; the
FulfilmentService that performs it is code. Keep the concept, not the
component.
- A mechanism and the guarantee it serves. A user relies on "a cancelled
order is never charged", not on the job that voids the charges. If the
guarantee needs a word, the entry is the guarantee's, under the domain's word
for it. The mechanism's name stays in the code.
- A mechanism's name the user documentation already uses. Documentation
that tells users about "the retry worker" is still telling them about a
routine. The entry is for what the user observes — a delivery that was
retried — if that needs a word at all, and the documentation's use of the
mechanism's name is drift to surface.
2. It needs defining
Does the word mean something here that its ordinary meaning does not carry?
At least one must hold:
- It is overloaded: someone using or developing the system would plausibly
apply another sense of the word to the same things — another sense in the
system, a platform class or message name a user meets, a data structure a
developer would expect behind the word (a queue that is not FIFO). A
collision the project itself records also counts: a decision record, a
convention, or a component named to dodge the word. An incidental
programming-language sense does not: a function's return value is no
overload of returning a parcel.
- It is narrower than ordinary use. "Pending means submitted and not yet
reviewed" — the domain fixed what the word covers. A word whose boundary the
system fixes is narrower even when the word is everyday: business day in a
payments system, where the system decides which days count. So is the name
of a quantity its users set and say, such as a cooling-off period or a
retention window: having a configuration key does not make it one.
- It names a distinction that must not be conflated. Near-synonyms that
must never be used for each other: archive vs. delete, user vs. member,
refund vs. reversal. These are the highest-value entries.
Fails this test: a word used in its ordinary sense, however central it is. Also a
word whose only specific content is a decision about how the thing behaves —
that is a decision record's content. For example, invoice in a billing
system: the ordinary sense covers it, and that invoices are numbered per year is
a decision, so it fails — unless the system distinguishes it from something a
reader would also call an invoice, such as a credit note; then the entry exists
for that distinction and names it.
3. It is the one word for its concept
Two entries for one concept are one entry. Merge them under the word the domain
uses, and say in that entry which word is not used. Shades of one concept are not
separate terms unless the difference must be preserved, in which case test 2
already holds for both and each entry names the other.
- Check for synonymy before retiring a word under test 2. A synonym of a
surviving entry is a merge, recorded there as a word not used, even if it would
also fail test 2 — otherwise nothing tells a reader it is not used.
- A redirect ("see X") is either a synonym, merged into X, or — if the
word passes tests 1 and 2 — a distinct term missing its entry, rewritten into
one.
- A headword the sources don't use is drift. If the code and every reference
agree on one other word, write the entry under their word, record the old one
as not used, and mark the rename for sign-off. If they disagree among
themselves, keep the headword and surface the split.
An entry
**term** — What it means, in the domain's words. Distinct from *other term*,
which ... See [ADR-NNNN](...).
- What the term means, in one to three sentences, in words a user or a new
developer already has.
- What it is distinct from, naming the other term, when the distinction is
why the entry exists — including any overload (test 2) the entry doesn't yet
state.
- At most one pointer, to the decision record whose decision defines what
the word means; where that part of it has been superseded, the superseding
record. Not every record that touched the concept.
- Other lexicon terms are italicized where used. Only terms the lexicon defines.
Not in an entry:
- How it is implemented: threads, types, API calls, which component does what.
- How it behaves, its edge cases, and what happens when: the decision records,
the references and the tests carry that. Except a clause that distinguishes
the term from a near-synonym — an archived item can be restored, a deleted one
cannot — which is meaning even when it reads as behaviour. Keep it.
- Configuration keys, verbs and their arguments: the references.
- Ticket numbers, and the history of how the meaning came about.
To tell meaning from behaviour: a clause that says how the term differs from a
neighbouring term is meaning. For any other clause, ask whether the word would
still pick out the same things if the clause were false; if so, it is
behaviour.
When you cut a condition or an exception, cut or qualify the universal it
qualified. Never leave every, never or only standing without its
exception.
An entry past about fifty words is almost always carrying code or a decision.
An entry changes only when the meaning changes. A new decision about the
concept goes into a decision record, and the entry is untouched unless that
decision changed what the word means.
Size
A domain's language usually comes to twenty or thirty terms. That is a symptom
check, not a quota: past it, the lexicon is probably admitting code or
decisions. Don't cut an entry that passes the tests to reach a number, and don't
keep one that fails them because there is room. A system that users meet through
several surfaces can honestly sit above thirty; say so rather than cutting
further.
Consolidating a lexicon
A lexicon accumulates and drifts however strictly entries are admitted. This is
the procedure for bringing one back. It changes the lexicon and nothing else.
Read the lexicon in full, then index where displaced content could live:
the decision records, the project's references, conventions, README and any
architecture overview, the tests, the code for any name an entry describes,
and the code's doc comments. Note the commit or date you audit, and cite
evidence at it.
Test every entry, in order. For each, record a verdict, the test that
decided it, and one line of reason:
- retire — fails test 1 or test 2, and nothing behind it needs a word of
its own. Say where the word belongs instead: the type or module it names,
the conventions, the existing entry that already carries the concept, or
nowhere.
- reframe — fails test 1, but the concept or guarantee behind it needs a
word the lexicon doesn't yet give it. If an existing entry is that word,
name it and carry over only meaning and distinction. Otherwise write a new
entry under the word the user documentation or the code already uses, and
list it in step 7 as a new entry awaiting sign-off — not also as a gap. It
is written into the lexicon with the rest; listing it is what lets the user
strike it. An entry for a component that exists, or was named, to avoid a
domain word is a reframe to that word.
- merge — fails test 3. Name the entry it merges into.
- rename — passes, but under a headword the sources don't use (test 3).
Name the new headword; it awaits sign-off.
- rewrite — passes, but is not in the shape above. Reduce it to meaning
and distinction, adding any overload it doesn't state, with where each
sense is met. Don't define a second term inside the entry where the sources
use that term differently; that is drift.
- keep — passes, is already in shape, names no component, and has no
unstated overload. Adding italics, or a "not used" note from a merge, does
not make a keep a rewrite.
Judge the term, not its current definition. A word that belongs can have an
entry that has turned into a description of code; that is a rewrite, not a
retirement.
Correct. Check every claim the shaped entries make against how the code
and the references use the word. Compare what is being cut with the home step
4 finds for it, and report a disagreement there as drift too.
- Where the code or the references split the term into cases — an enum, a
reported value, a kind of rule, a configuration option, a class of
trigger — check every claim against every case. A case the claim does not
fit is drift, not a finer grain.
- Check every universal — only, never, always, every, all, none,
cannot, entirely, and the like — against each reference that mentions
the word.
Surface each disagreement with its evidence, file:line as a search of the
file reports it — a citation that does not open to the quoted text is not
evidence — and let the user say which side is right. Don't settle it by
rewriting either side.
- A disputed claim that is the entry's meaning or distinction stays in the
entry, unchanged in substance, until the user answers — in words that
survive, never in a word retired under test 1 or merged as not used. A word
retired under test 2 is ordinary and free to use.
- A disputed claim about behaviour is not carried into the entry. Quote it in
the drift finding, name the sources on each side, and account for it in
step 4. Cutting it settles nothing: the lexicon simply no longer asserts it.
Account for everything cut, entry by entry: where the removed content
already lives. Search the decision records, README, tests and doc comments
for the sentence's substance, not its wording, before calling anything
homeless. List sentence by sentence only what has no other home: each is a
missing decision record, a missing doc comment or convention, or something
nobody needs. Cutting an entry must never be how a decision disappears. Don't
write the content elsewhere yourself.
Fix the edges.
- Inside the lexicon: cross-references to entries that were retired or
merged. Fix them.
- Outside it: code comments that cite the lexicon for a retired word or
define a surviving one through a retired one, and references and decision
records that use a retired word as if it were defined; and any convention
that tells people to add a lexicon entry for a code name. List them; don't
edit them.
- Gaps: a word the surviving entries or the references use in a
narrower sense than ordinary without defining it, which the audit turned
up — and a domain word some component was named to avoid, where that
component has no entry here (where it has one, step 2 reframes the entry
to the word). Propose these;
don't brainstorm. Where the file has no gaps line, present them only.
Hold a conversation in it. Write a handful of sentences someone would
actually say about the system — a user's bug report, a question about why
something happened, a design question, a review comment — in the consolidated
lexicon's words. Where one is ambiguous or misleading, fix the entry; where
the ambiguity is an open drift finding, name it instead. A lexicon is judged
by whether people can talk in it, not by how short it is.
Present, then write. Show:
- entries and words, before and after;
- the verdicts, grouped by verdict, with the test that decided each, and
any new entries and renames awaiting sign-off;
- the homeless content, the drift findings, the outside edges and the
proposed gaps;
- the consolidated lexicon in full.
The lexicon is shared vocabulary, so the user signs off before anything is
written. Then write it where it was:
- Keep its ordering and link style.
- Replace any statement in its header of what earns an entry or how entries
were admitted — including provenance or inference lines that give
admission reasons — with the three tests.
- A standing exclusion in the header (a class of words said to belong
elsewhere) goes too. Re-test the excluded words the surviving entries use,
and propose those that pass as gaps.
- New entries from reframes, and renames, are part of what was signed off:
write them. Only gaps wait.
- Put proposed gaps on the file's gaps line, if it has one, marked as not yet
admitted; a gap becomes an entry only when the user admits it.
- Update any provenance line to say it was consolidated, and when, and drop
retired terms from it.
1---2name: lexicon3description: House rules for a project's lexicon — the domain's ubiquitous language — and the procedure for pruning, consolidating and correcting one that has sprawled. Load before admitting, amending or retiring a lexicon or glossary entry in any repo; when asked to review, audit, prune, consolidate, clean up or correct a lexicon or glossary; and when a lexicon has grown past about thirty entries or its entries describe code rather than meaning. design-interview loads it for the admission tests. NOT for API reference documentation, code doc comments, or user manuals.4---56# Lexicon78A lexicon holds the words someone needs defined to talk about using or9developing the system — the domain's language, in the DDD sense. It exists so10that two people, or two documents, cannot mean different things by the same11word.1213**The code takes its names from the lexicon, never the other way round.** A14module, a class, a thread or a mechanism is part of the code, and the code is15what you read to talk about it; its documentation lives beside it. A lexicon that16admits the code's parts turns into a second, drifting description of the code,17and stops being read.1819## Location2021The lexicon lives wherever the project already keeps it. Scan for it:2223- `docs/lexicon.md`, `docs/design/lexicon.md`24- `docs/glossary.md`, `GLOSSARY.md`, `LEXICON.md`25- `.claude/lexicon.md`2627Where none exists and one is needed, ask where it goes, with a concrete28suggestion drawn from where the project keeps its other design documentation.2930## The tests3132A term earns an entry only by passing all three. Apply test 1, then check33whether the word is a synonym of a surviving entry (test 3's first rule), then34test 2, then the rest of test 3. The first test it fails decides its verdict.3536### 1. It is language, not code3738**Does someone need the word to talk about what the system does, without39pointing at a particular part of the code?** It is language if either holds:4041- **A user meets it** — in the user documentation, the configuration, the42 commands, the messages, the output — unless what the user meets is a43 mechanism's name; then the third case below decides.44- **Developers need it to talk about the system's behaviour across more than one45 part of the code, and it would survive a rewrite of the code with a different46 decomposition** — a concept the whole design is reasoned in, such as47 *settlement* in a payments system.4849Fails this test: a module, class, component, service, layer, thread, queue,50file, data structure, or the name of a mechanism inside the build — including a51word that spans several parts only because the build is cut the way it is, such52as a hand-off between two components. The behaviour such a word names goes to53the concept it serves, usually a case of an existing entry, or to the decision54records. Also fails: the project's working vocabulary — its test doubles,55harnesses, tools and process — which belongs in its conventions.5657Three cases need care:5859- **A concept and the component that realizes it.** An order's *fulfilment* is a60 concept anyone talking about a shop needs, however the code is cut; the61 `FulfilmentService` that performs it is code. Keep the concept, not the62 component.63- **A mechanism and the guarantee it serves.** A user relies on "a cancelled64 order is never charged", not on the job that voids the charges. If the65 guarantee needs a word, the entry is the guarantee's, under the domain's word66 for it. The mechanism's name stays in the code.67- **A mechanism's name the user documentation already uses.** Documentation68 that tells users about "the retry worker" is still telling them about a69 routine. The entry is for what the user observes — a delivery that was70 *retried* — if that needs a word at all, and the documentation's use of the71 mechanism's name is drift to surface.7273### 2. It needs defining7475**Does the word mean something here that its ordinary meaning does not carry?**76At least one must hold:7778- **It is overloaded**: someone using or developing the system would plausibly79 apply another sense of the word to the same things — another sense in the80 system, a platform class or message name a user meets, a data structure a81 developer would expect behind the word (a *queue* that is not FIFO). A82 collision the project itself records also counts: a decision record, a83 convention, or a component named to dodge the word. An incidental84 programming-language sense does not: a function's return value is no85 overload of returning a parcel.86- **It is narrower than ordinary use.** "Pending means submitted and not yet87 reviewed" — the domain fixed what the word covers. A word whose boundary the88 system fixes is narrower even when the word is everyday: *business day* in a89 payments system, where the system decides which days count. So is the name90 of a quantity its users set and say, such as a *cooling-off period* or a91 *retention window*: having a configuration key does not make it one.92- **It names a distinction that must not be conflated.** Near-synonyms that93 must never be used for each other: archive vs. delete, user vs. member,94 refund vs. reversal. These are the highest-value entries.9596Fails this test: a word used in its ordinary sense, however central it is. Also a97word whose only specific content is a decision about how the thing behaves —98that is a decision record's content. For example, *invoice* in a billing99system: the ordinary sense covers it, and that invoices are numbered per year is100a decision, so it fails — unless the system distinguishes it from something a101reader would also call an invoice, such as a *credit note*; then the entry exists102for that distinction and names it.103104### 3. It is the one word for its concept105106Two entries for one concept are one entry. Merge them under the word the domain107uses, and say in that entry which word is not used. Shades of one concept are not108separate terms unless the difference must be preserved, in which case test 2109already holds for both and each entry names the other.110111- **Check for synonymy before retiring a word under test 2.** A synonym of a112 surviving entry is a merge, recorded there as a word not used, even if it would113 also fail test 2 — otherwise nothing tells a reader it is not used.114- **A redirect** ("see *X*") is either a synonym, merged into *X*, or — if the115 word passes tests 1 and 2 — a distinct term missing its entry, rewritten into116 one.117- **A headword the sources don't use** is drift. If the code and every reference118 agree on one other word, write the entry under their word, record the old one119 as not used, and mark the rename for sign-off. If they disagree among120 themselves, keep the headword and surface the split.121122## An entry123124```markdown125**term** — What it means, in the domain's words. Distinct from *other term*,126which ... See [ADR-NNNN](...).127```128129- **What the term means**, in one to three sentences, in words a user or a new130 developer already has.131- **What it is distinct from**, naming the other term, when the distinction is132 why the entry exists — including any overload (test 2) the entry doesn't yet133 state.134- **At most one pointer**, to the decision record whose decision defines what135 the word means; where that part of it has been superseded, the superseding136 record. Not every record that touched the concept.137- Other lexicon terms are italicized where used. Only terms the lexicon defines.138139Not in an entry:140141- How it is implemented: threads, types, API calls, which component does what.142- How it behaves, its edge cases, and what happens when: the decision records,143 the references and the tests carry that. Except a clause that distinguishes144 the term from a near-synonym — an archived item can be restored, a deleted one145 cannot — which is meaning even when it reads as behaviour. Keep it.146- Configuration keys, verbs and their arguments: the references.147- Ticket numbers, and the history of how the meaning came about.148149To tell meaning from behaviour: a clause that says how the term differs from a150neighbouring term is meaning. For any other clause, ask whether the word would151still pick out the same things if the clause were false; if so, it is152behaviour.153154When you cut a condition or an exception, cut or qualify the universal it155qualified. Never leave *every*, *never* or *only* standing without its156exception.157158An entry past about fifty words is almost always carrying code or a decision.159160**An entry changes only when the meaning changes.** A new decision about the161concept goes into a decision record, and the entry is untouched unless that162decision changed what the word means.163164## Size165166A domain's language usually comes to twenty or thirty terms. That is a symptom167check, not a quota: past it, the lexicon is probably admitting code or168decisions. Don't cut an entry that passes the tests to reach a number, and don't169keep one that fails them because there is room. A system that users meet through170several surfaces can honestly sit above thirty; say so rather than cutting171further.172173## Consolidating a lexicon174175A lexicon accumulates and drifts however strictly entries are admitted. This is176the procedure for bringing one back. It changes the lexicon and nothing else.1771781. **Read the lexicon in full**, then index where displaced content could live:179 the decision records, the project's references, conventions, README and any180 architecture overview, the tests, the code for any name an entry describes,181 and the code's doc comments. Note the commit or date you audit, and cite182 evidence at it.1831842. **Test every entry, in order.** For each, record a verdict, the test that185 decided it, and one line of reason:186187 - **retire** — fails test 1 or test 2, and nothing behind it needs a word of188 its own. Say where the word belongs instead: the type or module it names,189 the conventions, the existing entry that already carries the concept, or190 nowhere.191 - **reframe** — fails test 1, but the concept or guarantee behind it needs a192 word the lexicon doesn't yet give it. If an existing entry is that word,193 name it and carry over only meaning and distinction. Otherwise write a new194 entry under the word the user documentation or the code already uses, and195 list it in step 7 as a new entry awaiting sign-off — not also as a gap. It196 is written into the lexicon with the rest; listing it is what lets the user197 strike it. An entry for a component that exists, or was named, to avoid a198 domain word is a reframe to that word.199 - **merge** — fails test 3. Name the entry it merges into.200 - **rename** — passes, but under a headword the sources don't use (test 3).201 Name the new headword; it awaits sign-off.202 - **rewrite** — passes, but is not in the shape above. Reduce it to meaning203 and distinction, adding any overload it doesn't state, with where each204 sense is met. Don't define a second term inside the entry where the sources205 use that term differently; that is drift.206 - **keep** — passes, is already in shape, names no component, and has no207 unstated overload. Adding italics, or a "not used" note from a merge, does208 not make a keep a rewrite.209210 Judge the term, not its current definition. A word that belongs can have an211 entry that has turned into a description of code; that is a rewrite, not a212 retirement.2132143. **Correct.** Check every claim the shaped entries make against how the code215 and the references use the word. Compare what is being cut with the home step216 4 finds for it, and report a disagreement there as drift too.217 - Where the code or the references split the term into cases — an enum, a218 reported value, a kind of rule, a configuration option, a class of219 trigger — check every claim against every case. A case the claim does not220 fit is drift, not a finer grain.221 - Check every universal — *only*, *never*, *always*, *every*, *all*, *none*,222 *cannot*, *entirely*, and the like — against each reference that mentions223 the word.224225 Surface each disagreement with its evidence, `file:line` as a search of the226 file reports it — a citation that does not open to the quoted text is not227 evidence — and let the user say which side is right. Don't settle it by228 rewriting either side.229 - A disputed claim that is the entry's meaning or distinction stays in the230 entry, unchanged in substance, until the user answers — in words that231 survive, never in a word retired under test 1 or merged as not used. A word232 retired under test 2 is ordinary and free to use.233 - A disputed claim about behaviour is not carried into the entry. Quote it in234 the drift finding, name the sources on each side, and account for it in235 step 4. Cutting it settles nothing: the lexicon simply no longer asserts it.2362374. **Account for everything cut**, entry by entry: where the removed content238 already lives. Search the decision records, README, tests and doc comments239 for the sentence's substance, not its wording, before calling anything240 homeless. List sentence by sentence only what has no other home: each is a241 missing decision record, a missing doc comment or convention, or something242 nobody needs. Cutting an entry must never be how a decision disappears. Don't243 write the content elsewhere yourself.2442455. **Fix the edges.**246 - **Inside the lexicon:** cross-references to entries that were retired or247 merged. Fix them.248 - **Outside it:** code comments that cite the lexicon for a retired word or249 define a surviving one through a retired one, and references and decision250 records that use a retired word as if it were defined; and any convention251 that tells people to add a lexicon entry for a code name. List them; don't252 edit them.253 - **Gaps:** a word the surviving entries or the references use in a254 narrower sense than ordinary without defining it, which the audit turned255 up — and a domain word some component was named to avoid, where that256 component has no entry here (where it has one, step 2 reframes the entry257 to the word). Propose these;258 don't brainstorm. Where the file has no gaps line, present them only.2592606. **Hold a conversation in it.** Write a handful of sentences someone would261 actually say about the system — a user's bug report, a question about why262 something happened, a design question, a review comment — in the consolidated263 lexicon's words. Where one is ambiguous or misleading, fix the entry; where264 the ambiguity is an open drift finding, name it instead. A lexicon is judged265 by whether people can talk in it, not by how short it is.2662677. **Present, then write.** Show:268 - entries and words, before and after;269 - the verdicts, grouped by verdict, with the test that decided each, and270 any new entries and renames awaiting sign-off;271 - the homeless content, the drift findings, the outside edges and the272 proposed gaps;273 - the consolidated lexicon in full.274275 The lexicon is shared vocabulary, so the user signs off before anything is276 written. Then write it where it was:277 - Keep its ordering and link style.278 - Replace any statement in its header of what earns an entry or how entries279 were admitted — including provenance or inference lines that give280 admission reasons — with the three tests.281 - A standing exclusion in the header (a class of words said to belong282 elsewhere) goes too. Re-test the excluded words the surviving entries use,283 and propose those that pass as gaps.284 - New entries from reframes, and renames, are part of what was signed off:285 write them. Only gaps wait.286 - Put proposed gaps on the file's gaps line, if it has one, marked as not yet287 admitted; a gap becomes an entry only when the user admits it.288 - Update any provenance line to say it was consolidated, and when, and drop289 retired terms from it.