Chapter 14 · Part VI

Where to Draw the Line

Every technique in this book answers one of two questions — how to constrain, or how to open. This chapter answers the question underneath them: for the decision actually in front of you, which one does it need?

Thirteen chapters have laid out two disciplines and a way to join them. But a catalog of techniques is not the same as knowing when to reach for each, and that knowing is the whole point — the reason the book is arranged as a tension to be resolved rather than a toolbox to be browsed. This chapter is the resolution: not a slogan, but a way to decide.

Start by rejecting the two slogans that pretend to settle it. "Make illegal states unrepresentable" is excellent advice that becomes a straitjacket when applied to the parts of a system that must grow in ways you cannot foresee. "Build for flexibility" is excellent advice that becomes a swamp when applied to the parts that must simply be correct. Each is right about its own half and silent about the other. The mistake is treating either as a global policy. Constrain-or-open is not a decision you make once for a system; it is a decision you make many times, locally, per seam — and the same well-built system will resolve it one way in its core and the opposite way at its edge.

14.1 A Local Question, Asked Often

The unit of the decision is a seam: a place where one part of the system meets another, or where the system meets the world. For each seam, you are choosing how much the types get to say. Tighten it, and the compiler carries more of the argument — fewer states, checked earlier, proven exhaustive. Open it, and the compiler steps back so that growth can happen by extension rather than edit. The art is that a single system has many seams, and they do not all want the same answer. The order state machine wants to be shut tight; the pricing catalog wants to be thrown open; the routine between them wants a contract. Judgment is knowing, seam by seam, which of the three you are looking at.

14.2 Reading the Signs

Most seams announce which way they want to go, if you know what to listen for. The signals cluster into two lists, and a real seam usually shows several from one side.

This seam wants tightening when…This seam wants opening when…
The set of cases is closed and knowable now (order states, payment status).The set of cases is genuinely open and unpredictable (pricing rules, integrations, formats).
Being wrong is expensive or irreversible — money, safety, corrupted data.Being wrong is cheap and recoverable — a rule mis-fires, you fix the rule.
It lives in the core, and many things depend on it.It lives at the edge, and depends on the core rather than the reverse.
The relationships can be expressed as types or contracts.New cases arrive from outside your team, at runtime, or from non-programmers.
Change is rare; correctness dominates.Change is constant; adaptability dominates.
You want a change to surface as compile errors you can chase.You keep reopening the same file to add the next variant, and it hurts.

The signals are not a scorecard to tally so much as a way to make the seam declare itself. When a seam shows tightening signs, the tools of Parts II and III apply: sum types, constrained types, parsing at the boundary, total transitions, closed combinator interfaces. When it shows opening signs, the tools of Parts IV and V apply: generic operations, domain languages, extensible evaluators, propagator networks, unification. The signs tell you which chapter you are living in.

14.3 When Unsure, Constrain

Some seams are ambiguous, and for those there is a tie-breaker worth holding as a default: constrain until you are forced to open. The reason is asymmetry. Opening a constrained design later is a backward-compatible move — you can add a table, an interface, a plug-in point to a sealed type without breaking anyone who used it while it was closed. Closing an open design later is a breaking change — every caller who relied on the freedom you are now revoking must be found and fixed. Constraint is the reversible choice; openness is the one that costs to undo.

This turns "how flexible should this be?" from a guess about the future into a much easier question about the present: is the openness needed now? If not, seal it, and open it the day a real requirement demands it — at which point you will also know exactly what shape the opening should take, instead of having guessed. Premature flexibility is as real a cost as premature optimization: an Any-typed plug-in system built for extensions that never arrive is pure liability, all guarantees surrendered for a benefit no one collected. Openness is earned by demonstrated need, not granted on speculation.

Constrain until forced to open. A closed design can be opened without breaking its callers; an open design cannot be closed without breaking them. When the signs are mixed, take the reversible choice.

14.4 Placing the Membrane

Once you have decided which regions constrain and which open, the interesting seams are the ones between them — where a constrained core meets an open edge. That seam is where a contract goes, and placing it well has a few rules that every membrane in this book obeyed.

