Type to search pages and rules.

Browse pages

Domain Model

The domain states how the product works: the words it uses, the areas it divides into, the things it keeps consistent, the rules that hold, and the behavior it defines with its endings and examples. This page states the domain record types, the fields and links each one carries, and how each record maps to its design pattern in technology-free words. Developers and agents write these records in the domain layer's words; reviewers read them in the book down to the scenario line.

Markdown21 provisions

Concepts#

Domain Record Types#

Each type answers one question about how the product works. Every record carries the common fields, and every identifier follows the grammar, that the model page defines. Entries inherit owner and status from their page, as the model page states.

TypeStored asFieldsLinks
termentry in docs/glossary.jsonterm, meaning, labels, rejected, broader, narrower, valuesnone
modulepage, docs/domain/modules/<module>/README.mdsummarydependsOn
aggregatepage, docs/domain/modules/<module>/<aggregate>/README.mdsummary, states, transitions, datacontainment by identifier; rule and event entries
ruleentry on its aggregate page; cross-aggregate rules on a rule pagekind, statementgoverns, refusal, fulfills, basedOn
evententry on its aggregate pagemeaning, carriesnone
use casepage beside its aggregatekind, anonymous, input, output, trigger on a reactionneeds, reachedThrough, raises, fulfills
resultentry on its use case pageoutcome, code on a refusal, meaningcontainment by identifier
scenarioentry on its use case pagegiven, when, then, examplesresult

Where the domain lives follows one order: module, then aggregate with its states, transitions, and data, then its rules, events, and use cases. The model page states the authoring order, the authority order, and how the builder verifies each record.

The Bounded Context#

The product speaks one business language inside one deployment boundary, as the scope page states. Modules organize that language into areas; they never become languages or deployments of their own. Words follow the layer's author: plain words in the product layer, and the established design words of domain-driven design, CQRS, and BDD here.

The Glossary#

The glossary is the project's ubiquitous language: the one word or phrase the project uses for each concept, with its meaning. Each term is an entry in docs/glossary.json with its term, meaning, labels, rejected, broader, narrower, and values fields, and no links. Any record names the terms it uses in terms, as the model page states. The authoring page states how the checks apply rejections and mannered terms.

Modules and Aggregates#

A module is one business area: a page at docs/domain/modules/<module>/README.md carrying a summary and the dependsOn links to the modules it relies on. An aggregate is one thing the product keeps consistent and changes in one step, through its root: a page at docs/domain/modules/<module>/<aggregate>/README.md beside its module page. The aggregate identifier carries both names, so containment follows the identifier and needs no link field.

States Transitions and Data#

Each aggregate declares its lifecycle as states entries with a name and a meaning, so every state a reviewer reads carries what it means. Its Transitions table lists every allowed change with the use case that moves it, naming the state it leaves and the state it enters. Its Data table lists the fields the aggregate keeps, each with a name, a meaning, and a classification of public, internal, personal, or sensitive. The model page states how the builder computes reachability and coverage from these tables.

Rules and Events#

A rule states what holds, with its kind and its statement. An invariant holds after every change; a precondition guards the behavior before it runs; a calculation derives a value; a cross-aggregate rule spans aggregates and lives on a rule page at docs/domain/rules/<name>.md, while each other rule lives as an entry on its aggregate page. A rule names what it constrains in governs, the refusal its breach produces in refusal (the result a caller sees, or a bare refusal code for a guard no caller reaches because an earlier check refuses first), the requirements it carries in fulfills, and the constraints and research behind it in basedOn. The product page states requirements.

An event is an immutable statement that a business fact completed, named in the past tense, with its meaning and the facts it carries. Each event is raised through the root of its aggregate, and each event stays inside the product: published events belong to the API compatibility extension.

Use Cases Results and Scenarios#

A use case is one independently testable goal: a page beside its aggregate at docs/domain/modules/<module>/<aggregate>/<use-case>.md, with one kind. A command changes the product; a query reads without changing and declares its output; a reaction answers events or a moment in time and names its trigger: the events it answers, or the schedule or deadline in words. Each use case carries its input, names the permissions it needs in needs, the entry points that reach it in reachedThrough, the events it raises in raises, and the requirements it carries in fulfills; a use case open to all sets anonymous. The access page states actors, roles, permissions, and entry points.

