Somebody chose eight percent. Four steps later, that number decides the angle at which the loaded trailer rolls over.
Design-as-code is arriving commercially. The pitch is consistent across everyone selling it: your CAD should be a program, not a sequence of clicks. Version it, diff it, generate variants, let a machine drive it.
I agree with all of that. I have been building on that thesis for a while — a dump trailer, a geneva indexer, a dynamometer and a launch vehicle, none of them finished.
That last part matters, and not as a disclaimer. The problem I want to describe shows up early in a design and never goes away. It is a property of designing, not of having designed — which is why I can write about it from the middle of four unfinished ones.
A word I am going to use a lot. A store is one file per machine — design.yaml — holding every number in the design as a tree of nodes: the machine, its assemblies, its parts. Each value declares what kind of number it is, and carries either the source it came from or the expression that produces it. Nothing else is authoritative. The drawings, the analyses and the bill of materials are generated views of it.
Geometry is the second-to-last thing that happens, and everything above it decides what the geometry is. Note the shape of the top row: requirements, judgements and datums are three parallel kinds of input, not a sequence. None of them derives from another — in the trailer store all 276 are leaves — and they converge on the derived values beneath. A parametric model with a clean API is a faithful executor of decisions made somewhere else — which is worth taking seriously, and I come back to it. If those decisions are not captured, programmable geometry means you can now build the wrong thing quickly, repeatably, and under version control.
One requirement, five steps, one thing a welder does
The layer diagram above is the abstract version. Here is a real one, traced out of the trailer store with each step’s own derivation printed on the arrow.
A requirement says the deck is forty feet. Five steps later you get camber_recommended_in = 0.116 in, and the note on that value calls it what it is: a fabrication instruction and not a check — camber each rail by this much and the deck is flat when it is empty.
Nobody would guess those two lines are connected. No drawing of the beam records that they are. And if the deck length changes, the camber changes, and the only thing that knows is the graph.
The same trace on the launch vehicle ends somewhere completely different.
Same store format, same machinery, six steps: a 200 km parking orbit implies a transfer ellipse, which implies a 45-minute coast, which sets the least acceleration that keeps propellant against the tank outlet, which sets the delta-v that costs.
And then the last value is not a number to build to. It is 10,652 — the factor between the floor and the ceiling of the answer, because one input was never stated. The settling propellant is unknown across four orders of magnitude, and the store says so in its own output.
That is the second thing provenance buys, and I did not expect it. The first is knowing what follows from what. The second is that a chain which reaches a value nobody supplied can say so, in the same units as everything else, instead of quietly picking something plausible.
Nobody builds the chain
The obvious objection to those two figures is that I constructed them — picked a start, picked an end, and wrote the steps between. I did not, and the reason is the part of this that transfers.
A chain is never authored. It is what is left when every other way of getting a number is made expensive.
Nine checks run against the store on every change. Three of them do the work.
A derived value has to reproduce. Each one carries a derivation and a cached result, and the check re-runs the derivation and refuses if the two have separated. The store’s own comment on this: a stored number and a stored reason are two copies of one fact, and copies separate. You cannot write down a bare number and call it derived. So the moment you want a value, you write an expression — and the expression names other values. That is the edge. Nobody drew it.
An expression may only reach what its node declares. A node may use its own values and whatever its interface binds, and nothing else. Reaching past that is refused, so the edges are declared rather than incidental.
And there is a hard budget on deciding instead of deriving. Every value held by unaided opinion spends from a cap that is stored in the file and may only shrink. Here is what that costs in practice:
| machine | budget | judgements | derived | ratio |
|---|---|---|---|---|
| dump trailer | 5 | 4 | 775 | 1 : 193 |
| launch vehicle | 24 | 24 | 56 | 1 : 2 |
| geneva indexer | 3 | 3 | 133 | 1 : 44 |
| dynamometer | 3 | 3 | 54 | 1 : 18 |
The trailer holds 775 derived values against four judgements, because the fifth would have to displace one of the four. You cannot escape a chain by simply choosing the number — and the other two exits are priced as well. A datum must name its source; unsourced, it is an opinion that looks like a measurement. A part must say whether it is made or purchased.
So the recursion has exactly three places to stop: a requirement, a sourced datum, or a budgeted judgement. Everything in between is forced to be a derivation, and the chain is the shape that leaves. There are 238 chains of the shape shown above running from a requirement to a dimensioned feature in the trailer store; I picked one to draw.
And how you know one is missing
Read backwards, the same rules say when a chain is missing. A requirement nothing claims is decorative. A dimension printed on a drawing that does not resolve to a store value is a third place numbers live — there is a check for exactly that, written after a four-inch clearance error reached a sheet and a human found it by reading. A part with neither a make nor a purchase route is an unmade decision wearing a part number.
The launch vehicle’s 1:2 ratio is the honest outlier. Twenty-four judgements against fifty-six derived values, budget fully spent — a design still mostly held by opinion. That is what an early vehicle looks like when the file is not flattering itself.
A requirement, a judgement and a datum, all the way to a dimension
Both of those chains ran from a single input. The real shape is a convergence, and this is that trace: one of each input kind, meeting, and continuing to a number on a sheet.
deck_length_in is a requirement, 480 in. sized_to_payload_lb is a datum, 48,000 lb with a source. contingency_fraction is a judgement, 0.08, chosen by a person. They meet at w_dump_lb_per_in — the judgement arriving through tipping_total_lb — and three steps later the answer is lb_dump_in, 96 in, the unbraced length the rail is checked over.
300 derived values in that store descend from all three kinds. Convergence is the normal case, not a curiosity.
And that last value read 100 in until an upstream pin moved four inches forward. It changed because the chain recomputed it. Nothing about the number sitting on a drawing would have told anyone it had moved — which is the failure this whole apparatus exists to make impossible.
When the chain starts at an opinion
The requirement and the datum in that figure are the comfortable terminals. The third one is not.
This one starts at an opinion — the eight percent this post opened on. contingency_fraction is 0.08 — eight percent, chosen by a person, spending one of the five slots the budget allows. Four steps later it is level_ground_rollover_deg: 9.90 degrees, the tilt at which the loaded trailer goes over.
An opinion sets a safety number. That is not an argument against opinions — every design is full of them, and a design that pretends otherwise is lying about itself. It is an argument for making them findable: budgeted, attributed, and attached to what they decide. The alternative is not a design without judgements. It is a design whose judgements you cannot locate.
The field a kernel has nowhere to put
Here is one judgement, verbatim from the store.
The value is 1.8. A geometry kernel can hold that fine — it is a float.
What it cannot hold is the rest of the card. because says a trailer beam sees its payload through a suspension over a road, that 1.8 is an opening figure and not a derivation, and — bluntly — that it is an opinion nobody has yet checked against the twenty years of hauling experience sitting on the other side of the conversation. rejected records the two numbers that were considered and set aside, 1.5 and 2.5, with what each would have done to the beam.
That second field is the one I would defend hardest. An engineering record that only holds what was chosen is missing the half that makes the choice legible. A year later, the question is never “what is the dynamic load factor” — the number is right there. It is “why isn’t it 1.5”, and if nobody wrote that down, the honest answer is that you are re-deciding it from scratch.
What comes out the other end
Every chain above ends in a number. Here is the sheet those numbers land on — a real drawing out of the trailer set, generated from the same design.yaml the figures above read.
480 DECK LENGTH is dimensioned on the plan view. That is the requirement the first chain started from, appearing as a dimension a fabricator would measure to. The camber that chain ends at is not on this sheet, because it belongs on the rail detail; but it comes from the same store, on the same run.
The sheet’s own notes say the thing this whole post is arguing:
Every dimension shown resolves from
design.yaml— the store is the authority and this sheet is a view of it.
And then the title block, which is the part I did not expect to care about.
DRAWN: GENERATED — design.yaml. Not a person’s initials. The drawing states where it came from, in the box that has held a draftsman’s name for a century.
APPR: NOT REVIEWED and STATUS: CONCEPT · NOT FOR FAB. No engineer has signed this and the sheet says so on its face rather than leaving a reader to assume. That matters more than it sounds: a drawing that looks finished is treated as finished, and the only defence is for the sheet to be honest about its own status in the place people look.
INTENT: 3e5fa4127b7e8b4b. A fingerprint of the store state this sheet was generated from. Two sheets carrying different hashes were made from different stores, whatever their revision letters say — which is a question you cannot ask of a drawing that was drawn by hand.
The same sheet, on a launch vehicle
Nothing above is specific to a trailer. Here is the general arrangement of the launch vehicle, out of the same kind of store and generated by the same kind of script.
Its note 14 says where it came from: generated from design.yaml; every dimension resolves from the store at generation, nothing here is a typed length. Note 2 puts it more sharply — the sheet is regenerated from the store, never maintained.
But the part worth enlarging is in the top right.
Every row names the store value it reads. Mount reaction 251.0 kN, mount_static. Hold-down uplift 138.0 kN, holddown_uplift. LOX to load 11.915 m³, service_s1_ox_vol. Ground wind that sizes the hold-down, 25.18 m/s, groundwind_speed_governing.
That is the whole argument printed on a drawing. Not “these numbers came from somewhere” — the id, beside the value, on the sheet a pad engineer would read. If one moves, you know what to search for.
And the sheet is candid about what the store does not contain, which is not a thing drawings usually do. Note 3: there is no parts list and there are no balloons, because design.yaml holds zero part nodes, so nothing drawn here is an item anybody buys, makes or counts. Note 9: the ground segment is emitted, not assumed. Note 10 is a list of what is still not emitted, each with what pulls it back.
A drawing that states its own gaps is doing something a finished-looking sheet cannot. The trailer’s title block says no engineer has signed it. This one goes further and says which questions it has not answered yet.
Most of a machine is consequences
One bookkeeping note, because the counts in this post vary and a reader is entitled to know why. The trailer’s store holds 1,113 nodes. 1,080 of them are values; the rest are structure — groups, assemblies and relations that organise the tree and carry no number of their own. Of those 1,080, 276 are inputs — requirements, datums and judgements — and the remaining 804 are derived or objective. Different figures here count different subsets, and each says which.
Here is what is currently in four design stores I maintain — all of them still moving. These are not illustrative numbers; the figure is generated by reading the live design.yaml out of each repo.
The dump trailer holds 269 datums and 775 derived values. Roughly three quarters of the numbers in that machine are not inputs at all — they are consequences of other numbers, standing in a chain that reaches back to something a person measured, quoted, or decided.
That ratio is the whole problem. A CAD model stores the result. It does not store the chain.
One number moves 501 others
The graph answers a sharper question than any single chain does. For every input in the trailer — requirement, datum or judgement alike — how many values change if this one moves?
The top three happen to be one of each kind, which is the argument in miniature. deck_length_in is a requirement and moves 501 values. sized_to_payload_lb is a datum and moves 343. contingency_fraction is a judgement — a number somebody chose — and moves 254. Meanwhile 66 of 276 inputs have no dependents at all; they are inert.
Nothing about a number’s appearance tells you which kind it is. The ones that carry the machine and the ones that carry nothing look identical in a spreadsheet, in a drawing title block, and in a CAD parameter table. The only thing that distinguishes them is the graph, and the graph only exists if something built it.
Every serious mistake I have made on these machines has the same shape: a figure that was correct when it was written, copied into a second place, and never compared again. Not a wrong calculation — a stale one. Programmable geometry does not help with that at all, because both copies are perfectly valid geometry.
“My CAD already does this”
That is the first thing a mechanical engineer says to all of the above, and it is largely right. A parametric model is a dependency graph. A feature tree with equations, driven dimensions and design tables: change a parameter, rebuild, everything downstream moves. SolidWorks, Creo and NX have done that for decades. Store the derivation rather than the result is not a new idea.
Four things are different, and the first one is the whole argument.
The parametric graph stops at the model boundary. It holds what the geometry needs in order to regenerate. It does not hold the deflection calculation, the weld sizing, the centre-of-gravity height or the rollover angle — those are analysis, and in an ordinary workflow they live in a spreadsheet, an FEA report and somebody’s hand calcs. The CAD parameter is a copy of their output.
I can put a number on that. Of the 1,080 values in the trailer store, 267 are dimensional — the kind of thing that could be a CAD parameter. The other 813 are loads, stresses, densities, allowables, ratios and factors. Three quarters of the store is the part a feature tree was never going to contain, and it is the part the dimensions are derived from.
A parameter has no kind. D1 = 1.8 does not know whether 1.8 was measured, chosen or computed. In the store that value is typed as a judgement, spends one of five budget slots, and carries because and rejected. An equation can carry a comment. It cannot carry a budget.
Requirements are not in it at all. No parametric model holds “the deck is 40 ft” as a node that dimensions derive from and that coverage can be audited against. You cannot ask a feature tree which requirements nothing discharges.
And the graph is not queryable across the machine. The 501-value blast radius spans loads, stresses, angles and dimensions across eight assemblies and everything under them. Most of those values were never in any model, so the question cannot be put to one.
The sharpest way to say it: in parametric CAD the dimension is the source of truth. In a store, the dimension is an output.
And the model does something the store does not do at all. It solves geometry — mates, constraints, interference, tangency. My store has no geometry solver and no opinion about whether two parts fit; that is exactly what the CAD is for. These are not competitors. The store sits upstream and produces the numbers the model is driven by.
What follows from this
Three things I would now say to anyone building on the design-as-code thesis, or buying it:
Type your values. A datum, a judgement, a derived result and a converged solution are four different kinds of thing with four different failure modes, and a spreadsheet renders them identically. The typing is what lets a check ask “is this still true?” — a question you cannot even pose about an untyped number.
Store the derivation, not the result. If a value came from an expression, keep the expression. That single habit is what turns a pile of numbers into a graph, and the graph is what makes blast radius computable.
Write down the rejected alternative. It costs one field and it is the only durable defence against re-litigating a decision every time someone new looks at it.
None of this requires a particular kernel, a particular language, or anyone’s product. It is a discipline about what you keep — and it is orthogonal to how the geometry gets made, which is exactly why the two halves can be built independently and why only one of them is currently getting attention.
Programmable geometry is genuinely good. It is just not the part that was hard.