It states the core's terms, not the edge's. The membrane is written from the constrained side: the core declares, as a precondition, exactly what any value must satisfy to be admitted, and the open edge — whatever produced the value, however unforeseen — must meet those terms. The engine's postcondition in Chapter 9 promised precisely what the core's apply_quote required. The core never asks how the price was computed; it asks only that the price be non-negative, in the right currency, above the floor.

It is narrow. A good membrane is one gate, not a perimeter of scattered checks. Untrusted input crosses at a single parser (Chapter 4); an open price enters the core through a single boundary routine. Concentrating the guarantee at one seam is what lets everything behind it stop checking — the payoff that a diffuse membrane forfeits.

Trust flows one way through it. The direction of the membrane is inward: distrust on the open side, trust on the closed side, and the contract is the one-way valve. Nothing crosses into the core without meeting its terms, and once across, it is believed. That asymmetry is the entire reason the arrangement holds.

14.5 The Whole Book on a Page

With the framework in hand, the techniques stop being a list and become a map. Every one of them draws the line somewhere and guards it with something; laid side by side, they trace the full arc from sealed core to open search — and show that a single idea, constraint held against flexibility across a contract, runs through all of them.

TechniqueStanceWhat draws the lineWhat guards it
Sum & constrained types (Ch 3)ConstrainTypes enumerate the kinds; illegal states unrepresentableInvariants on relationships
Parse, don't validate (Ch 4)Constrain the boundaryOne parser converts input to precise types, onceResult plus the parser's checks
Workflows as transformations (Ch 5)ConstrainTransitions are total functions; illegal transitions don't typecheckTypes plus ensure on each step
Effects at the edges (Ch 6)Constrain the corePure core; effects returned as dataThe thin imperative shell
Combinators (Ch 7)Additive, closedA closed interface; results composeContracts on the interface
Layering (Ch 8)Additive, closedEvery stratum shares one interfaceSubstitutability as a postcondition
Generic operations (Ch 9)Open vocabularyBehavior looked up in a runtime tablerequire a handler exists; membrane on the result
Domain languages (Ch 10)Open vocabularyRules become inspectable dataPer-term contracts behind the membrane
Extensible evaluators (Ch 11)Open primitivesClosed syntax, open operator tableExhaustive syntax; known_op, arity
Propagators (Ch 12)Open topologyCells of partial info; monotone mergeThe ordering invariant on the merge
Unification & search (Ch 13)Open structureBindings discovered by matching and searchA termination variant; a solution postcondition

Read the columns and the thesis reads itself. The stance slides from constrain at the top to open at the bottom. "What draws the line" moves from types toward tables, data, and discovery. And "what guards it" moves, just as steadily, from types toward contracts — until, at the open end, contracts are nearly the whole of the guarantee. That final column is the through-line: the more a seam opens, the more the guarding shifts from the compiler to the contract, and the membrane is where the two hand off.

14.6 The Two Ways to Get It Wrong

Judgment is also knowing the failure on each side, because both are common and each feels like virtue while you commit it.

Over-constraining looks like discipline and produces rigidity. Its symptoms: a sealed type you keep reopening to add cases; domain models so elaborate that the types are harder to understand than the bugs they prevent; a plug-in point that should exist but doesn't, so every extension is a fork of the core. The corrective is to find the seam that is actually changing and open it — introduce a table, a language, an interface — while leaving the genuinely stable core sealed. You do not open the system; you open the one part that asked to be opened.

Over-opening looks like flexibility and produces a swamp. Its symptoms: Any where a precise type belonged; runtime failures that a sum type would have made compile errors; a configuration language reinvented badly because someone opened a seam that only ever had four values. The corrective is the default of §14.3 run in reverse — find the open seam whose set of cases turned out to be closed after all, and seal it, converting a class of runtime surprises back into compile-time certainty.

And one last piece of judgment, easy to forget: the line moves. A seam that was rightly sealed when the business was young may need to open as the business grows, and a seam opened in speculation may deserve to be closed once its real shape is known. Drawing the line is not a decision you make once and enshrine; it is one you revisit as the system teaches you where it actually flexes. The framework is not a verdict but a lens — you look through it again each time a seam starts to ache.

That is the judgment the whole book was built to support. One chapter remains, and it is not an argument but a demonstration: a single system, built end to end, with a constrained order core and an open pricing edge living in one codebase without contradiction — every technique in its place, and the line drawn where each part of the domain asked for it.