Each use case ends in named results: entries with an outcome of success or refusal, a code on a refusal, and a meaning. A refusal code is a stable identifier such as spending.receipt-missing, shared by every layer that reports it. Each scenario proves one ending: an entry with given, when, then, an optional examples table, and the result it ends in. Scenarios are the leaf of the review: the last records a person reads, and the first a test proves, as the model page states.

Spec to Design Mapping#

The specification names only what carries business meaning. Child entities and value objects are design choices inside an aggregate: they appear in code and the glossary, never as records. Each record maps to one design pattern; the stack pages state the code form of each pattern, and the model page states how the builder links records to code by naming convention.

Spec recordDesign pattern
moduleone module in the bounded context
aggregateone aggregate, changed through its root
aggregate statesthe lifecycle of the aggregate
(none)child entity, changed only through the root
(none)value object, compared by value
rule, invariantan invariant checked on every change
rule, preconditiona guard the behavior passes before it runs
rule, calculationa pure computation on the aggregate or its values
rule, cross-aggregatea stored constraint, or a reaction reaching eventual consistency
eventa domain event raised through the root
use case, command or queryone handler behind the use case
use case, reactiona policy answering events or time
resultone ending per result, each refusal with its code
scenarioan executable example proving the scenario

Standards#

Define Every Domain Term in the Glossary#

ruleguidance
litespec/core/domain#define-every-domain-term-in-the-glossary
Statement

Each term a domain record names MUST have a glossary entry stating its meaning.

Why

Records and code share one language only when each word is defined once: a term used without an entry means whatever each reader guesses. The entry fixes the meaning, the rejected synonyms, and the wider and narrower terms, so reviewers and checks read the same word the same way.

Example

The term receipt carries the meaning "proof of payment accepted for an expense" in docs/glossary.json, so every record naming a receipt means that proof.

Checka reviewer answers

Each term the domain records name appears with its meaning in docs/glossary.json.

Marked guidance: a reviewer answers this question.

State Every Module Dependency#

ruleguidance
litespec/core/domain#state-every-module-dependency
Statement

Each module MUST name every module it depends on in dependsOn.

Why

A module relies on the language and behavior of others, and each reliance shapes change: editing a module ripples into everything depending on it. Naming every reliance in dependsOn shows that ripple as data instead of leaving it for readers to reconstruct from prose.

Checka reviewer answers

Each module page names in dependsOn the modules its text and records rely on.

Marked guidance: a reviewer answers this question.

Model Each Aggregate Lifecycle with States#

rulecheckable
litespec/core/domain#model-each-aggregate-lifecycle-with-states
Statement

Each aggregate MUST declare its lifecycle as states entries with a name and a meaning.

Why

A bare state name tells a reviewer nothing: rejected without its meaning hides what rejection costs and allows. A name with a meaning makes each lifecycle step reviewable on the page, and gives the Transitions table states worth connecting.

Example

The expense aggregate declares submitted, approved, rejected, and reimbursed, each with one sentence saying what the expense awaits or has received.

Checkchecked by a tool

litespec consumer fails an aggregate page without states entries carrying a name and a meaning.

Marked checkable: the tool named here checks it.

Connect Every State Through Transitions#

ruleguidance
litespec/core/domain#connect-every-state-through-transitions
Statement

Each aggregate MUST list every allowed state change with its use case in its Transitions table.

Why

States without transitions are a list of names with no path between them: a reviewer sees where an aggregate rests but never how it moves. Listing every allowed change with the use case that moves it shows the whole lifecycle as debated rows, and leaves no state reachable only by a path no record names.

Checka reviewer answers

The Transitions table on each aggregate page pairs every allowed change with an existing state and use case.

Marked guidance: a reviewer answers this question.

Classify Every Aggregate Data Field#

ruleguidance
litespec/core/domain#classify-every-aggregate-data-field
Statement

Each aggregate MUST list its data fields with their meaning and classification in its Data table.

Why

Data without a classification is handled by guess: personal facts sit beside public ones with nothing marking which is which. A meaning with a classification of public, internal, personal, or sensitive tells every later reader how each field is handled, and shows which fields the erasure and export behavior covers. A project that erases and exports in one module states which aggregates that behavior covers with a rule that governs both the aggregate and the erasure or export use case.

