A team builds a system that classifies requests into three priorities: urgent, normal, low.
Three values, clearly bounded.
The allowed values are placed directly in the program code. Months later, a fourth priority is added. A change to the vocabulary turns into a code change, a review, new tests, and a deployment.
Yet there was nothing wrong with the team’s decision to use a fixed set of values in the first place.
The mistake lies somewhere else: fixed was confused with hard-coded.
A closed set of values is allowed to be closed
Not every interface has to accept arbitrary values.
A cell - in the sense of the Cell Model, an independent unit with a clear responsibility - may describe very precisely which messages it understands. The Cell Model explicitly provides for a cell to describe its incoming events rather than process every message.
A contract can therefore certainly require:
priority must be a value from the priorities vocabulary
That is a closed contract. A value outside this set is invalid.
But that does not yet imply:
if priority == "urgent"
or priority == "normal"
or priority == "low" then
The contract defines the boundary. The program code does not have to contain its current contents itself.
Contract and vocabulary are two different things
This is exactly where two layers often become blurred in programs.
The contract might describe:
priority:
type: vocabulary
vocabulary: priorities
required: true
The vocabulary, on the other hand, describes:
priorities:
- urgent
- normal
- low
The first part says what kind of value is allowed. The second says which values currently belong to this set.
The code can still validate strictly: Is the received value part of priorities? But to do that, it does not have to know that urgent, normal, and low exist - and it must not know if this set is supposed to be able to change at runtime. As soon as urgent appears as a condition in the code, not only has a rule been implemented, but knowledge about the world has been baked into the implementation. The cell can no longer change that knowledge itself afterward.
Four layers
In our work, this separation has started appearing in several places. It helps to distinguish four different layers.
A capability describes what a unit can fundamentally do - for example, handle requests according to priority. That does not yet define what a request looks like technically.
The contract describes the shared language: A request has a priority, and the priority must come from the agreed vocabulary. That makes the boundary unambiguous.
The vocabulary contains the currently agreed values - urgent, normal, low. This set can be closed. It still does not have to be in the program code.
At runtime, a specific value is finally transmitted:
priority: urgent
The recipient does not have to have been programmed for urgent. It has to know how to validate a value against the agreed vocabulary and what a priority means within its responsibility.
This keeps four questions separate: What can I do? How do we talk about it? Which terms apply right now? What was actually said?
Learning requires changeable content
This separation becomes particularly important when a program is supposed to be able to change its behavior at runtime.
A program can only change what is not already part of its immutable program code. If urgent, normal, and low appear in a switch, the system can receive new data. But it cannot truly learn a new priority - it has no hard-coded response for emergency.
The obvious solution would be to move not only the names of the priorities out of the code, but also the properties the cell uses to work with them:
priorities:
urgent:
order: 10
strategy: immediate
normal:
order: 20
strategy: queued
low:
order: 30
strategy: background
The code no longer knows urgent. It knows rules such as order, response strategy, or conditions. The vocabulary provides the current forms of those rules. This moves knowledge out of the program and into an area that the cell or its environment can change - and emergency can be added without touching the code.
If strategy, in turn, is a hard-coded list of immediate, queued, background, the problem has merely been moved up one layer.
That does not mean the system no longer has fixed rules. On the contrary: The rules can define very strictly which properties an entry must have, which combinations are allowed, and which boundaries apply. The contract can strictly define the language in which changes are currently described. What remains changeable is what is expressed in that language - and, in principle, the language itself.
Why we are encountering this again right now
Lists like these appear quickly when programming. A few allowed event types. A few status values. A few operations. A few roles.
The simplest approach is an enum, a switch, or a table directly in the module. As long as nothing changes, this can even look like a particularly clean solution: The possible values are collected in one place, and the code can validate them unambiguously.
Only with the next step does it become apparent which decision was made without anyone noticing. Not only: This set is closed. But additionally: This program owns this set.
Those are two different statements.
A contract can be strict without becoming rigid
A closed vocabulary is therefore not the problem. On the contrary: A clearly defined set of values makes communication verifiable. Sender and recipient know which terms are part of their shared language.
The real question is who needs to own this knowledge. If it resides in the program code, every change to the vocabulary also changes the program. If it resides in contract data or configuration, the code can remain unchanged as long as its capability and the form of the contract do not change.
So before the next switch, there is more at stake than avoiding a deployment: How much does a program need to know in order to act - and how little must it know in order to still be able to learn?