Draft, September 2026

Sections / 15

15. What the grammar demands

In short

Deciding what goes into the grammar is architectural judgement. The language should sit above the product's primitives, and a customer's change needs a path into it: into the customer's own document, or a scoped plugin that is generalised once the need recurs.

In the video · scene 21
The explainer video for the paper SaaS architecture when code is cheap

Deciding what goes into it is architectural judgement. Whether something becomes a primitive, a parameter, or stays as configuration is a decision an architect makes today. The mechanics of building the language are largely automatable. The aim is to derive the grammar from the knowledge graph automatically, so that a new product goes from graph to grammar to modernised surface as a routine sequence. The method does not yet do that.

The language has to sit above the product's primitives rather than restate them. A grammar that only exposes what the software already does, one construction per existing configuration option, is a command-line substitute for a form. The value is in the higher-level constructions that encode how a stated requirement becomes a set of those options, which is the knowledge the configuration specialist holds. Put in the terms of section 1.3, the grammar should express the domain value proposition in the domain's own vocabulary.

A customer's change needs a path into the language. A change requested by one customer is made first in the domain document that represents that customer's implementation. Where the language can already express it, that is the whole change. Where it cannot, the need is met by a plugin: an extension scoped to that customer, attached to that customer's document, written in the host language under engineer review, and largely independent of the language's primitives. Either way the change stays local to one customer and the shared layers are untouched.

That locality buys time. Whether a request is a genuine variation of the domain or one customer's particular need is rarely clear from its first appearance, and generalising too early produces a primitive shaped around a single customer. Once the same need has appeared across enough customers for its shape to be understood and mapped, the plugins that met it are generalised into a new primitive or a change to the grammar, and the customers using them move onto the language construct. Refactoring practice encodes the same caution as the rule of three: two similar instances can be tolerated, and the third is the point at which to generalise (Refactoring: Improving the Design of Existing Code, Fowler et al., 1999, crediting the rule to Don Roberts).

A customer's change path into the languageA customer requests a change. If it is expressible in the language, it becomes a change in that customer's document. If not, it becomes a plugin scoped to that customer and reviewed by an engineer. If the same need recurs across customers and its shape is understood, it becomes a new primitive or grammar change, and customers move from the plugin to the language construct. If not yet, the plugin stays local and is reviewed for promotion or retirement.Customer requests a changeExpressible inthe language?Same need recursacross customers?Change in thatcustomer's documentPlugin scoped to thatcustomer, engineerreviewedNew primitive orgrammar changePlugin stays local,reviewed for promotionor retirementCustomers move from pluginto the language constructyesnoyes, shape understoodnot yet

The plugin is where the risk of section 1.1 now sits, so it needs its own discipline. Each plugin is scoped to one customer, recorded as a candidate for generalisation, and reviewed periodically for promotion or retirement. A plugin that lives indefinitely is a special case again, with the one improvement that it cannot affect another customer.

This is how the language grows, and it is compatible with keeping the language bounded. What the grammar should not acquire is general-purpose machinery, such as user-defined functions, general recursion or arbitrary expressions, because that turns it back into a programming language with worse tooling than the one it replaced. The discipline of keeping general computation out of the language and in reviewed host-language code is what has kept SQL, regular expressions and Terraform's configuration language bounded for decades. Adding a domain primitive that several customers have shown they need is the intended way for the language to grow.

Sources

  1. Refactoring: Improving the Design of Existing Code