Checka reviewer answers

The Data table on each aggregate page pairs each field with a meaning and a classification.

Marked guidance: a reviewer answers this question.

State Each Rule with Its Kind#

rulecheckable
litespec/core/domain#state-each-rule-with-its-kind
Statement

Each rule MUST state its kind and its statement.

Why

The kind decides how the rule is proved: an invariant on every change, a precondition before the behavior, a calculation through its examples, a cross-aggregate rule where aggregates meet. A rule without a kind leaves each reader to prove it a different way, and the checks no place to confirm it.

Example

rule/expense.receipt-required-above-threshold is a precondition stating "an expense above the policy threshold needs a receipt", checked before Submit expense runs.

Checkchecked by a tool

litespec consumer fails a rule entry without kind or statement.

Marked checkable: the tool named here checks it.

Keep the Negation Word in the Rule Name#

rulecheckable
litespec/core/domain#keep-the-negation-word-in-the-rule-name
Statement

A rule whose statement says not, no, never, or cannot MUST keep that word in its name.

Why

A name that drops the negation reads as its own opposite: a rule named cancel-after-shipment whose statement says "never" approves what it forbids. Keeping the word keeps the name a true sentence a reviewer trusts without opening the statement.

Example

The rule reads rule/order.cancel-never-after-shipment.

Checkchecked by a tool

litespec model check reports rule-name-negation for a rule whose statement says not, no, never, or cannot outside its name.

Marked checkable: the tool named here checks it.

Keep Cross-Aggregate Rules on a Rule Page#

ruleguidance
litespec/core/domain#keep-cross-aggregate-rules-on-a-rule-page
Statement

A rule spanning aggregates MUST live as an entry on a rule page.

Why

An aggregate page holds what one aggregate guarantees; a rule spanning aggregates guarantees what no single aggregate owns. Giving such rules their own page keeps each aggregate page a true account of its aggregate, and gathers the shared rules where a reviewer judges them together.

Checka reviewer answers

Each cross-aggregate rule sits as an entry on a rule page; other rules sit on their aggregate page.

Marked guidance: a reviewer answers this question.

Name the Refusal a Broken Rule Produces#

ruleguidance
litespec/core/domain#name-the-refusal-a-broken-rule-produces
Statement

Each rule whose breach refuses a use case MUST name the refusal in refusal.

Why

A guarded rule without a named refusal leaves the ending unnamed: callers branch on a refusal the records never predicted. Naming the refusal ties the rule to the result the use case declares, so the breach and the ending stay one fact.

Checka reviewer answers

Each guarded rule names its refusal in refusal beside the rule entry.

Marked guidance: a reviewer answers this question.

State Each Event with Meaning and Carries#

rulecheckable
litespec/core/domain#state-each-event-with-meaning-and-carries
Statement

Each event MUST state its meaning and the facts it carries.

Why

An event without a meaning is a name every reaction interprets alone; facts without a carries list are reconstructed from code at each reaction. Stating both on the entry lets each reaction trust what happened and what arrived with it.

Checkchecked by a tool

litespec consumer fails an event entry without meaning or carries.

Marked checkable: the tool named here checks it.

Raise Every Event Through Its Aggregate Root#

ruleguidance
litespec/core/domain#raise-every-event-through-its-aggregate-root
Statement

Each event MUST be raised through its aggregate root.

Why

The root is the one type through which outside behavior changes the aggregate, so it sees every change worth announcing. Raising each event there keeps the fact and its announcement in one step, and leaves no event announced for a change the aggregate never made.

Checka reviewer answers

Each event is raised from the root of the aggregate that owns it.

Marked guidance: a reviewer answers this question.

Give Each Use Case One Kind#

rulecheckable
litespec/core/domain#give-each-use-case-one-kind
Statement

Each use case MUST declare one kind: command, query, or reaction.

Why

The kind decides what the use case is allowed to touch: a command changes, a query reads, a reaction answers. A use case with no kind, or two, leaves each reader to allow it everything, and the checks no shape to confirm.

Checkchecked by a tool

litespec consumer fails a use-case page without kind or with an unknown kind.

Marked checkable: the tool named here checks it.

