The configurability boundary
Almost nothing is purely configurable or purely fixed — every value sits inside an invariant, and the job is to write both down.
The slogan is configuration changes the path, never the walls. It is a good slogan and a bad model, because almost nothing in a real system is purely one or the other. Read the slogan as a reminder of what misconfiguration is allowed to cost, and the rest of this page as the model.
Getting the line wrong fails in two directions. Too far one way and you have a rigid product the client abandons because it no longer matches how they work. Too far the other and you have a product whose confidentiality guarantee is whatever the last administrator typed.
Most things are not one or the other
The binary — this field is configurable, that one is fixed — breaks on the first real field. Almost everything is a configurable value inside a fixed invariant:
| Field | The value, configured | The invariant, fixed |
|---|---|---|
| Retention period | 3 years, 7 years, whatever the client sets | Never below the statutory minimum, never above the legal maximum; the deletion job runs regardless |
| Approval threshold | 50 million, 10 million | Above the threshold, a second approver is required, and it may not be the same person |
| Workflow guard | Which conditions gate this step | The step's required capability comes from a trusted registry, never from the configuration |
| File size limit | 10 MB, 25 MB | Content is validated on its real type; the scan runs; the quota exists |
| Territory list | Whatever territories exist | Every record has exactly one; it is never empty |
The question is never "configurable or fixed". It is: what is the value, and what is the invariant that value may never break?
Write both. The invariant goes in the module reference; the value goes wherever it is changed from — a versioned configuration artefact you publish, or an administration screen the client operates. Either way a publication check enforces the invariant against the value before the change takes effect.
The two tests
First — is there a value here at all?
Would this statement be false if the client reorganised next year?
Yes → there is a configurable value. No → it is pure specification.
Second — what invariant bounds it?
Run the value against each of these. Any yes is an invariant you must write down and enforce; it does not make the value unconfigurable.
| If an administrator sets this wrong, can it… | Then the invariant concerns |
|---|---|
| let someone see what they should not? | confidentiality |
| let someone change or delete what they should not? | integrity |
| let one person complete an act alone that requires two? | separation of duties |
| move money, or move more of it than someone may authorise? | financial authority |
| breach a statutory minimum or maximum? | legality |
| destroy records before they may be destroyed? | retention |
| make the system unusable, or a record unreachable? | availability |
| cause physical or personal harm? | safety |
A value with no yes is free configuration. A value with several is still configurable — it just carries several fixed bounds.
Where the value almost always exists
These carry a configurable value in nearly every system. That does not mean they carry no invariant — run them through the table above.
| Element | Examples |
|---|---|
| Organisational units | Services, teams, directorates, branches, and their names |
| Territories and their coverage | Which unit covers which area, dated so history survives |
| Category sets | Request types, actor categories, document types, priority levels |
| Process steps | The states, the transitions, their order, their guards |
| Required inputs | Which documents, which fields, per category |
| Thresholds and bounds | Amounts, sizes, formats, deadlines, retention |
| Labels and templates | What things are called, what notifications say |
What has no configurable value at all
A small set of things really is pure specification — there is no value to set, only a rule. Changing one is a specification change, reviewed as such.
| Element | Why it cannot be configuration |
|---|---|
| Capabilities | Adding one means deciding who holds it; that is a design act |
| Role × capability matrix | If this is runtime data, no statement about confidentiality is verifiable |
| Object ownership | "Only the author sees their own record" cannot be switched off by an admin |
| Separation of duties | The rule exists precisely to constrain people with administrative reach |
| Audit integrity | A log an administrator can rewrite proves nothing |
| Identity uniqueness | One person, one account — otherwise every separation rule is bypassable |
| Allowed effect types | Configuration may select an effect; it may never define a new one |
The asymmetry worth stating out loud
A badly configured system must be able to stall a case. It must never be able to expose one, pay out on one, or destroy one.
This is the sentence to put in the specification. It does not decide every field on its own — that is what the invariant table is for — but it settles the argument about what misconfiguration is allowed to cost, which is the argument that actually recurs.
Three-family enumerations
Closed does not mean fixed. Sort every enumeration into one of three families and say which route changes it:
| Family | Changed by | Example |
|---|---|---|
| Fixed by specification | Editing the specification | Roles, verdict values, visibility scopes |
| Administered reference data | An administration capability | Territories, units, categories, request types |
| Carried by a versioned artefact | Publishing a new version of that artefact | Workflow states and transitions |
All three are closed — no free text, no "other" category. They differ only in who may change them and how.
Configuration is not free
Four costs, all real. They are the price of the administration surface — not of the schema. If you cannot fund all four, defer the surface: hold the value in a versioned configuration artefact you change on the client's behalf. Do not defer the schema.
- Validation. Configuration without validation is worse than a constant: it lets someone configure an incoherent state and discover it in production. Every configurable surface needs a publication check — references resolve, no unreachable state, no dead end, every value mapped.
- Withdrawal safety. Retiring a configured element while live records depend on it makes those records unreachable with no visible error. Nothing is deleted; things are retired; and retirement is refused while dependencies exist.
- History. Old records must keep the meaning they had. Retired elements stay readable with the labels they carried at the time. A rename that rewrites history is a bug, not a feature.
- Governance. Who may change the value, whether the change needs a second pair of eyes, how it is rolled back, and what trail it leaves. Removing the deployment step does not remove the review step.
And one fact that is not a cost: somebody has to operate it. A client with no administrator gets a screen frozen at its defaults, and you have built governance and rollback to achieve nothing. Ask who will operate it before deciding to build a screen. That question never touches the schema.
Separate the administration of things from the administration of access
If belonging to a configured unit grants access to records, then whoever can create units and place people in them can grant access — regardless of what the access-control design says.
Split it:
- one capability administers the lists — create, rename, retire units, territories, categories;
- a different capability administers people — who belongs to which unit, who holds which role.
Neither alone opens access to anything. Cheap to specify, and impossible to retrofit once an administration screen exists.
Worked example — a ministry's grant platform
The first specification said "the ministry has 32 regional directorates" and "the 8 central directorates are: …", and produced a 28-item question list for the client, of which the first two were "give us the list of the 32" and "tell us which of the 8 participate".
Both were blocking. Neither should have been asked.
What the corrected model separated:
| Notion | What it is | Family |
|---|---|---|
| Territory | A locality the applicant declares. Geography, not organisation. | Administered |
| Organisational unit | A service that acts in the process, carrying a closed operational function | Administered — except the function |
| Territorial competence | Dated mapping between the two | Administered |
The operational function — does this unit instruct, or is it consulted — stayed fixed in the specification, because the product behaves differently for each and confusing them would erase a step. Everything else became data.
Two further corrections fell out of it:
- The applicant declares where they are, never which internal service handles it. Making them pick a service exposes the org chart to someone who has no reason to know it, and their declaration becomes false at the next reorganisation.
- Access stayed an intersection — bounded by unit and by territory. Collapsing them into "any unit you belong to" turns AND into OR and grants an agent their whole unit nationwide plus their whole territory across every unit.
The question list went from 28 to 13 — 15 of 28 were configuration, just over half.
Note what did not become configurable, and why. The role × capability matrix stayed fixed: making it runtime data would have meant no statement about confidentiality was verifiable. The operational function stayed fixed: the product behaves differently for a unit that instructs and one that is consulted. Both are invariants, not values.