Constraints — what you were not free to choose
The choices that were already made for you — technical, organisational, regulatory, commercial — each with the reason it exists and, where one is knowable, the date it stops being true. This is the cheapest concern to write and the one that most often explains a design that looks wrong.
Spine concern · arc42 §2 · spine.yaml: constraints
A constraint is something the design must accept rather than decide: a mandated platform, a team boundary, a compliance rule, a contract, a fixed deadline. Record each one with its type, the reason it exists and its expiry date if it has a knowable one. Constraints without reasons become folklore, and constraints without expiry dates outlive the situations that created them — which is how a system ends up shaped by a rule nobody can defend.
The Basics
Durable. What this concern is responsible for, regardless of decade or stack.
Every architecture is partly a response to things that were not up for discussion. Writing them down costs an afternoon and pays for itself the first time a new engineer asks why the system is shaped like that — because the honest answer is often not a decision at all, and pretending otherwise makes the design look incompetent rather than constrained.
Constraint, decision, or preference
The distinguishing question is whether you had a choice. If you did and you made it, it is a decision and it belongs in the decisions folder with its rejected options. If you did not, it is a constraint: you can note that you dislike it, but there are no alternatives to enumerate because none were available to you.
The line matters because the two are reviewed differently. A decision is revisited when the goal it serves changes rank. A constraint is revisited when the external fact changes — a contract ends, a licence expires, a team reorganises, a regulation lands. Filing one as the other means it gets reviewed on the wrong trigger, which in practice means never.
And a preference is neither. "We use this ORM" is a preference unless something forced it. The test is whether you can name the external fact or the authority that removed the choice. If you cannot, you are recording taste, and taste in the constraints list is how a team ends up believing it is trapped by something it could change tomorrow.
The four types, and why the type is a field
Technical: a mandated runtime, a legacy interface you cannot change, a fixed integration protocol, a device or browser floor. Usually the most visible in the code and the least contentious to write down.
Organisational: team boundaries, who owns which system, the size of the team, the fact that a required skill exists in one person. These shape the architecture more than most technical constraints do — Conway's law is not a metaphor — and they are the ones teams are most reluctant to write down, because they read as criticism.
Regulatory: data residency, retention floors and ceilings, auditability, consent, sector-specific regimes. Non-negotiable, expensive to discover late, and the type most likely to be stated second-hand and inaccurately. Cite the actual obligation, not the summary someone repeated in a meeting.
Commercial: contractual response times, licence terms, a fixed launch date, a vendor commitment tied to a discount. These expire more often than any other type, which is exactly why they need dates.
A constraint without a reason becomes folklore
"We cannot use managed services" survives for years after the person who knew why has left. Then two things happen: engineers route around it in ways nobody planned, and any attempt to challenge it fails because there is nothing to challenge — no reason to examine, only a rule.
So the reason is not documentation courtesy, it is the mechanism by which a constraint can be retired. One sentence naming the authority or the external fact — the security review of 2025, the contract with this customer, the regulation, the fact that only one team can deploy to that network — is what makes the constraint falsifiable later. Without it, the constraint is permanent by default.
Expiry dates are the whole discipline
Most constraints are true for a period. A licence runs to a date. A legacy system is scheduled for decommission. A team is short-staffed for a quarter. A deadline passes. Write the date, and the constraint stops being a permanent feature of the landscape and becomes something with a known end.
Where no date is knowable, write the condition instead: valid while we are on this contract, while that system exists, while the team is under five people. A condition is checkable in the same way a date is, which is all the review needs.
The cost of skipping this is specific and common: an architecture still paying for a constraint that ended eighteen months ago. Nobody re-examines a rule that presents itself as permanent, and the work it forces looks like normal engineering overhead rather than a bill for something that no longer exists.
Say what each one costs you
A constraint list that reads as neutral facts is missing its most useful column. Note what the constraint prevents or makes more expensive: the deployment model it rules out, the goal it caps, the workaround it forces you to maintain.
That is the sentence that gets a constraint removed. Somebody with the authority to change a vendor contract or a team boundary will not act on a line in a spec, but they will act on the observation that it is costing a week per release. It is also what stops a quality goal from being written in a way the constraints make unreachable — the conflict becomes visible at authoring time rather than at delivery time.
A constraint is complete when
- It removed a choice
- You can name the external fact or authority behind it. If you cannot, it is a preference or an undocumented decision.
- It has a type
- Technical, organisational, regulatory or commercial. The type decides who can lift it and on what trigger it is reviewed.
- It has a reason
- One sentence naming the source. This is what makes it possible to retire the constraint later.
- It has an expiry or a condition
- A date where one exists, otherwise 'valid while X'. Undated constraints become permanent by default.
- It names its cost
- What it prevents, caps or forces you to maintain — the sentence that gets it challenged by someone able to remove it.
- It is cited where it bites
- Referenced from the decisions it forced, so the design that looks odd reads as constrained rather than careless.
In the spec
What the concern looks like once it is written down — the machine layer entries a build check or a coding agent resolves by id, and the prose beside them.
constraints:
- id: C-1
constraint: >-
EU customer personal data is stored and processed in
eu-west-1 only; no cross-region replication.
type: regulatory
rationale: >-
Contractual DPA with two enterprise customers; see
legal/dpa-2026-03. Enforced by G-5 (region allowlist).
- id: C-2
constraint: >-
Deploys to the payments network require a Tuesday or
Thursday change window.
type: organisational
rationale: >-
Network group owns the firewall change process.
Caps QG-2 lead time at ~3 days.
expires: "2027-01-31"
- id: C-3
constraint: No GPL or AGPL dependencies in shipped artefacts.
type: commercial
rationale: >-
Distribution terms in the OEM agreement. Enforced by
G-3 (licence scan, fails the build).
- id: C-4
constraint: >-
Runtime is the internal platform's Node 22 base image;
no custom base images.
type: technical
rationale: >-
Platform-inherited. Exception process exists —
platform-team RFC, ~2 weeks.# Constraints
Reviewed quarterly with the quality-goal ranking. Anything past
its expiry date is retired here, not quietly ignored.
## C-2 Change windows for payments deploys (organisational)
Deploys touching the payments VPC need a Tuesday or Thursday
window; the network group owns firewall changes.
Source: platform/network RFC-114, agreed 2026-02.
Expires: 2027-01-31 (self-service firewall API is on their roadmap).
Costs us: lead time for payments changes is ~3 days against a
QG-2 target of five days end to end, so most of that budget is
spent waiting. Two decisions exist only because of this —
ADR-0018 (batch the schema migrations) and ADR-0022 (feature
flags for payment provider rollout).
## Retired
- C-7 single-vendor database licence. Expired 2026-06-30 when
the ELA ended. ADR-0011 was taken under it and should be
reopened.The Current
Reviewed 2026-09-09How it actually plays out in production now. Dated, because this is the part that decays.
Two things have changed the shape of this list in the last few years. Regulatory constraints have moved from a specialist concern to a default one — residency, retention, AI-specific obligations — and the platform layer has become a large source of constraints that product teams did not choose and often cannot see written down anywhere.
Data residency and retention are now common, not exotic
Where data may live, how long it must be kept, how long it may be kept, and who may see it in an incident — these now appear in ordinary commercial contracts, not just in regulated sectors. They are architectural, because they decide region topology, backup strategy, log handling and which managed services are even usable.
Discovered late, they are the most expensive class of constraint there is: a residency requirement that arrives after launch is a data migration and a redeployment, not a configuration change. Ask for them during design, and record which customer or regime each one comes from — because the answer is frequently one contract, and one contract can be renegotiated.
AI-specific obligations have joined the list
Where model inference may run, whether provider training on your data is permitted, what has to be disclosed to a user, what must be logged for an audit, whether a human decision point is required. These behave like classical regulatory constraints and they land on features teams often treat as experimental.
The practical failure is scope drift: a constraint accepted for a prototype — no customer data leaves this region, no third-party model — is silently violated when the prototype becomes a product and someone adds a provider. Record it as a constraint with the feature named, so the next person adding a model call meets it in the spec.
The platform is a constraint supplier
On a team using an internal platform, a real share of the architecture is not chosen: the deployment target, the ingress, the service mesh, the base images, the observability stack, the languages the pipeline supports. That is usually a good trade, and it is still a set of constraints.
Write them as inherited, with the platform named and, if there is one, the escape hatch. Two failures follow from leaving them out. A team designs something the platform cannot deploy and finds out at the end. Or a team treats a platform default as immovable when an exception process exists — and pays for years to avoid a conversation it never had.
Cloud commitments and the cost of leaving
Reserved capacity, committed spend, discount agreements tied to a vendor's services. They are commercial constraints with hard dates, and they legitimately shape a design — but only until the term ends.
This is where dated constraints pay most obviously. A commitment expiring in fourteen months is a planning input; the same commitment recorded as "we are on this cloud" is a permanent conclusion. Same fact, two very different architectures downstream.
Organisational constraints are still the ones nobody writes down
The team is four people. One person understands the payment integration. Two teams share a database because they used to be one team. Deploys need a change window because a separate group owns the network. These constrain the architecture at least as hard as any technical rule, and they are almost always absent from the spec.
Write them factually and without blame: the constraint is the structure, not anybody's competence. A spec that records "one team owns both services, so the boundary between them is not enforced by process" is describing a real property of the system. Left unwritten, the same fact shows up later as an unexplained coupling that the next architect will read as an error.
Future-ready
What changes when agents write and operate the code. Opinionated on purpose.
This is the concern where the machine layer earns its keep fastest. A coding agent has no way to infer a constraint — a residency rule, a banned dependency, a legacy interface that cannot change is invisible in the code that complies with it — so an unrecorded constraint is one an agent will violate confidently, at speed, with a clean test run.
Compliance leaves no trace in the code
A decision usually leaves fingerprints: an interface, a pattern, a package boundary an agent can notice and follow. A constraint often leaves nothing at all. Code that keeps data in one region looks exactly like code that has never considered regions. Code that avoids a licensed library looks like code that did not need it.
So the agent infers freely and adds the obvious dependency, the convenient managed service, the second region for resilience — each a reasonable suggestion in general and a violation here. The cost is not the wasted patch; it is that the reviewer has to hold the entire constraint list in their head to catch it, every time.
Machine-readable, and short enough to always be in context
Constraints are the cheapest high-value entries in spine.yaml: an id, one line of text, a type, a reason, an expiry. Ten of them cost almost nothing in a context window, which means they can be included in every session rather than fetched when someone remembers to.
Prefer the enforceable phrasing. "No third-party model providers for customer data" is checkable; "be careful with customer data" is not. Where a constraint can be expressed as a rule about the repository — a banned dependency, a forbidden import, a region allowlist — it should also become a guardrail, because prose in a prompt is guidance and only the check is enforcement.
Agents can maintain the list they cannot invent
An agent cannot discover that your largest customer's contract forbids something. It is genuinely good at the upkeep: finding constraints referenced in decisions but absent from the list, flagging the three whose expiry dates have passed, noticing that a proposed change conflicts with C-4, spotting that two constraints say incompatible things.
That is the right division. The list's content comes from contracts, regulators and the organisation chart; the hygiene — completeness, expiry, contradiction — is mechanical, and mechanical work is what the agent should be doing.
The expired constraint is now more expensive
A stale constraint used to slow a team down. Now it also steers every agent session: each one designs around a rule that ended last year, and the resulting code looks correct because it is internally consistent and passes review.
Which makes the expiry field an operational control rather than an act of tidiness. Check the dates on the same cadence as the quality-goal ranking, retire what has ended, and note the retirement — the decisions taken under a constraint that no longer exists are exactly the ones worth reopening.
How this concern fails
Named, because a failure mode you can name is one you can spot in your own repository before it costs you a quarter.
The undated constraint
True when written, permanent by default. The architecture keeps paying for a licence, a contract or a staffing situation that ended, and because the rule looks structural nobody thinks to check.
Folklore with no source
'We cannot use managed services.' No reason, no author, no date. It cannot be challenged because there is nothing to examine, so engineers route around it invisibly instead.
The preference in disguise
A technology choice filed as a constraint, usually to end an argument. It removes the rejected options from the record and makes a reversible decision look immovable to everyone who arrives later.
The regulation nobody read
A retention or residency rule recorded from memory, slightly wrong, and expensive in both directions — over-compliance costs money, under-compliance is a finding. Cite the obligation, and name who confirmed it.
The invisible platform constraint
Half the deployment architecture was decided by the platform team and appears nowhere in the spec. New engineers read it as a series of odd choices, and someone eventually designs something the pipeline cannot ship.
The organisational constraint left unsaid
Team size, ownership splits and change windows shape the design as hard as anything technical, but they read as criticism so they go unwritten. The coupling they cause then looks like an engineering mistake.
Go deeper
Frequently asked
What is the difference between a constraint and an architecture decision?
Whether you had a choice. A decision is a choice you made, so it carries the alternatives you rejected and the consequences you accepted. A constraint is a choice that was removed — by a contract, a regulator, a platform team, an existing system — so there are no rejected options to record, only the reason it applies and the date or condition under which it stops. They are also reviewed on different triggers: decisions when the quality goal they serve changes rank, constraints when the external fact changes.
How do you document architecture constraints?
One line each, with four fields: the constraint itself, its type (technical, organisational, regulatory or commercial), the reason or authority behind it, and an expiry date or condition. Add what it costs you — the option it removes or the goal it caps — because that is the sentence that eventually gets it lifted. Keep the list short enough that it can be read in a minute and included in every coding-agent session.
Why do constraints need expiry dates?
Because most of them are temporary and none of them look temporary. Licences end, legacy systems get decommissioned, contracts are renegotiated, teams grow, deadlines pass. Without a date or a 'valid while X' condition, a constraint presents itself as a permanent property of the landscape, and the architecture keeps paying for a situation that ended — usually without anyone noticing, because the cost shows up as ordinary engineering overhead.
Are quality goals constraints?
No, and conflating them costs you the ranking. A quality goal is something you chose to be good at and can trade against other goals; a constraint is something you must accept and cannot trade at all. A constraint can make a goal unreachable — a mandated single region caps an availability goal — and that conflict is exactly what you want visible while the goals are being written rather than during delivery.
Should organisational constraints go in the architecture spec?
Yes, and they are the ones most often left out. Team size, ownership boundaries, a required skill held by one person, a change window owned by another group — these shape the architecture at least as hard as technical rules, and Conway's law means the structure will reflect them whether or not the spec admits it. Write them factually as properties of the organisation rather than as criticism of anyone, or the next architect will read the resulting coupling as an error and 'fix' it.
How do constraints help AI coding agents?
They stop a class of violation an agent cannot possibly avoid on its own. Compliance leaves no trace in code — a repository that keeps data in one region looks identical to one that has never thought about regions — so an unrecorded constraint gets broken confidently, at speed, with passing tests. Constraints are also cheap in context: ten one-line entries can sit in every session, and any constraint expressible as a repository rule should become a guardrail as well, since prose is guidance and only a check is enforcement.
How does this map to arc42 section 2?
It is the same section with two fields added. arc42 section 2 asks for the constraints the architecture must accept, usually as a table of constraint and explanation. The Spine keeps that and requires a type and an expiry date or condition, then indexes the entries by id in spine.yaml so a decision, a guardrail or an agent can cite the constraint that forced it. If you already have an arc42 section 2, migrating is a matter of adding those two columns and giving each row an id.
The other eleven concerns
Pages are being written one at a time. The ones without a page yet are still in the template, with prompts instead of prose.
- Decisions
- Quality goals
- Guardrails
- Context & scope
- Solution strategy
- Structure
- Behaviour
- Deployment & operations
- Cross-cutting concepts
- Risks & debt
- Vocabulary
Constraints
Take the template, not the idea
The zip, the single-file variant, the schema and the agent bundle. Nothing behind an email address.
Free to fork, modify and use commercially. No attribution required.