Trigger Every Reaction#

ruleguidance
litespec/core/domain#trigger-every-reaction
Statement

Each reaction MUST name its trigger: the events it answers, or the schedule or deadline in words.

Why

A reaction without a trigger never runs or runs whenever its author remembered: reviewers see behavior with no moment attached. Naming the events, or the schedule or deadline in plain words, states when the behavior answers.

Checka reviewer answers

Each reaction page names its trigger events, schedule, or deadline.

Marked guidance: a reviewer answers this question.

Declare Every Query Output#

ruleguidance
litespec/core/domain#declare-every-query-output
Statement

Each query MUST declare its output.

Why

A query without a declared output returns whatever its author fetched: readers and screens guess the shape from code. Declaring the output states what readers receive, so the shape is reviewable before anything implements it.

Checka reviewer answers

Each query page declares the shape its readers receive in output.

Marked guidance: a reviewer answers this question.

End Every Use Case in Named Results#

rulecheckable
litespec/core/domain#end-every-use-case-in-named-results
Statement

Each use case MUST declare its success result and every refusal with its code.

Why

Callers branch on named endings: success, or one refusal with its stable code. A use case with no named success leaves readers guessing what good looks like, and a refusal without a code leaves each layer reporting the same breach another way.

Example

The submit use case declares submitted and receipt-missing with the code spending.receipt-missing.

Checkchecked by a tool

litespec model check reports use-case-without-success for a use case with no success result; each refusal entry on the use-case page carries its code.

Marked checkable: the tool named here checks it.

Name the Result Each Scenario Ends In#

rulecheckable
litespec/core/domain#name-the-result-each-scenario-ends-in
Statement

Each scenario MUST name the result it ends in.

Why

A scenario that names no result illustrates behavior without asserting an ending: it reads well and proves nothing. Naming the result ties each example to the ending it walks toward, so every test knows what to assert.

Checkchecked by a tool

litespec consumer fails a scenario entry without result, and litespec model check reports scenario-result-unresolved for a scenario ending in another use case's result.

Marked checkable: the tool named here checks it.

Write Every Scenario in Given When Then#

rulecheckable
litespec/core/domain#write-every-scenario-in-given-when-then
Statement

Each scenario MUST state its given, when, and then.

Why

The three steps separate the situation from the action and the action from the assertion: collapsing them blurs what was true, what moved, and what the test checks. Stating all three keeps each example readable by a person and provable by a test.

Example

JSON
{
  "type": "scenario",
  "id": "scenario/submit-expense.submitting-with-a-receipt-succeeds",
  "title": "Submitting with a receipt succeeds",
  "given": "An employee paid for a train ticket and kept the receipt.",
  "when": "The employee submits the expense with the receipt.",
  "then": "The expense is submitted.",
  "result": "result/submit-expense.submitted"
}
Checkchecked by a tool

litespec consumer fails a scenario entry without given, when, or then.

Marked checkable: the tool named here checks it.

Tabulate Every Calculation with Examples#

ruleguidance
litespec/core/domain#tabulate-every-calculation-with-examples
Statement

Each calculation rule MUST be proven by a scenario carrying an examples table.

Why

A calculation stated only as a sentence hides its edges: reviewers agree on the words while each pictures other numbers. An examples table shows the debated rows, and the scenario proving the rule pins them as checked facts.

Checka reviewer answers

A scenario proving each calculation rule carries its examples table on the use-case page.

Marked guidance: a reviewer answers this question.

Keep Design Choices Out of the Records#

ruleguidance
litespec/core/domain#keep-design-choices-out-of-the-records
Statement

Child entities and value objects MUST appear only in code and the glossary, never as records.

Why

The records name what carries business meaning; the design inside an aggregate changes without changing that meaning. Recording each child and value as its own record would triple the tree while adding no decision a reviewer approves. Code and the glossary carry them, where builders and readers meet them.

Example

Order lines live as a child of the order aggregate, and money as a value with its glossary entry; neither is a record.

Checka reviewer answers

No record page declares a child entity or value object as a record type.

Marked guidance: a reviewer answers this question.

Conventions#

Illustrate Pages with Examples from the Cast#

conventionguidance
litespec/core/domain#illustrate-pages-with-examples-from-the-cast
Default

