Writing · September 2026

Programmable geometry is the easy half

Design-as-code is arriving commercially, and making the geometry programmable is the visible half. The half that decides whether it holds is provenance — and every figure below is read out of the live design stores of four unfinished machines.


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.

Three input cards side by side under a rule labelled THE PROVENANCE LAYER: Requirements (stated), Datums (given) and Judgements (chosen), each in its own colour. Curved arrows run from all three down into a single box, Derived values (consequences), which is described as computed from the inputs above and most of the store. Straight arrows continue to Geometry (shape) and then Artifacts (outputs), the last two shaded and bracketed on the right as what design as code usually means.
Three kinds of input, none derived from another, converging on everything below them. A tool that makes the bottom two programmable has not touched what determines them.

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 vertical chain of five value cards joined by arrows, each arrow labelled with the derivation expression that produces the next value. deck_length_in, a requirement, 480 inches, 501 values downstream. Then w_deck_lb_per_in at 6.416 pounds per inch, w_total_lb_per_in at 60.396, deflection_total_in at 0.674 inches, and finally camber_recommended_in at 0.116 inches, highlighted. A note beneath says the last value is a fabrication instruction: camber each rail 0.116 inches so the empty deck sits flat.
Five derivations separate a stated deck length from a number a fabricator builds to. No drawing of the beam records that the two are connected.

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.

A vertical chain of six value cards from the launch-vehicle store, joined by arrows carrying their derivations. park_alt_m, an objective, 200,000 metres. Then a_transfer at 6,703,500 metres, coast_s at 2,731 seconds, settle_accel_floor at 4.32e-07 metres per second squared, settle_dv_floor at 0.00118 metres per second, and settle_dv_span at 10,651.767, highlighted. A note beneath says the last value is a ratio of 10,652: one input was never stated, leaving the settling propellant uncertain across four orders of magnitude.
The same machinery on a different machine. This chain does not end at a number to build to — it ends at how much the answer is not known, because one input was never supplied.

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:

machinebudgetjudgementsderivedratio
dump trailer547751 : 193
launch vehicle2424561 : 2
geneva indexer331331 : 44
dynamometer33541 : 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.

Three input cards across the top: deck_length_in, a requirement, 480; sized_to_payload_lb, a datum, 48,000; and contingency_fraction, a judgement, 0.08, marked in a contrasting colour. Curved arrows run from the requirement and the datum down to w_dump_lb_per_in at 62.246 pounds per inch per rail, and from the judgement across to tipping_total_lb at 11,756 pounds, which also feeds w_dump_lb_per_in. Its derivation is printed beneath. A straight chain continues to x_inflection_dump_in at 160.549 inches and then to lb_dump_in at 96 inches, highlighted.
One of each input kind, converging on a single derived value, and three steps later a length the rail is checked over.

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.

A vertical chain of four value cards. contingency_fraction, a judgement, 0.08, marked in a contrasting colour and glossed as eight percent, a person chose this, with 254 values downstream. Then tipping_total_lb at 11,756 pounds, system_cg_height_in at 206.192 inches above ground, and level_ground_rollover_deg at 9.904 degrees, highlighted. A note beneath observes that an opinion sets a safety number.
This chain starts at an opinion. Four steps from somebody’s eight percent is the angle at which the loaded trailer rolls over.

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.

A single value card. The identifier dynamic_load_factor, value 1.8, kind judgement, with 56 values depending on it. Below a rule, two labelled prose fields. The because field explains that a trailer beam sees its payload through a suspension over a road, that 1.8 is an opening figure, and states plainly that it is an opinion nobody has checked against the stakeholder's twenty years of hauling. The rejected field records 1.5 and 2.5 as alternatives, with what each would have done to the required section.
The value is one number. The record around it is the rest — including the alternative that was considered and set aside, and why.

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.

A full A2 engineering general-arrangement sheet for a combination flatbed dump trailer. A plan view dimensioned 480 deck length and 488 overall, a side elevation showing the tipping body, hoist cylinders and tandem axles, an end view, and a small pictorial. Below them, dense general notes. A parts list and title block occupy the bottom right, with a zoning grid numbered one to twelve around the border.
A generated sheet, FDT-GA-01. 480 DECK LENGTH is dimensioned on the plan view — the first node of the chain above, appearing as a dimension a fabricator would measure to.

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.

A close crop of the drawing's title block and parts list. The drawing is FDT-GA-01, sheet 1 of 18. Material reads STEEL — SELECTION OPEN, scale 1:50, date 2026-08-30. The DRAWN field reads GENERATED — design.yaml. The APPR field reads NOT REVIEWED — SEE NOTES. Status reads CONCEPT · NOT FOR FAB, and an INTENT field carries the hash 3e5fa4127b7e8b4b.
The box that has held a draftsman’s name for a century, reading GENERATED — design.yaml. The sheet also states that no engineer has reviewed it, and carries a fingerprint of the store it came from.

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.

An A2 general-arrangement sheet for a two-stage launch vehicle. A side elevation stationed from the nose tip runs across the top, 24.332 metres overall, with servicing bands, hold-down reaction band and launch mount datum plane called out in phantom. Four detail views beneath show the forward end, the stage break, the aft end and an end elevation. A ground interface schedule occupies the top right. Fourteen numbered general notes fill the lower half, and a title block sits bottom right.
LB-GA-01. Same store format, same generation, a machine with nothing in common with a trailer.

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.

A close crop of the ground interface schedule. Three columns headed REQUIRED OF THE PAD, VALUE, and STORE VALUE. Rows include mount reaction static at 251.0 kilonewtons naming mount_static, hold-down uplift limit at 138.0 kilonewtons naming holddown_uplift, LOX to load stage 1 at 11.915 cubic metres naming service_s1_ox_vol, ground wind that sizes the hold-down at 25.18 metres per second naming groundwind_speed_governing, and acoustic power radiated at liftoff at 5.062 megawatts naming acoustic_power.
Every row names the store value it reads. The identifier sits beside the number, on the sheet a pad engineer would read.

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.

Four horizontal stacked bars, one per machine, segmented by kind of value. The dump trailer bar is by far the longest at 1,080 of 1,113 nodes, dominated by 269 datums and 775 derived values. The launch vehicle shows 247 of 260 with a large converged segment of 121. The geneva indexer shows 189 of 221 and the dynamometer 100 of 132. Requirements are the leftmost segment on every bar and are too few to see at this scale — three on the trailer, six across all four stores. A legend totals requirement 6, datum 385, judgement 34, objective 52, derived 1,018 and converged 121.
Every value in four stores, by kind. Derived values outnumber the inputs they come from roughly three to one — most of a machine is consequences.

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?

A horizontal bar chart of the eight inputs with the largest downstream dependency cones in the trailer store. deck_length_in leads at 501, marked as a requirement. sized_to_payload_lb follows at 343 as a datum, then contingency_fraction at 254, marked in a contrasting colour and labelled a person chose this. Then rear_beam_depth_in 204, rail_depth_in 184, kingpin_setting_in 175, dump_payload_ratio 156 and steel_density_lb_in3 153. A note beneath records that 66 of 276 inputs have no downstream dependents at all.
Ranked by transitive dependency cone, read out of the store’s own derivations. The top three are one of each kind — a requirement, a datum and a judgement.

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.