Each module, aggregate, and use case page carries an Example section drawn from the example cast.

ReplacementA project names the pages that carry an Example section in its documentation.

Why

Every other section states a rule, a state, or a mapping; none says when the behavior happens or who is under pressure while it does. An occasion drawn from the one shared cast lets a reviewer judge whether the behavior fits the world it serves, and lets readers carry the same people and figures from page to page.

Checka reviewer answers

Each module, aggregate, and use case page carries an Example section from the cast, or the project documentation names the pages that do.

Marked guidance: a reviewer answers this question.

Group Cross-Aggregate Rules by Theme#

conventionguidance
litespec/core/domain#group-cross-aggregate-rules-by-theme
Default

Each rule page holds the cross-aggregate rules of one theme, named in its Summary.

ReplacementA project names its rule-page grouping in its documentation.

Why

Cross-aggregate rules share no aggregate to gather them, so an ungrouped rule page becomes a list of unrelated checks. One theme per page, stated in its Summary, keeps each page reviewable as one decision about what holds across the domain.

Checka reviewer answers

Each rule page states its theme in Summary, or the project documentation names another grouping.

Marked guidance: a reviewer answers this question.

Verification#

IDMarkEvidence
litespec/core/domain#define-every-domain-term-in-the-glossaryguidanceEach term the domain records name appears with its meaning in docs/glossary.json.
litespec/core/domain#state-every-module-dependencyguidanceEach module page names in dependsOn the modules its text and records rely on.
litespec/core/domain#model-each-aggregate-lifecycle-with-statescheckablelitespec consumer fails an aggregate page without states entries carrying a name and a meaning.
litespec/core/domain#connect-every-state-through-transitionsguidanceThe Transitions table on each aggregate page pairs every allowed change with an existing state and use case.
litespec/core/domain#classify-every-aggregate-data-fieldguidanceThe Data table on each aggregate page pairs each field with a meaning and a classification.
litespec/core/domain#state-each-rule-with-its-kindcheckablelitespec consumer fails a rule entry without kind or statement.
litespec/core/domain#keep-the-negation-word-in-the-rule-namecheckablelitespec model check reports rule-name-negation for a rule whose statement says not, no, never, or cannot outside its name.
litespec/core/domain#keep-cross-aggregate-rules-on-a-rule-pageguidanceEach cross-aggregate rule sits as an entry on a rule page; other rules sit on their aggregate page.
litespec/core/domain#name-the-refusal-a-broken-rule-producesguidanceEach guarded rule names its refusal in refusal beside the rule entry.
litespec/core/domain#state-each-event-with-meaning-and-carriescheckablelitespec consumer fails an event entry without meaning or carries.
litespec/core/domain#raise-every-event-through-its-aggregate-rootguidanceEach event is raised from the root of the aggregate that owns it.
litespec/core/domain#give-each-use-case-one-kindcheckablelitespec consumer fails a use-case page without kind or with an unknown kind.
litespec/core/domain#trigger-every-reactionguidanceEach reaction page names its trigger events, schedule, or deadline.
litespec/core/domain#declare-every-query-outputguidanceEach query page declares the shape its readers receive in output.
litespec/core/domain#end-every-use-case-in-named-resultscheckablelitespec model check reports use-case-without-success for a use case with no success result; each refusal entry on the use-case page carries its code.
litespec/core/domain#name-the-result-each-scenario-ends-incheckablelitespec consumer fails a scenario entry without result, and litespec model check reports scenario-result-unresolved for a scenario ending in another use case's result.
litespec/core/domain#write-every-scenario-in-given-when-thencheckablelitespec consumer fails a scenario entry without given, when, or then.
litespec/core/domain#tabulate-every-calculation-with-examplesguidanceA scenario proving each calculation rule carries its examples table on the use-case page.
litespec/core/domain#keep-design-choices-out-of-the-recordsguidanceNo record page declares a child entity or value object as a record type.
litespec/core/domain#illustrate-pages-with-examples-from-the-castguidanceEach module, aggregate, and use case page carries an Example section from the cast, or the project documentation names the pages that do.
litespec/core/domain#group-cross-aggregate-rules-by-themeguidanceEach rule page states its theme in Summary, or the project documentation names another grouping.